feat(region): write several tests and finish SRgn functionality

This commit is contained in:
Adam Heinermann 2025-04-22 17:31:02 -07:00 committed by fallenoak
parent e7d8124b43
commit 8d86943c94
3 changed files with 401 additions and 41 deletions

View file

@ -11,6 +11,7 @@ static TSExportTableSyncReuse<RGN, HSRGN, HLOCKEDRGN, CCritSect> s_rgntable;
void DeleteCombinedRect(TSGrowableArray<RECTF>* combinedArray, uint32_t index);
void DeleteRect(RECTF* rect);
void DeleteSourceRect(TSGrowableArray<SOURCE>* sourceArray, uint32_t index);
int32_t IsNullRect(const RECTF* rect);
@ -184,32 +185,104 @@ void FragmentCombinedRectangles(TSGrowableArray<RECTF>* combinedArray, uint32_t
}
void FragmentSourceRectangles(TSGrowableArray<SOURCE>* sourceArray, uint32_t firstIndex, uint32_t lastIndex, int32_t previousOverlap, const RECTF* rect, void* param, int32_t sequence) {
if (firstIndex >= lastIndex) {
AddSourceRect(sourceArray, rect, param, sequence, SF_ADDING | (previousOverlap ? SF_OVERLAPS : SF_NONE));
return;
}
auto overlapsExisting = previousOverlap;
for (uint32_t i = firstIndex; i < lastIndex; i++) {
auto source = &(*sourceArray)[i];
for (uint32_t index = firstIndex; index < lastIndex; index++) {
RECTF* checkRect = &(*sourceArray)[index].rect;
if (CheckForIntersection(rect, &source->rect)) {
if (!CompareRects(rect, &source->rect)) {
break;
}
source->flags |= SF_OVERLAPS;
if (CheckForIntersection(rect, checkRect)) {
if (CompareRects(rect, checkRect)) {
(*sourceArray)[index].flags |= SF_OVERLAPS;
overlapsExisting = 1;
continue;
}
if (i + 1 == lastIndex) {
AddSourceRect(sourceArray, rect, param, sequence, SF_ADDING | (previousOverlap ? SF_OVERLAPS : SF_NONE));
const RECTF* overlapRect[2] = { rect, checkRect };
uint32_t minLeft = rect->left > checkRect->left ? 1 : 0;
uint32_t maxLeft = checkRect->left > rect->left ? 1 : 0;
uint32_t minBottom = rect->bottom > checkRect->bottom ? 1 : 0;
uint32_t maxBottom = checkRect->bottom > rect->bottom ? 1 : 0;
uint32_t minRight = rect->right > checkRect->right ? 1 : 0;
uint32_t maxRight = checkRect->right > rect->right ? 1 : 0;
uint32_t minTop = rect->top > checkRect->top ? 1 : 0;
uint32_t maxTop = checkRect->top > rect->top ? 1 : 0;
RECTF newRect[5];
newRect[0] = {
overlapRect[minBottom]->left,
overlapRect[minBottom]->bottom,
overlapRect[minBottom]->right,
overlapRect[maxBottom]->bottom,
};
newRect[1] = {
overlapRect[maxTop]->left,
overlapRect[minTop]->top,
overlapRect[maxTop]->right,
overlapRect[maxTop]->top,
};
newRect[2] = {
overlapRect[minLeft]->left,
overlapRect[maxBottom]->bottom,
overlapRect[maxLeft]->left,
overlapRect[minTop]->top,
};
newRect[3] = {
overlapRect[minRight]->right,
overlapRect[maxBottom]->bottom,
overlapRect[maxRight]->right,
overlapRect[minTop]->top,
};
newRect[4] = {
overlapRect[maxLeft]->left,
overlapRect[maxBottom]->bottom,
overlapRect[minRight]->right,
overlapRect[minTop]->top,
};
int32_t overlaps[5][2];
for (uint32_t j = 0; j < 5; j++) {
if (IsNullRect(&newRect[j])) {
overlaps[j][1] = 0;
overlaps[j][0] = 0;
}
else {
for (uint32_t k = 0; k < 2; k++) {
overlaps[j][k] = CheckForIntersection(&newRect[j], overlapRect[k]);
}
}
}
for (uint32_t j = 0; j < 5; j++) {
if (overlaps[j][0]) {
FragmentSourceRectangles(
sourceArray,
index + 1,
lastIndex,
overlapsExisting || overlaps[j][1],
&newRect[j],
param,
sequence);
}
if (overlaps[j][1]) {
uint32_t newFlags = (*sourceArray)[index].flags & ~SF_TEMPMASK;
newFlags |= overlaps[j][0] ? SF_OVERLAPS : SF_NONE;
AddSourceRect(sourceArray, &newRect[j], (*sourceArray)[index].param, (*sourceArray)[index].sequence, newFlags);
}
}
DeleteSourceRect(sourceArray, index);
return;
}
}
// TODO
AddSourceRect(sourceArray, rect, param, sequence, SF_ADDING | (overlapsExisting ? SF_OVERLAPS : SF_NONE));
}
int32_t IsNullRect(const RECTF* rect) {
@ -242,20 +315,20 @@ void DeleteSourceRect(TSGrowableArray<SOURCE>* sourceArray, uint32_t index) {
}
void OptimizeSource(TSGrowableArray<SOURCE>* sourceArray) {
for (uint32_t i = 0; i < sourceArray->Count(); i++) {
auto source = &(*sourceArray)[i];
for (uint32_t index = 0; index < sourceArray->Count(); ) {
auto source = &(*sourceArray)[index];
if (IsNullRect(&source->rect)) {
// Set current (null) element to last element
auto last = &(*sourceArray)[sourceArray->Count() - 1];
(*sourceArray)[i] = *last;
// Decrement index by 1 to force null check on copied last element on next tick
i--;
(*sourceArray)[index] = *last;
// Shrink by 1 (to account for the removal of the null element)
sourceArray->SetCount(sourceArray->Count() - 1);
}
else {
index++;
}
}
}

View file

@ -7,7 +7,7 @@
#include <cstdint>
// region flgs
#define SF_NONE 0x00
#define SF_NONE 0x00000000
#define SF_ADDING 0x00000001
#define SF_OVERLAPS 0x00000002
#define SF_TEMPMASK 0x00000003

View file

@ -1,10 +1,13 @@
#include "RegionTest.hpp"
// Note: diagrams are y inverted when building for WoW,
// still conveys understanding.
TEST_CASE("SRgnClear", "[region]") {
RgnDataTest region;
SECTION("operates on an empty object") {
uint32_t numrects = 0;
uint32_t numrects = 1;
SRgnClear(region);
@ -12,6 +15,11 @@ TEST_CASE("SRgnClear", "[region]") {
CHECK(numrects == 0);
}
SECTION("runs on an invalid pointer") {
HSRGN inval = reinterpret_cast<HSRGN>(1234);
CHECK_NOTHROW(SRgnClear(inval));
}
SECTION("clears rects out of a region object") {
uint32_t numrects = 0;
@ -26,6 +34,23 @@ TEST_CASE("SRgnClear", "[region]") {
SRgnGetRectsf(region, &numrects, nullptr);
CHECK(numrects == 0);
}
SECTION("clears params out of a region object") {
uint32_t numParams = 1;
void* params[1];
RECTF baseRect = { 0.0f, 0.0f, 1.0f, 1.0f };
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
SRgnCombineRectf(region, &baseRect, &baseRect, SRGN_PARAMONLY);
SRgnGetRectParamsf(region, &baseRect, &numParams, params);
CHECK(numParams == 1);
SRgnClear(region);
SRgnGetRectParamsf(region, &baseRect, &numParams, params);
CHECK(numParams == 0);
}
}
TEST_CASE("SRgnCreate", "[region]") {
@ -41,9 +66,10 @@ TEST_CASE("SRgnCreate", "[region]") {
TEST_CASE("SRgnCombineRectf", "[region]") {
RgnDataTest region;
RECTF baseRect = { 0.0f, 0.0f, 1.0f, 1.0f };
RECTF testRect = { 0.5f, 0.6f, 1.8f, 1.4f };
SECTION("combines the region with a single given rect 1") {
RECTF baseRect = { 0.0f, 0.0f, 1.0f, 1.0f };
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
RECTF boundingRect = { 0.0f, 0.0f, 0.0f, 0.0f };
@ -53,7 +79,6 @@ TEST_CASE("SRgnCombineRectf", "[region]") {
}
SECTION("combines the region with multiple given rects 1") {
RECTF baseRect = { 0.0f, 0.0f, 1.0f, 1.0f };
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
RECTF newRect = { 0.0f, 0.0f, 0.0f, 0.0f };
@ -66,7 +91,6 @@ TEST_CASE("SRgnCombineRectf", "[region]") {
}
SECTION("combines the region with multiple given rects 2") {
RECTF baseRect = { 0.0f, 0.0f, 1.0f, 1.0f };
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
RECTF newRect = { 0.0f, 0.0f, 1.0f, 1.0f };
@ -79,7 +103,6 @@ TEST_CASE("SRgnCombineRectf", "[region]") {
}
SECTION("combines the region with multiple given rects 3") {
RECTF baseRect = { 0.0f, 0.0f, 1.0f, 1.0f };
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
RECTF newRect = { 0.0f, 1.0f, 1.0f, 1.0f };
@ -90,6 +113,202 @@ TEST_CASE("SRgnCombineRectf", "[region]") {
CHECK_THAT(boundingRect, MatchesRect({ 0.0f, 0.0f, 1.0f, 1.0f }));
}
SECTION("OR operation combines rects") {
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
SRgnCombineRectf(region, &testRect, nullptr, SRGN_OR);
uint32_t numRects = 4;
RECTF buffer[4];
SRgnGetRectsf(region, &numRects, buffer);
// ┌─────────┐
// │ 0 │
// │┈┈┈┈┌────┼──────┐
// │ 1 │
// └────┼────┘┈┈┈┈┈┈│
// │ 2 │
// └───────────┘
CHECK(numRects == 3);
CHECK_THAT(buffer[0], MatchesRect({ 0.0f, 0.0f, 1.0f, 0.6f }));
CHECK_THAT(buffer[1], MatchesRect({ 0.0f, 0.6f, 1.8f, 1.0f }));
CHECK_THAT(buffer[2], MatchesRect({ 0.5f, 1.0f, 1.8f, 1.4f }));
}
SECTION("AND operation intersects rects") {
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
SRgnCombineRectf(region, &testRect, nullptr, SRGN_AND);
uint32_t numRects = 2;
RECTF buffer[2];
SRgnGetRectsf(region, &numRects, buffer);
// ┌─────────┐
// │╳╳╳╳╳╳╳╳╳│
// │╳╳╳╳┌────┼──────┐
// │╳╳╳╳│ 0 │╳╳╳╳╳╳│
// └────┼────┘╳╳╳╳╳╳│
// │╳╳╳╳╳╳╳╳╳╳╳│
// └───────────┘
CHECK(numRects == 1);
CHECK_THAT(buffer[0], MatchesRect({ 0.5f, 0.6f, 1.0f, 1.0f }));
}
SECTION("XOR operation takes exclusive differences of rects") {
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
SRgnCombineRectf(region, &testRect, nullptr, SRGN_XOR);
uint32_t numRects = 5;
RECTF buffer[5];
SRgnGetRectsf(region, &numRects, buffer);
// ┌─────────┐
// │ 0 │
// │┈┈┈┈┌────┼──────┐
// │ 1 │╳╳╳╳│ 2 │
// └────┼────┘┈┈┈┈┈┈│
// │ 3 │
// └───────────┘
CHECK(numRects == 4);
CHECK_THAT(buffer[0], MatchesRect({ 0.0f, 0.0f, 1.0f, 0.6f }));
CHECK_THAT(buffer[1], MatchesRect({ 0.0f, 0.6f, 0.5f, 1.0f }));
CHECK_THAT(buffer[2], MatchesRect({ 1.0f, 0.6f, 1.8f, 1.0f }));
CHECK_THAT(buffer[3], MatchesRect({ 0.5f, 1.0f, 1.8f, 1.4f }));
}
SECTION("DIFF operation removes parts of rects") {
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
SRgnCombineRectf(region, &testRect, nullptr, SRGN_DIFF);
uint32_t numRects = 5;
RECTF buffer[5];
SRgnGetRectsf(region, &numRects, buffer);
// ┌─────────┐
// │ 0 │
// │┈┈┈┈┌────┼──────┐
// │ 1 │╳╳╳╳│╳╳╳╳╳╳│
// └────┼────┘╳╳╳╳╳╳│
// │╳╳╳╳╳╳╳╳╳╳╳│
// └───────────┘
CHECK(numRects == 2);
CHECK_THAT(buffer[0], MatchesRect({ 0.0f, 0.0f, 1.0f, 0.6f }));
CHECK_THAT(buffer[1], MatchesRect({ 0.0f, 0.6f, 0.5f, 1.0f }));
}
SECTION("COPY operation splits intersecting rects") {
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
SRgnCombineRectf(region, &testRect, nullptr, SRGN_COPY);
uint32_t numRects = 5;
RECTF buffer[5];
SRgnGetRectsf(region, &numRects, buffer);
// ┌─────────┐
// │ 0 │
// │┈┈┈┈┌────┼──────┐
// │ 1 │╳╳╳╳╳╳│
// └────┼────┘╳╳╳╳╳╳│
// │╳╳╳╳╳╳╳╳╳╳╳│
// └───────────┘
CHECK(numRects == 2);
CHECK_THAT(buffer[0], MatchesRect({ 0.0f, 0.0f, 1.0f, 0.6f }));
CHECK_THAT(buffer[1], MatchesRect({ 0.0f, 0.6f, 1.0f, 1.0f }));
}
SECTION("COPY operation splits intersecting rects 2") {
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
RECTF testRect2 = { 0.4f, 0.4f, 0.6f, 0.6f };
SRgnCombineRectf(region, &testRect2, nullptr, SRGN_COPY);
uint32_t numRects = 5;
RECTF buffer[5];
SRgnGetRectsf(region, &numRects, buffer);
// ┌─────────┐
// │ 0 │
// │┈┈┈┌┈┐┈┈┈│
// │┈┈┈└┈┘┈1┈│
// │ 2 │
// └─────────┘
CHECK(numRects == 3);
CHECK_THAT(buffer[0], MatchesRect({ 0.0f, 0.0f, 1.0f, 0.4f }));
CHECK_THAT(buffer[1], MatchesRect({ 0.0f, 0.4f, 1.0f, 0.6f }));
CHECK_THAT(buffer[2], MatchesRect({ 0.0f, 0.6f, 1.0f, 1.0f }));
}
SECTION("operation doesn't work when operating rect width or height is <= 0") {
int32_t combineMode = GENERATE(SRGN_OR, SRGN_XOR, SRGN_DIFF, SRGN_COPY, SRGN_PARAMONLY);
RECTF testRects = GENERATE(
RECTF{ 0.5f, 0.0f, 0.5f, 1.0f },
RECTF{ 0.0f, 0.5f, 1.0f, 0.5f },
RECTF{ 1.0f, 0.0f, 0.5f, 1.0f },
RECTF{ 0.0f, 1.0f, 1.0f, 0.5f }
);
INFO("CombineMode = " << combineMode);
INFO("testRects = " << testRects);
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
uint32_t numRects = 5;
RECTF buffer[5];
SRgnCombineRectf(region, &testRects, &testRects, combineMode);
SRgnGetRectsf(region, &numRects, buffer);
CHECK(numRects == 1);
CHECK_THAT(buffer[0], MatchesRect(baseRect));
uint32_t numParams = 1;
SRgnGetRectParamsf(region, &testRects, &numParams, nullptr);
CHECK(numParams == 0);
SRgnGetRectParamsf(region, &baseRect, &numParams, nullptr);
CHECK(numParams == 1);
}
SECTION("AND kills all rects when rect width or height is <= 0") {
int32_t combineMode = SRGN_AND;
RECTF testRects = GENERATE(
RECTF{ 0.5f, 0.0f, 0.5f, 1.0f },
RECTF{ 0.0f, 0.5f, 1.0f, 0.5f },
RECTF{ 1.0f, 0.0f, 0.5f, 1.0f },
RECTF{ 0.0f, 1.0f, 1.0f, 0.5f }
);
INFO("CombineMode = " << combineMode);
INFO("testRects = " << testRects);
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
uint32_t numRects = 5;
RECTF buffer[5];
SRgnCombineRectf(region, &testRects, &testRects, combineMode);
SRgnGetRectsf(region, &numRects, buffer);
CHECK(numRects == 0);
uint32_t numParams = 1;
SRgnGetRectParamsf(region, &testRects, &numParams, nullptr);
CHECK(numParams == 0);
SRgnGetRectParamsf(region, &baseRect, &numParams, nullptr);
CHECK(numParams == 0);
}
SECTION("PARAM operation doesn't influence rects") {
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
SRgnCombineRectf(region, &testRect, nullptr, SRGN_PARAMONLY);
uint32_t numRects = 5;
RECTF buffer[5];
SRgnGetRectsf(region, &numRects, buffer);
CHECK(numRects == 1);
CHECK_THAT(buffer[0], MatchesRect(baseRect));
}
}
TEST_CASE("SRgnDelete", "[region]") {
@ -114,9 +333,9 @@ TEST_CASE("SRgnDelete", "[region]") {
TEST_CASE("SRgnDuplicate", "[region]") {
RgnDataTest region;
RECTF baseRect = { -1.0f, 1.0f, 1.0f, 2.0f };
SECTION("creates an independent copy of a region") {
RECTF baseRect = { -1.0f, 1.0f, 1.0f, 2.0f };
SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR);
HSRGN newrgn = nullptr;
@ -133,6 +352,28 @@ TEST_CASE("SRgnDuplicate", "[region]") {
CHECK_THAT(buffer[0], MatchesRect(baseRect));
}
SECTION("copies parms") {
SRgnCombineRectf(region, &baseRect, &baseRect, SRGN_PARAMONLY);
uint32_t numParams = 2;
void* buffer[2];
SRgnGetRectParamsf(region, &baseRect, &numParams, buffer);
CHECK(numParams == 1);
CHECK(buffer[0] == &baseRect);
HSRGN newrgn = nullptr;
SRgnDuplicate(region, &newrgn);
REQUIRE(newrgn != nullptr);
SRgnClear(region);
SRgnGetRectParamsf(newrgn, &baseRect, &numParams, buffer);
CHECK(numParams == 1);
CHECK(buffer[0] == &baseRect);
}
SECTION("sets handle to null when using an invalid region object") {
HSRGN inval = reinterpret_cast<HSRGN>(1234);
@ -244,6 +485,52 @@ TEST_CASE("SRgnGetRectsf", "[region]") {
CHECK_THAT(buffer[0], MatchesRect(rct1));
}
SECTION("retrieves rects in order of top value") {
RECTF rects[4] = {
{ 0, 0, 1, 1 },
{ 4, -2, 5, 5 },
{ -2, 2, -1, 3 },
{ 2, -2, 3, -1 },
};
for (int i = 0; i < 4; i++) {
SRgnCombineRectf(region, &rects[i], nullptr, SRGN_OR);
}
uint32_t numrects = 5;
RECTF buffer[5];
SRgnGetRectsf(region, &numrects, buffer);
CHECK(numrects == 4);
CHECK_THAT(buffer[0], MatchesRect(rects[3]));
CHECK_THAT(buffer[1], MatchesRect(rects[0]));
CHECK_THAT(buffer[2], MatchesRect(rects[2]));
CHECK_THAT(buffer[3], MatchesRect(rects[1]));
}
SECTION("retrieves rects in order of left value when tops are equal") {
RECTF rects[4] = {
{ 0, 0, 1, 1 },
{ 4, 0.5, 5, 1 },
{ -2, -2, -1, 1 },
{ 2, -1, 3, 1 },
};
for (int i = 0; i < 4; i++) {
SRgnCombineRectf(region, &rects[i], nullptr, SRGN_OR);
}
uint32_t numrects = 5;
RECTF buffer[5];
SRgnGetRectsf(region, &numrects, buffer);
CHECK(numrects == 4);
CHECK_THAT(buffer[0], MatchesRect(rects[2]));
CHECK_THAT(buffer[1], MatchesRect(rects[0]));
CHECK_THAT(buffer[2], MatchesRect(rects[3]));
CHECK_THAT(buffer[3], MatchesRect(rects[1]));
}
}
TEST_CASE("SRgnIsPointInRegionf", "[region]") {
@ -311,7 +598,7 @@ TEST_CASE("SRgnIsRectInRegionf", "[region]") {
CHECK_FALSE(SRgnIsRectInRegionf(inval, &rect));
}
SECTION("reports if rects overlap a region") {
SECTION("true if rects overlap a region") {
RECTF checkRects[] = {
{ 0.5f, 0.5f, 0.5f, 0.5f },
{ 0.00001f, 0.00001f, 0.999999f, 0.999999f },
@ -331,7 +618,7 @@ TEST_CASE("SRgnIsRectInRegionf", "[region]") {
CHECK(SRgnIsRectInRegionf(region, &checkRects[4]));
}
SECTION("reports if rects are outside a region") {
SECTION("false if rects are outside a region") {
RECTF checkRects[] = {
{ 2.0f, 2.0f, 2.0f, 2.0f },
{ 1.0f, 1.0f, 2.0f, 2.0f },
@ -379,8 +666,8 @@ TEST_CASE("SRgnOffsetf", "[region]") {
SECTION("shifts rects by given amount") {
RECTF rects[] = {
{ 0.0f, 0.0f, 100.0f, 100.0f },
{ -200.0f, -200.0f, -100.0f, -100.0f }
{ -200.0f, -200.0f, -100.0f, -100.0f },
{ 0.0f, 0.0f, 100.0f, 100.0f }
};
SRgnCombineRectf(region, &rects[0], nullptr, SRGN_OR);
@ -398,8 +685,8 @@ TEST_CASE("SRgnOffsetf", "[region]") {
SECTION("shifts rects back to their original positions with opposite amounts") {
RECTF rects[] = {
{ 0.0f, 0.0f, 100.0f, 100.0f },
{ -200.0f, -200.0f, -100.0f, -100.0f }
{ -200.0f, -200.0f, -100.0f, -100.0f },
{ 0.0f, 0.0f, 100.0f, 100.0f }
};
SRgnCombineRectf(region, &rects[0], nullptr, SRGN_OR);
@ -413,14 +700,14 @@ TEST_CASE("SRgnOffsetf", "[region]") {
RECTF buffer[2];
SRgnGetRectsf(region, &numRects, buffer);
CHECK_THAT(buffer[0], MatchesRect(rects[1]));
CHECK_THAT(buffer[1], MatchesRect(rects[0]));
CHECK_THAT(buffer[0], MatchesRect(rects[0]));
CHECK_THAT(buffer[1], MatchesRect(rects[1]));
}
SECTION("doesn't shift anything with 0") {
RECTF rects[] = {
{ 0.0f, 0.0f, 100.0f, 100.0f },
{ -200.0f, -200.0f, -100.0f, -100.0f }
{ -200.0f, -200.0f, -100.0f, -100.0f },
{ 0.0f, 0.0f, 100.0f, 100.0f }
};
SRgnCombineRectf(region, &rects[0], nullptr, SRGN_OR);
@ -432,7 +719,7 @@ TEST_CASE("SRgnOffsetf", "[region]") {
RECTF buffer[2];
SRgnGetRectsf(region, &numRects, buffer);
CHECK_THAT(buffer[0], MatchesRect(rects[1]));
CHECK_THAT(buffer[1], MatchesRect(rects[0]));
CHECK_THAT(buffer[0], MatchesRect(rects[0]));
CHECK_THAT(buffer[1], MatchesRect(rects[1]));
}
}