diff --git a/storm/Region.cpp b/storm/Region.cpp index 24e13a3..537e0d5 100644 --- a/storm/Region.cpp +++ b/storm/Region.cpp @@ -435,6 +435,21 @@ void SRgnCombineRectf(HSRGN handle, const RECTF* rect, void* param, int32_t comb s_rgntable.Unlock(lockedHandle); } +void SRgnCombineRecti(HSRGN handle, const RECT* rect, void* param, int32_t combineMode) { + STORM_VALIDATE_BEGIN; + STORM_VALIDATE(rect); + STORM_VALIDATE_END_VOID; + + // NOTE: top and bottom get flipped, this is a bug in Storm + RECTF rectf = { + static_cast(rect->left), + static_cast(rect->top), + static_cast(rect->right), + static_cast(rect->bottom) + }; + SRgnCombineRectf(handle, &rectf, param, combineMode); +} + void SRgnClear(HSRGN handle) { STORM_VALIDATE_BEGIN; STORM_VALIDATE(handle); @@ -539,6 +554,21 @@ void SRgnGetBoundingRectf(HSRGN handle, RECTF* rect) { } } +void SRgnGetBoundingRecti(HSRGN handle, RECT* rect) { + STORM_VALIDATE_BEGIN; + STORM_VALIDATE(rect); + STORM_VALIDATE_END_VOID; + + RECTF rectf; + SRgnGetBoundingRectf(handle, &rectf); + + // NOTE: top and bottom get flipped, this is a bug in Storm + rect->left = static_cast(rectf.left); + rect->top = static_cast(rectf.bottom); + rect->right = static_cast(rectf.right); + rect->bottom = static_cast(rectf.top); +} + void SRgnGetRectParamsf(HSRGN handle, const RECTF* rect, uint32_t* numParams, void** buffer) { STORM_VALIDATE_BEGIN; STORM_VALIDATE(handle); @@ -580,6 +610,21 @@ void SRgnGetRectParamsf(HSRGN handle, const RECTF* rect, uint32_t* numParams, vo s_rgntable.Unlock(lockedHandle); } +void SRgnGetRectParamsi(HSRGN handle, const RECT* rect, uint32_t* numParams, void** buffer) { + STORM_VALIDATE_BEGIN; + STORM_VALIDATE(rect); + STORM_VALIDATE_END_VOID; + + // NOTE: top and bottom get flipped, this is a bug in Storm + RECTF rectf = { + static_cast(rect->left), + static_cast(rect->top), + static_cast(rect->right), + static_cast(rect->bottom) + }; + SRgnGetRectParamsf(handle, &rectf, numParams, buffer); +} + void SRgnGetRectsf(HSRGN handle, uint32_t* numRects, RECTF* buffer) { STORM_VALIDATE_BEGIN; STORM_VALIDATE(handle); @@ -609,6 +654,22 @@ void SRgnGetRectsf(HSRGN handle, uint32_t* numRects, RECTF* buffer) { s_rgntable.Unlock(lockedHandle); } +void SRgnGetRectsi(HSRGN handle, uint32_t* numRects, RECT* buffer) { + RECTF* bufferf = reinterpret_cast(buffer); + SRgnGetRectsf(handle, numRects, bufferf); + if (buffer) { + for (uint32_t i = 0; i < *numRects; i++) { + float bottom = bufferf[i].bottom; + float top = bufferf[i].top; + + buffer[i].left = static_cast(bufferf[i].left); + buffer[i].bottom = static_cast(bottom); + buffer[i].right = static_cast(bufferf[i].right); + buffer[i].top = static_cast(top); + } + } +} + int32_t SRgnIsPointInRegionf(HSRGN handle, float x, float y) { STORM_VALIDATE_BEGIN; STORM_VALIDATE(handle); @@ -638,6 +699,10 @@ int32_t SRgnIsPointInRegionf(HSRGN handle, float x, float y) { return result; } +int32_t SRgnIsPointInRegioni(HSRGN handle, int32_t x, int32_t y) { + return SRgnIsPointInRegionf(handle, static_cast(x), static_cast(y)); +} + int32_t SRgnIsRectInRegionf(HSRGN handle, const RECTF* rect) { STORM_VALIDATE_BEGIN; STORM_VALIDATE(handle); @@ -667,6 +732,21 @@ int32_t SRgnIsRectInRegionf(HSRGN handle, const RECTF* rect) { return result; } +int32_t SRgnIsRectInRegioni(HSRGN handle, const RECT* rect) { + STORM_VALIDATE_BEGIN; + STORM_VALIDATE(rect); + STORM_VALIDATE_END; + + // NOTE: top and bottom get flipped, this is a bug in Storm + RECTF rectf = { + static_cast(rect->left), + static_cast(rect->top), + static_cast(rect->right), + static_cast(rect->bottom) + }; + return SRgnIsRectInRegionf(handle, &rectf); +} + void SRgnOffsetf(HSRGN handle, float xoffset, float yoffset) { STORM_VALIDATE_BEGIN; STORM_VALIDATE(handle); @@ -690,3 +770,7 @@ void SRgnOffsetf(HSRGN handle, float xoffset, float yoffset) { s_rgntable.Unlock(lockedHandle); } + +void SRgnOffseti(HSRGN handle, int32_t xoffset, int32_t yoffset) { + SRgnOffsetf(handle, static_cast(xoffset), static_cast(yoffset)); +} diff --git a/storm/Region.hpp b/storm/Region.hpp index cb5a8c3..ea5aa61 100644 --- a/storm/Region.hpp +++ b/storm/Region.hpp @@ -8,6 +8,8 @@ void SRgnClear(HSRGN handle); void SRgnCombineRectf(HSRGN handle, const RECTF* rect, void* param, int32_t combineMode); +void SRgnCombineRecti(HSRGN handle, const RECT* rect, void* param, int32_t combineMode); + void SRgnCreate(HSRGN* handlePtr, uint32_t reserved = 0); void SRgnDelete(HSRGN handle); @@ -16,14 +18,26 @@ void SRgnDuplicate(HSRGN origHandle, HSRGN* handle, uint32_t reserved = 0); void SRgnGetBoundingRectf(HSRGN handle, RECTF* rect); +void SRgnGetBoundingRecti(HSRGN handle, RECT* rect); + void SRgnGetRectParamsf(HSRGN handle, const RECTF* rect, uint32_t* numParams, void** buffer); +void SRgnGetRectParamsi(HSRGN handle, const RECT* rect, uint32_t* numParams, void** buffer); + void SRgnGetRectsf(HSRGN handle, uint32_t* numRects, RECTF* buffer); +void SRgnGetRectsi(HSRGN handle, uint32_t* numRects, RECT* buffer); + int32_t SRgnIsPointInRegionf(HSRGN handle, float x, float y); +int32_t SRgnIsPointInRegioni(HSRGN handle, int32_t x, int32_t y); + int32_t SRgnIsRectInRegionf(HSRGN handle, const RECTF* rect); +int32_t SRgnIsRectInRegioni(HSRGN handle, const RECT* rect); + void SRgnOffsetf(HSRGN handle, float xoffset, float yoffset); +void SRgnOffseti(HSRGN handle, int32_t xoffset, int32_t yoffset); + #endif diff --git a/storm/region/Types.hpp b/storm/region/Types.hpp index 7fea81e..c7d3766 100644 --- a/storm/region/Types.hpp +++ b/storm/region/Types.hpp @@ -3,9 +3,9 @@ #include "storm/Handle.hpp" #include +#include DECLARE_STORM_HANDLE(HSRGN); - DECLARE_STORM_HANDLE(HLOCKEDRGN); struct RECTF { @@ -15,6 +15,15 @@ struct RECTF { float top; }; +#if defined(WHOA_SYSTEM_WIN) +// NOTE: WINAPI's RECT uses `long`. +#include +#else +struct RECT { + int32_t left, top, right, bottom; +}; +#endif + // Combine modes #define SRGN_AND 1 #define SRGN_OR 2 diff --git a/test/Region.cpp b/test/Region.cpp index 2246531..83a7ebc 100644 --- a/test/Region.cpp +++ b/test/Region.cpp @@ -1,6 +1,9 @@ #include "RegionTest.hpp" #include +static_assert(sizeof(RECT::left) == sizeof(RECTF::left), "RECT doesn't match RECTF"); +static_assert(sizeof(RECT) == sizeof(RECTF), "RECT doesn't match RECTF"); + TEST_CASE("SRgnClear", "[region]") { RgnDataTest region; @@ -78,7 +81,7 @@ TEST_CASE("SRgnCombineRectf", "[region]") { RECTF boundingRect = { 0.0f, 0.0f, 0.0f, 0.0f }; SRgnGetBoundingRectf(region, &boundingRect); - CHECK_THAT(boundingRect, MatchesRect({ 0.0f, 0.0f, 1.0f, 1.0f })); + CHECK_THAT(boundingRect, MatchesRectf({ 0.0f, 0.0f, 1.0f, 1.0f })); } SECTION("combines the region with multiple given rects 1") { @@ -90,7 +93,7 @@ TEST_CASE("SRgnCombineRectf", "[region]") { RECTF boundingRect = { 0.0f, 0.0f, 0.0f, 0.0f }; SRgnGetBoundingRectf(region, &boundingRect); - CHECK_THAT(boundingRect, MatchesRect({ 0.0f, 0.0f, 1.0f, 1.0f })); + CHECK_THAT(boundingRect, MatchesRectf({ 0.0f, 0.0f, 1.0f, 1.0f })); } SECTION("combines the region with multiple given rects 2") { @@ -102,7 +105,7 @@ TEST_CASE("SRgnCombineRectf", "[region]") { RECTF boundingRect = { 0.0f, 0.0f, 0.0f, 0.0f }; SRgnGetBoundingRectf(region, &boundingRect); - CHECK_THAT(boundingRect, MatchesRect({ 0.0f, 0.0f, 0.0f, 0.0f })); + CHECK_THAT(boundingRect, MatchesRectf({ 0.0f, 0.0f, 0.0f, 0.0f })); } SECTION("combines the region with multiple given rects 3") { @@ -114,7 +117,7 @@ TEST_CASE("SRgnCombineRectf", "[region]") { RECTF boundingRect = { 0.0f, 0.0f, 0.0f, 0.0f }; SRgnGetBoundingRectf(region, &boundingRect); - CHECK_THAT(boundingRect, MatchesRect({ 0.0f, 0.0f, 1.0f, 1.0f })); + CHECK_THAT(boundingRect, MatchesRectf({ 0.0f, 0.0f, 1.0f, 1.0f })); } SECTION("OR operation combines rects") { @@ -133,9 +136,9 @@ TEST_CASE("SRgnCombineRectf", "[region]") { // │ 0 │ // └─────────┘ 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 })); + CHECK_THAT(buffer[0], MatchesRectf({ 0.0f, 0.0f, 1.0f, 0.6f })); + CHECK_THAT(buffer[1], MatchesRectf({ 0.0f, 0.6f, 1.8f, 1.0f })); + CHECK_THAT(buffer[2], MatchesRectf({ 0.5f, 1.0f, 1.8f, 1.4f })); } SECTION("OR operation on self does nothing") { @@ -147,7 +150,7 @@ TEST_CASE("SRgnCombineRectf", "[region]") { SRgnGetRectsf(region, &numRects, buffer); CHECK(numRects == 1); - CHECK_THAT(buffer[0], MatchesRect({ 0.0f, 0.0f, 1.0f, 1.0f })); + CHECK_THAT(buffer[0], MatchesRectf({ 0.0f, 0.0f, 1.0f, 1.0f })); } SECTION("OR operation merges vertical rects") { @@ -162,7 +165,7 @@ TEST_CASE("SRgnCombineRectf", "[region]") { SRgnGetRectsf(region, &numRects, buffer); CHECK(numRects == 1); - CHECK_THAT(buffer[0], MatchesRect({ 0.0f, 0.0f, 1.0f, 3.0f })); + CHECK_THAT(buffer[0], MatchesRectf({ 0.0f, 0.0f, 1.0f, 3.0f })); } SECTION("OR operation merges vertical rects 2") { @@ -177,7 +180,7 @@ TEST_CASE("SRgnCombineRectf", "[region]") { SRgnGetRectsf(region, &numRects, buffer); CHECK(numRects == 1); - CHECK_THAT(buffer[0], MatchesRect({ 0.0f, 0.0f, 1.0f, 3.0f })); + CHECK_THAT(buffer[0], MatchesRectf({ 0.0f, 0.0f, 1.0f, 3.0f })); } SECTION("OR operation merges horizontal rects") { @@ -192,7 +195,7 @@ TEST_CASE("SRgnCombineRectf", "[region]") { SRgnGetRectsf(region, &numRects, buffer); CHECK(numRects == 1); - CHECK_THAT(buffer[0], MatchesRect({ 0.0f, 0.0f, 3.0f, 1.0f })); + CHECK_THAT(buffer[0], MatchesRectf({ 0.0f, 0.0f, 3.0f, 1.0f })); } SECTION("OR operation merges horizontal rects 2") { @@ -207,7 +210,7 @@ TEST_CASE("SRgnCombineRectf", "[region]") { SRgnGetRectsf(region, &numRects, buffer); CHECK(numRects == 1); - CHECK_THAT(buffer[0], MatchesRect({ 0.0f, 0.0f, 3.0f, 1.0f })); + CHECK_THAT(buffer[0], MatchesRectf({ 0.0f, 0.0f, 3.0f, 1.0f })); } SECTION("AND operation intersects rects") { @@ -226,7 +229,7 @@ TEST_CASE("SRgnCombineRectf", "[region]") { // │╳╳╳╳╳╳╳╳╳│ // └─────────┘ CHECK(numRects == 1); - CHECK_THAT(buffer[0], MatchesRect({ 0.5f, 0.6f, 1.0f, 1.0f })); + CHECK_THAT(buffer[0], MatchesRectf({ 0.5f, 0.6f, 1.0f, 1.0f })); } SECTION("AND operation on self does nothing") { @@ -238,7 +241,7 @@ TEST_CASE("SRgnCombineRectf", "[region]") { SRgnGetRectsf(region, &numRects, buffer); CHECK(numRects == 1); - CHECK_THAT(buffer[0], MatchesRect({ 0.0f, 0.0f, 1.0f, 1.0f })); + CHECK_THAT(buffer[0], MatchesRectf({ 0.0f, 0.0f, 1.0f, 1.0f })); } SECTION("XOR operation takes exclusive differences of rects") { @@ -257,10 +260,10 @@ TEST_CASE("SRgnCombineRectf", "[region]") { // │ 0 │ // └─────────┘ 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 })); + CHECK_THAT(buffer[0], MatchesRectf({ 0.0f, 0.0f, 1.0f, 0.6f })); + CHECK_THAT(buffer[1], MatchesRectf({ 0.0f, 0.6f, 0.5f, 1.0f })); + CHECK_THAT(buffer[2], MatchesRectf({ 1.0f, 0.6f, 1.8f, 1.0f })); + CHECK_THAT(buffer[3], MatchesRectf({ 0.5f, 1.0f, 1.8f, 1.4f })); } SECTION("XOR operation on self erases rect") { @@ -289,8 +292,8 @@ TEST_CASE("SRgnCombineRectf", "[region]") { // │ 0 │ // └─────────┘ 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 })); + CHECK_THAT(buffer[0], MatchesRectf({ 0.0f, 0.0f, 1.0f, 0.6f })); + CHECK_THAT(buffer[1], MatchesRectf({ 0.0f, 0.6f, 0.5f, 1.0f })); } SECTION("DIFF operation on self erases rect") { @@ -319,8 +322,8 @@ TEST_CASE("SRgnCombineRectf", "[region]") { // │ 0 │ // └─────────┘ 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 })); + CHECK_THAT(buffer[0], MatchesRectf({ 0.0f, 0.0f, 1.0f, 0.6f })); + CHECK_THAT(buffer[1], MatchesRectf({ 0.0f, 0.6f, 1.0f, 1.0f })); } SECTION("COPY operation splits intersecting rects 2") { @@ -339,9 +342,9 @@ TEST_CASE("SRgnCombineRectf", "[region]") { // │ 0 │ // └───────────┘ 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 })); + CHECK_THAT(buffer[0], MatchesRectf({ 0.0f, 0.0f, 1.0f, 0.4f })); + CHECK_THAT(buffer[1], MatchesRectf({ 0.0f, 0.4f, 1.0f, 0.6f })); + CHECK_THAT(buffer[2], MatchesRectf({ 0.0f, 0.6f, 1.0f, 1.0f })); } SECTION("COPY operation on self does nothing") { @@ -353,7 +356,7 @@ TEST_CASE("SRgnCombineRectf", "[region]") { SRgnGetRectsf(region, &numRects, buffer); CHECK(numRects == 1); - CHECK_THAT(buffer[0], MatchesRect({ 0.0f, 0.0f, 1.0f, 1.0f })); + CHECK_THAT(buffer[0], MatchesRectf({ 0.0f, 0.0f, 1.0f, 1.0f })); } SECTION("operation doesn't work when operating rect width or height is <= 0") { @@ -365,9 +368,6 @@ TEST_CASE("SRgnCombineRectf", "[region]") { 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; @@ -377,7 +377,7 @@ TEST_CASE("SRgnCombineRectf", "[region]") { SRgnGetRectsf(region, &numRects, buffer); CHECK(numRects == 1); - CHECK_THAT(buffer[0], MatchesRect(baseRect)); + CHECK_THAT(buffer[0], MatchesRectf(baseRect)); uint32_t numParams = 1; SRgnGetRectParamsf(region, &testRects, &numParams, nullptr); @@ -396,9 +396,6 @@ TEST_CASE("SRgnCombineRectf", "[region]") { 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; @@ -426,7 +423,380 @@ TEST_CASE("SRgnCombineRectf", "[region]") { SRgnGetRectsf(region, &numRects, buffer); CHECK(numRects == 1); - CHECK_THAT(buffer[0], MatchesRect(baseRect)); + CHECK_THAT(buffer[0], MatchesRectf(baseRect)); + } +} + +TEST_CASE("SRgnCombineRecti", "[region]") { + RgnDataTest region; + RECT baseRect = { 0, 0, 10, 10 }; + RECT testRect = { 5, 6, 18, 14 }; + + SECTION("flips top and bottom") { + // left top right bottom + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + + RECTF bounds; + SRgnGetBoundingRectf(region, &bounds); + + // left bottom right top + CHECK_THAT(bounds, MatchesRectf({ 0, 0, 10, 10 })); + } + + SECTION("does nothing with an invalid region object") { + HSRGN inval = reinterpret_cast(1234); + SRgnCombineRecti(inval, &baseRect, nullptr, SRGN_OR); + } + + SECTION("combines the region with a single given rect 1") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + + RECT boundingRect = { 0, 0, 0, 0 }; + SRgnGetBoundingRecti(region, &boundingRect); + + CHECK_THAT(boundingRect, MatchesRecti({ 0, 0, 10, 10 })); + } + + SECTION("combines the region with multiple given rects 1") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + + RECT newRect = { 0, 0, 0, 0 }; + SRgnCombineRecti(region, &newRect, nullptr, SRGN_DIFF); + + RECT boundingRect = { 0, 0, 0, 0 }; + SRgnGetBoundingRecti(region, &boundingRect); + + CHECK_THAT(boundingRect, MatchesRecti({ 0, 0, 10, 10 })); + } + + SECTION("combines the region with multiple given rects 2") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + + RECT newRect = { 0, 0, 10, 10 }; + SRgnCombineRecti(region, &newRect, nullptr, SRGN_DIFF); + + RECT boundingRect = { 0, 0, 0, 0 }; + SRgnGetBoundingRecti(region, &boundingRect); + + CHECK_THAT(boundingRect, MatchesRecti({ 0, 0, 0, 0 })); + } + + SECTION("combines the region with multiple given rects 3") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + + RECT newRect = { 0, 1, 1, 1 }; + SRgnCombineRecti(region, &newRect, nullptr, SRGN_DIFF); + + RECT boundingRect = { 0, 0, 0, 0 }; + SRgnGetBoundingRecti(region, &boundingRect); + + CHECK_THAT(boundingRect, MatchesRecti({ 0, 0, 10, 10 })); + } + + SECTION("OR operation combines rects") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &testRect, nullptr, SRGN_OR); + + uint32_t numRects = 4; + RECT buffer[4]; + SRgnGetRectsi(region, &numRects, buffer); + + // ┌────────────┐ + // | 2 | + // ┌─────┼───┐┈┈┈┈┈┈┈┈│ + // | 1 │ + // |┈┈┈┈┈└───┼────────┘ + // │ 0 │ + // └─────────┘ + CHECK(numRects == 3); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 6, 10, 0 })); + CHECK_THAT(buffer[1], MatchesRecti({ 0, 10, 18, 6 })); + CHECK_THAT(buffer[2], MatchesRecti({ 5, 14, 18, 10 })); + } + + SECTION("OR operation on self does nothing") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + + uint32_t numRects = 1; + RECT buffer[1]; + SRgnGetRectsi(region, &numRects, buffer); + + CHECK(numRects == 1); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 10, 10, 0 })); + } + + SECTION("OR operation merges vertical rects") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + RECT rect2 = { 0, 20, 10, 30 }; + RECT rect3 = { 0, 10, 10, 20 }; + SRgnCombineRecti(region, &rect3, nullptr, SRGN_OR); + SRgnCombineRecti(region, &rect2, nullptr, SRGN_OR); + + uint32_t numRects = 1; + RECT buffer[1]; + SRgnGetRectsi(region, &numRects, buffer); + + CHECK(numRects == 1); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 30, 10, 0 })); + } + + SECTION("OR operation merges vertical rects 2") { + RECT rect2 = { 0, 20, 10, 30 }; + RECT rect3 = { 0, 10, 10, 20 }; + SRgnCombineRecti(region, &rect3, nullptr, SRGN_OR); + SRgnCombineRecti(region, &rect2, nullptr, SRGN_OR); + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + + uint32_t numRects = 1; + RECT buffer[1]; + SRgnGetRectsi(region, &numRects, buffer); + + CHECK(numRects == 1); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 30, 10, 0 })); + } + + SECTION("OR operation merges horizontal rects") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + RECT rect2 = { 20, 0, 30, 10 }; + RECT rect3 = { 10, 0, 20, 10 }; + SRgnCombineRecti(region, &rect3, nullptr, SRGN_OR); + SRgnCombineRecti(region, &rect2, nullptr, SRGN_OR); + + uint32_t numRects = 1; + RECT buffer[1]; + SRgnGetRectsi(region, &numRects, buffer); + + CHECK(numRects == 1); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 10, 30, 0 })); + } + + SECTION("OR operation merges horizontal rects 2") { + RECT rect2 = { 20, 0, 30, 10 }; + RECT rect3 = { 10, 0, 20, 10 }; + SRgnCombineRecti(region, &rect3, nullptr, SRGN_OR); + SRgnCombineRecti(region, &rect2, nullptr, SRGN_OR); + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + + uint32_t numRects = 1; + RECT buffer[1]; + SRgnGetRectsi(region, &numRects, buffer); + + CHECK(numRects == 1); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 10, 30, 0 })); + } + + SECTION("AND operation intersects rects") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &testRect, nullptr, SRGN_AND); + + uint32_t numRects = 2; + RECT buffer[2]; + SRgnGetRectsi(region, &numRects, buffer); + + // ┌────────────┐ + // |╳╳╳╳╳╳╳╳╳╳╳╳| + // ┌─────┼───┐╳╳╳╳╳╳╳╳│ + // |╳╳╳╳╳| 0 |╳╳╳╳╳╳╳╳│ + // |╳╳╳╳╳└───┼────────┘ + // │╳╳╳╳╳╳╳╳╳│ + // └─────────┘ + CHECK(numRects == 1); + CHECK_THAT(buffer[0], MatchesRecti({ 5, 10, 10, 6 })); + } + + SECTION("AND operation on self does nothing") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_AND); + + uint32_t numRects = 1; + RECT buffer[1]; + SRgnGetRectsi(region, &numRects, buffer); + + CHECK(numRects == 1); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 10, 10, 0 })); + } + + SECTION("XOR operation takes exclusive differences of rects") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &testRect, nullptr, SRGN_XOR); + + uint32_t numRects = 5; + RECT buffer[5]; + SRgnGetRectsi(region, &numRects, buffer); + + // ┌────────────┐ + // | 3 | + // ┌─────┼───┐┈┈┈┈┈┈┈┈│ + // | 1 |╳╳╳| 2 │ + // |┈┈┈┈┈└───┼────────┘ + // │ 0 │ + // └─────────┘ + CHECK(numRects == 4); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 6, 10, 0 })); + CHECK_THAT(buffer[1], MatchesRecti({ 0, 10, 5, 6 })); + CHECK_THAT(buffer[2], MatchesRecti({ 10, 10, 18, 6 })); + CHECK_THAT(buffer[3], MatchesRecti({ 5, 14, 18, 10 })); + } + + SECTION("XOR operation on self erases rect") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_XOR); + + uint32_t numRects = 1; + SRgnGetRectsi(region, &numRects, nullptr); + + CHECK(numRects == 0); + } + + SECTION("DIFF operation removes parts of rects") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &testRect, nullptr, SRGN_DIFF); + + uint32_t numRects = 5; + RECT buffer[5]; + SRgnGetRectsi(region, &numRects, buffer); + + // ┌────────────┐ + // |╳╳╳╳╳╳╳╳╳╳╳╳| + // ┌─────┼╳╳╳╳╳╳╳╳╳╳╳╳│ + // | 1 |╳╳╳╳╳╳╳╳╳╳╳╳│ + // |┈┈┈┈┈└───┼────────┘ + // │ 0 │ + // └─────────┘ + CHECK(numRects == 2); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 6, 10, 0 })); + CHECK_THAT(buffer[1], MatchesRecti({ 0, 10, 5, 6 })); + } + + SECTION("DIFF operation on self erases rect") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_DIFF); + + uint32_t numRects = 1; + SRgnGetRectsi(region, &numRects, nullptr); + + CHECK(numRects == 0); + } + + SECTION("COPY operation splits intersecting rects") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &testRect, nullptr, SRGN_COPY); + + uint32_t numRects = 5; + RECT buffer[5]; + SRgnGetRectsi(region, &numRects, buffer); + + // ┌────────────┐ + // |╳╳╳╳╳╳╳╳╳╳╳╳| + // ┌─────┼───┐╳╳╳╳╳╳╳╳│ + // | 1 │╳╳╳╳╳╳╳╳│ + // |┈┈┈┈┈└───┼────────┘ + // │ 0 │ + // └─────────┘ + CHECK(numRects == 2); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 6, 10, 0 })); + CHECK_THAT(buffer[1], MatchesRecti({ 0, 10, 10, 6 })); + } + + SECTION("COPY operation splits intersecting rects 2") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + RECT testRect2 = { 4, 4, 6, 6 }; + SRgnCombineRecti(region, &testRect2, nullptr, SRGN_COPY); + + uint32_t numRects = 5; + RECT buffer[5]; + SRgnGetRectsi(region, &numRects, buffer); + + // ┌───────────┐ + // │ 2 │ + // │┈┈┈┈┌┈┐┈┈┈┈│ + // │┈┈┈┈└┈┘┈┈1┈│ + // │ 0 │ + // └───────────┘ + CHECK(numRects == 3); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 4, 10, 0 })); + CHECK_THAT(buffer[1], MatchesRecti({ 0, 6, 10, 4 })); + CHECK_THAT(buffer[2], MatchesRecti({ 0, 10, 10, 6 })); + } + + SECTION("COPY operation on self does nothing") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_COPY); + + uint32_t numRects = 1; + RECT buffer[1]; + SRgnGetRectsi(region, &numRects, buffer); + + CHECK(numRects == 1); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 10, 10, 0 })); + } + + 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); + RECT testRects = GENERATE( + RECT{ 5, 0, 5, 10 }, + RECT{ 0, 5, 10, 5 }, + RECT{ 10, 0, 5, 10 }, + RECT{ 0, 10, 10, 5 } + ); + + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + + uint32_t numRects = 5; + RECT buffer[5]; + + SRgnCombineRecti(region, &testRects, &testRects, combineMode); + + SRgnGetRectsi(region, &numRects, buffer); + CHECK(numRects == 1); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 10, 10, 0 })); + + uint32_t numParams = 1; + SRgnGetRectParamsi(region, &testRects, &numParams, nullptr); + CHECK(numParams == 0); + + SRgnGetRectParamsi(region, &baseRect, &numParams, nullptr); + CHECK(numParams == 1); + } + + SECTION("AND kills all rects when rect width or height is <= 0") { + int32_t combineMode = SRGN_AND; + RECT testRects = GENERATE( + RECT{ 5, 0, 5, 10 }, + RECT{ 0, 5, 10, 5 }, + RECT{ 10, 0, 5, 10 }, + RECT{ 0, 10, 10, 5 } + ); + + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + + uint32_t numRects = 5; + RECT buffer[5]; + + SRgnCombineRecti(region, &testRects, &testRects, combineMode); + + SRgnGetRectsi(region, &numRects, buffer); + CHECK(numRects == 0); + + uint32_t numParams = 1; + SRgnGetRectParamsi(region, &testRects, &numParams, nullptr); + CHECK(numParams == 0); + + SRgnGetRectParamsi(region, &baseRect, &numParams, nullptr); + CHECK(numParams == 0); + } + + SECTION("PARAM operation doesn't influence rects") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &testRect, nullptr, SRGN_PARAMONLY); + + uint32_t numRects = 5; + RECT buffer[5]; + SRgnGetRectsi(region, &numRects, buffer); + + CHECK(numRects == 1); + CHECK_THAT(buffer[0], MatchesRecti({ 0, 10, 10, 0 })); } } @@ -468,7 +838,7 @@ TEST_CASE("SRgnDuplicate", "[region]") { SRgnGetRectsf(newrgn, &numrects, buffer); REQUIRE(numrects == 1); - CHECK_THAT(buffer[0], MatchesRect(baseRect)); + CHECK_THAT(buffer[0], MatchesRectf(baseRect)); } SECTION("copies params") { @@ -513,21 +883,21 @@ TEST_CASE("SRgnGetBoundingRectf", "[region]") { result = { 123, 456, 666, 9 }; SRgnGetBoundingRectf(region, &result); - CHECK_THAT(result, MatchesRect({ 0, 0, 0, 0 })); + CHECK_THAT(result, MatchesRectf({ 0, 0, 0, 0 })); } SECTION("returns invalid rect when using an invalid region object") { HSRGN inval = reinterpret_cast(1234); SRgnGetBoundingRectf(inval, &result); - CHECK_THAT(result, MatchesRect({ FLT_MAX, FLT_MAX, FLT_MIN, FLT_MIN })); + CHECK_THAT(result, MatchesRectf({ FLT_MAX, FLT_MAX, FLT_MIN, FLT_MIN })); } SECTION("returns the dimensions of 1 rect") { SRgnCombineRectf(region, &baseRect, nullptr, SRGN_OR); SRgnGetBoundingRectf(region, &result); - CHECK_THAT(result, MatchesRect({ 0.0f, 0.0f, 1.0f, 1.0f })); + CHECK_THAT(result, MatchesRectf({ 0.0f, 0.0f, 1.0f, 1.0f })); } SECTION("returns the combined dimensions of multiple rects") { @@ -537,12 +907,12 @@ TEST_CASE("SRgnGetBoundingRectf", "[region]") { SRgnCombineRectf(region, &testRect, nullptr, SRGN_OR); SRgnGetBoundingRectf(region, &result); - CHECK_THAT(result, MatchesRect({ 0.0f, 0.0f, 1.5f, 1.5f })); + CHECK_THAT(result, MatchesRectf({ 0.0f, 0.0f, 1.5f, 1.5f })); SRgnCombineRectf(region, &testRect2, nullptr, SRGN_OR); SRgnGetBoundingRectf(region, &result); - CHECK_THAT(result, MatchesRect({ -0.5f, -0.5f, 1.5f, 1.5f })); + CHECK_THAT(result, MatchesRectf({ -0.5f, -0.5f, 1.5f, 1.5f })); } SECTION("excludes param rects") { @@ -552,7 +922,73 @@ TEST_CASE("SRgnGetBoundingRectf", "[region]") { SRgnCombineRectf(region, &testRect, ¶m1, SRGN_PARAMONLY); SRgnGetBoundingRectf(region, &result); - CHECK_THAT(result, MatchesRect({ 0.0f, 0.0f, 1.0f, 1.0f })); + CHECK_THAT(result, MatchesRectf({ 0.0f, 0.0f, 1.0f, 1.0f })); + } +} + +TEST_CASE("SRgnGetBoundingRecti", "[region]") { + RgnDataTest region; + RECT result = {}; + RECT baseRect = { 0, 0, 10, 10 }; + + SECTION("flips top and bottom") { + // left bottom right top + RECTF testRect = { 0.0f, 0.0f, 10.0f, 10.0f }; + SRgnCombineRectf(region, &testRect, nullptr, SRGN_OR); + + SRgnGetBoundingRecti(region, &result); + // left top right bottom + CHECK_THAT(result, MatchesRecti({ 0, 0, 10, 10 })); + } + + SECTION("returns null rect when there are no rects") { + // make the result dirty so we test it being set + result = { 123, 456, 666, 9 }; + + SRgnGetBoundingRecti(region, &result); + CHECK_THAT(result, MatchesRecti({ 0, 0, 0, 0 })); + } + + SECTION("returns invalid rect when using an invalid region object") { + HSRGN inval = reinterpret_cast(1234); + + SRgnGetBoundingRecti(inval, &result); + // Casting huge float values to int is inconsistent between compilers + // It is also inconsistent between compile and runtime with the same compiler + // So don't even bother comparing with a value + CHECK_THAT(result, !MatchesRecti({ 0, 0, 0, 0 })); + } + + SECTION("returns the dimensions of 1 rect") { + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + + SRgnGetBoundingRecti(region, &result); + CHECK_THAT(result, MatchesRecti({ 0, 0, 10, 10 })); + } + + SECTION("returns the combined dimensions of multiple rects") { + RECT testRect = { 5, 5, 15, 15 }; + RECT testRect2 = { -5, -5, 5, 5 }; + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &testRect, nullptr, SRGN_OR); + + SRgnGetBoundingRecti(region, &result); + CHECK_THAT(result, MatchesRecti({ 0, 0, 15, 15 })); + + SRgnCombineRecti(region, &testRect2, nullptr, SRGN_OR); + + SRgnGetBoundingRecti(region, &result); + CHECK_THAT(result, MatchesRecti({ -5, -5, 15, 15 })); + } + + SECTION("excludes param rects") { + RECT testRect = { 5, 5, 15, 15 }; + int param1 = 0; + SRgnCombineRecti(region, &baseRect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &testRect, ¶m1, SRGN_PARAMONLY); + + SRgnGetBoundingRecti(region, &result); + CHECK_THAT(result, MatchesRecti({ 0, 0, 10, 10 })); } } @@ -669,6 +1105,119 @@ TEST_CASE("SRgnGetRectParamsf", "[region]") { } } +TEST_CASE("SRgnGetRectParamsi", "[region]") { + RgnDataTest region; + + SECTION("retrieves empty list if nothing put in") { + uint32_t numParams = 1; + void* buffer[1]; + + RECT rect = { 0, 0, 1, 1 }; + SRgnGetRectParamsi(region, &rect, &numParams, buffer); + + CHECK(numParams == 0); + } + + SECTION("retrieves 0 when using an invalid region object") { + HSRGN inval = reinterpret_cast(1234); + + uint32_t numParams = 1; + RECT rect = { 0, 0, 1, 1 }; + SRgnGetRectParamsi(inval, &rect, &numParams, nullptr); + + CHECK(numParams == 0); + } + + SECTION("retrieves only overlapping params") { + RECT rect1 = { 0, 0, 10, 10 }; + RECT rect2 = { 20, 20, 40, 40 }; + RECT rect3 = { 25, 25, 50, 50 }; + RECT queryRect = { 30, 30, 50, 50 }; + int param1 = 5; + int param2 = 10; + int param3 = 11; + + SRgnCombineRecti(region, &rect1, ¶m1, SRGN_PARAMONLY); + SRgnCombineRecti(region, &rect2, ¶m2, SRGN_PARAMONLY); + SRgnCombineRecti(region, &rect3, ¶m3, SRGN_PARAMONLY); + + uint32_t numParams = 0; + SRgnGetRectParamsi(region, &queryRect, &numParams, nullptr); + REQUIRE(numParams == 2); + + void* buffer[2]; + SRgnGetRectParamsi(region, &queryRect, &numParams, buffer); + CHECK(numParams == 2); + CHECK(buffer[0] == ¶m2); + CHECK(buffer[1] == ¶m3); + } + + SECTION("can have multiple params for same rect") { + RECT rect1 = { 0, 0, 1, 1 }; + RECT queryRect = { 0, 0, 1, 1 }; + int param1 = 5; + int param2 = 8; + + SRgnCombineRecti(region, &rect1, ¶m1, SRGN_PARAMONLY); + SRgnCombineRecti(region, &rect1, ¶m2, SRGN_PARAMONLY); + + uint32_t numParams = 0; + SRgnGetRectParamsi(region, &queryRect, &numParams, nullptr); + REQUIRE(numParams == 2); + + void* buffer[2]; + SRgnGetRectParamsi(region, &queryRect, &numParams, buffer); + CHECK(numParams == 2); + CHECK(buffer[0] == ¶m1); + CHECK(buffer[1] == ¶m2); + } + + SECTION("can duplicate the same param") { + RECT rect1 = { 0, 0, 1, 1 }; + RECT queryRect = { 0, 0, 1, 1 }; + int param1 = 5; + + SRgnCombineRecti(region, &rect1, ¶m1, SRGN_PARAMONLY); + SRgnCombineRecti(region, &rect1, ¶m1, SRGN_PARAMONLY); + + uint32_t numParams = 0; + SRgnGetRectParamsi(region, &queryRect, &numParams, nullptr); + REQUIRE(numParams == 2); + + void* buffer[2]; + SRgnGetRectParamsi(region, &queryRect, &numParams, buffer); + CHECK(numParams == 2); + CHECK(buffer[0] == ¶m1); + CHECK(buffer[1] == ¶m1); + } + + SECTION("orders params by sequence") { + RECT rect1 = { 0, 0, 1, 1 }; + RECT queryRect = { 0, 0, 1, 1 }; + int param1 = 11; + int param2 = 10; + int param3 = 5; + int param4 = 55; + + SRgnCombineRecti(region, &rect1, ¶m1, SRGN_PARAMONLY); + SRgnCombineRecti(region, &rect1, ¶m2, SRGN_PARAMONLY); + SRgnCombineRecti(region, &rect1, ¶m3, SRGN_PARAMONLY); + SRgnCombineRecti(region, &rect1, ¶m4, SRGN_PARAMONLY); + + uint32_t numParams = 0; + SRgnGetRectParamsi(region, &queryRect, &numParams, nullptr); + REQUIRE(numParams == 4); + + void* buffer[4]; + SRgnGetRectParamsi(region, &queryRect, &numParams, buffer); + CHECK(numParams == 4); + CHECK(buffer[0] == ¶m1); + CHECK(buffer[1] == ¶m2); + CHECK(buffer[2] == ¶m3); + CHECK(buffer[3] == ¶m4); + } +} + TEST_CASE("SRgnGetRectsf", "[region]") { RgnDataTest region; @@ -703,8 +1252,8 @@ TEST_CASE("SRgnGetRectsf", "[region]") { REQUIRE(numrects == 2); SRgnGetRectsf(region, &numrects, buffer); - CHECK_THAT(buffer[0], MatchesRect(rct2)); - CHECK_THAT(buffer[1], MatchesRect(rct1)); + CHECK_THAT(buffer[0], MatchesRectf(rct2)); + CHECK_THAT(buffer[1], MatchesRectf(rct1)); } SECTION("automatically merges overlapping rects") { @@ -720,7 +1269,7 @@ TEST_CASE("SRgnGetRectsf", "[region]") { REQUIRE(numrects == 1); SRgnGetRectsf(region, &numrects, buffer); - CHECK_THAT(buffer[0], MatchesRect(rct1)); + CHECK_THAT(buffer[0], MatchesRectf(rct1)); } SECTION("retrieves rects in order of top value") { @@ -740,10 +1289,10 @@ TEST_CASE("SRgnGetRectsf", "[region]") { 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])); + CHECK_THAT(buffer[0], MatchesRectf(rects[3])); + CHECK_THAT(buffer[1], MatchesRectf(rects[0])); + CHECK_THAT(buffer[2], MatchesRectf(rects[2])); + CHECK_THAT(buffer[3], MatchesRectf(rects[1])); } SECTION("retrieves rects in order of left value when tops are equal") { @@ -763,10 +1312,111 @@ TEST_CASE("SRgnGetRectsf", "[region]") { 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])); + CHECK_THAT(buffer[0], MatchesRectf(rects[2])); + CHECK_THAT(buffer[1], MatchesRectf(rects[0])); + CHECK_THAT(buffer[2], MatchesRectf(rects[3])); + CHECK_THAT(buffer[3], MatchesRectf(rects[1])); + } +} + +TEST_CASE("SRgnGetRectsi", "[region]") { + RgnDataTest region; + + SECTION("retrieves empty list if nothing put in") { + uint32_t numrects = 1; + RECT buffer[1]; + + SRgnGetRectsi(region, &numrects, buffer); + + CHECK(numrects == 0); + } + + SECTION("retrieves 0 when using an invalid region object") { + HSRGN inval = reinterpret_cast(1234); + + uint32_t numrects = 1; + SRgnGetRectsi(inval, &numrects, nullptr); + + CHECK(numrects == 0); + } + + SECTION("retrieves all rects that were put in") { + RECTF rct1 = { 5.0f, 5.0f, 10.0f, 10.0f }; + RECTF rct2 = { 0.0f, 0.0f, 1.0f, 1.0f }; + SRgnCombineRectf(region, &rct1, nullptr, SRGN_OR); + SRgnCombineRectf(region, &rct2, nullptr, SRGN_OR); + + uint32_t numrects = 0; + RECT buffer[2]; + SRgnGetRectsi(region, &numrects, nullptr); + + REQUIRE(numrects == 2); + SRgnGetRectsi(region, &numrects, buffer); + + CHECK_THAT(buffer[0], MatchesRecti({ 0, 1, 1, 0 })); + CHECK_THAT(buffer[1], MatchesRecti({ 5, 10, 10, 5 })); + } + + SECTION("automatically merges overlapping rects") { + RECTF rct1 = { -10.0f, -10.0f, 10.0f, 10.0f }; + RECTF rct2 = { 0.0f, 0.0f, 1.0f, 1.0f }; + SRgnCombineRectf(region, &rct1, nullptr, SRGN_OR); + SRgnCombineRectf(region, &rct2, nullptr, SRGN_OR); + + uint32_t numrects = 0; + RECT buffer[1]; + SRgnGetRectsi(region, &numrects, nullptr); + + REQUIRE(numrects == 1); + SRgnGetRectsi(region, &numrects, buffer); + + CHECK_THAT(buffer[0], MatchesRecti({ -10, 10, 10, -10 })); + } + + SECTION("retrieves rects in order of top value") { + RECTF rects[4] = { + { 0.0f, 0.0f, 1.0f, 1.0f }, + { 4.0f, -2.0f, 5.0f, 5.0f }, + { -2.0f, 2.0f, -1.0f, 3.0f }, + { 2.0f, -2.0f, 3.0f, -1.0f }, + }; + + for (int i = 0; i < 4; i++) { + SRgnCombineRectf(region, &rects[i], nullptr, SRGN_OR); + } + + uint32_t numrects = 5; + RECT buffer[5]; + SRgnGetRectsi(region, &numrects, buffer); + + CHECK(numrects == 4); + CHECK_THAT(buffer[0], MatchesRecti({ 2, -1, 3, -2 })); + CHECK_THAT(buffer[1], MatchesRecti({ 0, 1, 1, 0 })); + CHECK_THAT(buffer[2], MatchesRecti({ -2, 3, -1, 2 })); + CHECK_THAT(buffer[3], MatchesRecti({ 4, 5, 5, -2 })); + } + + SECTION("retrieves rects in order of left value when tops are equal") { + RECTF rects[4] = { + { 0, 0, 10, 10 }, + { 40, 5, 50, 10 }, + { -20, -20, -10, 10 }, + { 20, -10, 30, 10 }, + }; + + for (int i = 0; i < 4; i++) { + SRgnCombineRectf(region, &rects[i], nullptr, SRGN_OR); + } + + uint32_t numrects = 5; + RECT buffer[5]; + SRgnGetRectsi(region, &numrects, buffer); + + CHECK(numrects == 4); + CHECK_THAT(buffer[0], MatchesRecti({ -20, 10, -10, -20 })); + CHECK_THAT(buffer[1], MatchesRecti({ 0, 10, 10, 0 })); + CHECK_THAT(buffer[2], MatchesRecti({ 20, 10, 30, -10 })); + CHECK_THAT(buffer[3], MatchesRecti({ 40, 10, 50, 5 })); } } @@ -822,6 +1472,58 @@ TEST_CASE("SRgnIsPointInRegionf", "[region]") { } } +TEST_CASE("SRgnIsPointInRegioni", "[region]") { + RgnDataTest region; + + SECTION("false if region has no rects") { + auto x = GENERATE(0, 1, INT32_MAX); + auto y = GENERATE(0, -1, INT32_MIN); + + CHECK_FALSE(SRgnIsPointInRegioni(region, x, y)); + } + + SECTION("false if using an invalid region object") { + HSRGN inval = reinterpret_cast(1234); + + CHECK_FALSE(SRgnIsPointInRegioni(inval, 0, 0)); + } + + SECTION("reports if points are inside a region") { + RECT rct1 = { -10, -10, -1, -1 }; + RECT rct2 = { 1, 1, 5, 5 }; + SRgnCombineRecti(region, &rct1, nullptr, SRGN_OR); + SRgnCombineRecti(region, &rct2, nullptr, SRGN_OR); + + CHECK_FALSE(SRgnIsPointInRegioni(region, 0, 0)); + CHECK_FALSE(SRgnIsPointInRegioni(region, -11, -5)); + CHECK_FALSE(SRgnIsPointInRegioni(region, 4, 6)); + CHECK_FALSE(SRgnIsPointInRegioni(region, 1, 0)); + CHECK_FALSE(SRgnIsPointInRegioni(region, 0, -1)); + CHECK(SRgnIsPointInRegioni(region, -10, -10)); + CHECK(SRgnIsPointInRegioni(region, -2, -2)); + CHECK(SRgnIsPointInRegioni(region, 3, 2)); + } + + SECTION("ignores param only rects") { + RECT rect = { -1, -1, 1, 1 }; + SRgnCombineRecti(region, &rect, &rect, SRGN_PARAMONLY); + + CHECK_FALSE(SRgnIsPointInRegioni(region, 0, 0)); + } + + SECTION("excludes upper bounds of rect") { + RECT rect = { 10, 10, 50, 50 }; + SRgnCombineRecti(region, &rect, nullptr, SRGN_OR); + + CHECK_FALSE(SRgnIsPointInRegioni(region, 50, 50)); + CHECK_FALSE(SRgnIsPointInRegioni(region, 50, 40)); + CHECK_FALSE(SRgnIsPointInRegioni(region, 40, 50)); + CHECK(SRgnIsPointInRegioni(region, 49, 49)); + CHECK(SRgnIsPointInRegioni(region, 10, 10)); + CHECK_FALSE(SRgnIsPointInRegioni(region, 9, 10)); + } +} + TEST_CASE("SRgnIsRectInRegionf", "[region]") { RgnDataTest region; RECTF rect = { 0.0f, 0.0f, 1.0f, 1.0f }; @@ -898,6 +1600,91 @@ TEST_CASE("SRgnIsRectInRegionf", "[region]") { } } +TEST_CASE("SRgnIsRectInRegioni", "[region]") { + RgnDataTest region; + RECT rect = { 0, 0, 10, 10 }; + + SECTION("flips top and bottom") { + RECTF bound = { 5.0f, 5.0f, 15.0f, 15.0f }; // left bottom right top + SRgnCombineRectf(region, &bound, nullptr, SRGN_OR); + + // True if left/top/right/bottom were consistent + RECT cmp = { 0, 10, 10, 0 }; // left top right bottom + CHECK_FALSE(SRgnIsRectInRegioni(region, &cmp)); + } + + SECTION("false if region has no rects") { + CHECK_FALSE(SRgnIsRectInRegioni(region, &rect)); + } + + SECTION("false if using an invalid region object") { + HSRGN inval = reinterpret_cast(1234); + CHECK_FALSE(SRgnIsRectInRegioni(inval, &rect)); + } + + SECTION("true if rects overlap a region") { + RECT checkRects[] = { + { 5, 5, 5, 5 }, + { 0, 0, 10, 10 }, + { 110, 110, 120, 190 }, + { 140, 140, 200, 200 }, + { -1000, -1000, 1, 1 }, + }; + + RECT secondRect = { 100, 100, 150, 200 }; + SRgnCombineRecti(region, &rect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &secondRect, nullptr, SRGN_OR); + + CHECK(SRgnIsRectInRegioni(region, &checkRects[0])); + CHECK(SRgnIsRectInRegioni(region, &checkRects[1])); + CHECK(SRgnIsRectInRegioni(region, &checkRects[2])); + CHECK(SRgnIsRectInRegioni(region, &checkRects[3])); + CHECK(SRgnIsRectInRegioni(region, &checkRects[4])); + } + + SECTION("false if rects are outside a region") { + RECT checkRects[] = { + { 20, 20, 20, 20 }, + { 10, 10, 20, 20 }, + { -10, -10, 0, 0 }, + { 90, 90, 100, 100 } + }; + + RECT secondRect = { 100, 100, 150, 200 }; + SRgnCombineRecti(region, &rect, nullptr, SRGN_OR); + SRgnCombineRecti(region, &secondRect, nullptr, SRGN_OR); + + CHECK_FALSE(SRgnIsRectInRegioni(region, &checkRects[0])); + CHECK_FALSE(SRgnIsRectInRegioni(region, &checkRects[1])); + CHECK_FALSE(SRgnIsRectInRegioni(region, &checkRects[2])); + CHECK_FALSE(SRgnIsRectInRegioni(region, &checkRects[3])); + } + + SECTION("ignores param only rects") { + SRgnCombineRecti(region, &rect, &rect, SRGN_PARAMONLY); + CHECK_FALSE(SRgnIsRectInRegioni(region, &rect)); + } + + SECTION("excludes matching on rect bounds") { + RECT checkRects[] = { + { -1, -1, 0, 10 }, + { -1, -1, 10, 0 }, + { 10, 10, 10, 10 }, + { 0, 0, 0, 0 }, + { 5, 0, 5, 0 }, + { 0, 5, 0, 5 }, + }; + SRgnCombineRecti(region, &rect, nullptr, SRGN_OR); + + CHECK_FALSE(SRgnIsRectInRegioni(region, &checkRects[0])); + CHECK_FALSE(SRgnIsRectInRegioni(region, &checkRects[1])); + CHECK_FALSE(SRgnIsRectInRegioni(region, &checkRects[2])); + CHECK_FALSE(SRgnIsRectInRegioni(region, &checkRects[3])); + CHECK_FALSE(SRgnIsRectInRegioni(region, &checkRects[4])); + CHECK_FALSE(SRgnIsRectInRegioni(region, &checkRects[5])); + } +} + TEST_CASE("SRgnOffsetf", "[region]") { RgnDataTest region; @@ -922,8 +1709,8 @@ TEST_CASE("SRgnOffsetf", "[region]") { RECTF buffer[2]; SRgnGetRectsf(region, &numRects, buffer); - CHECK_THAT(buffer[0], MatchesRect({ -100.0f, -150.0f, 0.0f, -50.0f })); - CHECK_THAT(buffer[1], MatchesRect({ 100.0f, 50.0f, 200.0f, 150.0f })); + CHECK_THAT(buffer[0], MatchesRectf({ -100.0f, -150.0f, 0.0f, -50.0f })); + CHECK_THAT(buffer[1], MatchesRectf({ 100.0f, 50.0f, 200.0f, 150.0f })); } SECTION("shifts rects back to their original positions with opposite amounts") { @@ -943,8 +1730,8 @@ TEST_CASE("SRgnOffsetf", "[region]") { RECTF buffer[2]; SRgnGetRectsf(region, &numRects, buffer); - CHECK_THAT(buffer[0], MatchesRect(rects[0])); - CHECK_THAT(buffer[1], MatchesRect(rects[1])); + CHECK_THAT(buffer[0], MatchesRectf(rects[0])); + CHECK_THAT(buffer[1], MatchesRectf(rects[1])); } SECTION("doesn't shift anything with 0") { @@ -962,7 +1749,76 @@ TEST_CASE("SRgnOffsetf", "[region]") { RECTF buffer[2]; SRgnGetRectsf(region, &numRects, buffer); - CHECK_THAT(buffer[0], MatchesRect(rects[0])); - CHECK_THAT(buffer[1], MatchesRect(rects[1])); + CHECK_THAT(buffer[0], MatchesRectf(rects[0])); + CHECK_THAT(buffer[1], MatchesRectf(rects[1])); + } +} + +TEST_CASE("SRgnOffseti", "[region]") { + RgnDataTest region; + + SECTION("does nothing when using an invalid region object") { + // doesn't crash + HSRGN inval = reinterpret_cast(1234); + SRgnOffseti(inval, 100, 100); + } + + SECTION("shifts rects by given amount") { + RECTF rects[] = { + { -2000.0f, -2000.0f, -1000.0f, -1000.0f }, + { 0.0f, 0.0f, 1000.0f, 1000.0f } + }; + + SRgnCombineRectf(region, &rects[0], nullptr, SRGN_OR); + SRgnCombineRectf(region, &rects[1], nullptr, SRGN_OR); + + SRgnOffseti(region, 1000, 500); + + uint32_t numRects = 2; + RECT buffer[2]; + SRgnGetRectsi(region, &numRects, buffer); + + CHECK_THAT(buffer[0], MatchesRecti({ -1000, -500, 0, -1500 })); + CHECK_THAT(buffer[1], MatchesRecti({ 1000, 1500, 2000, 500 })); + } + + SECTION("shifts rects back to their original positions with opposite amounts") { + RECTF rects[] = { + { -2000.0f, -2000.0f, -1000.0f, -1000.0f }, + { 0.0f, 0.0f, 1000.0f, 1000.0f } + }; + + SRgnCombineRectf(region, &rects[0], nullptr, SRGN_OR); + SRgnCombineRectf(region, &rects[1], nullptr, SRGN_OR); + + SRgnOffseti(region, 1000, 500); + SRgnOffseti(region, 50, 100); + SRgnOffseti(region, -1050, -600); + + uint32_t numRects = 2; + RECT buffer[2]; + SRgnGetRectsi(region, &numRects, buffer); + + CHECK_THAT(buffer[0], MatchesRecti({ -2000, -1000, -1000, -2000 })); + CHECK_THAT(buffer[1], MatchesRecti({ 0, 1000, 1000, 0 })); + } + + SECTION("doesn't shift anything with 0") { + RECTF rects[] = { + { -2000.0f, -2000.0f, -1000.0f, -1000.0f }, + { 0.0f, 0.0f, 1000.0f, 1000.0f } + }; + + SRgnCombineRectf(region, &rects[0], nullptr, SRGN_OR); + SRgnCombineRectf(region, &rects[1], nullptr, SRGN_OR); + + SRgnOffseti(region, 0, 0); + + uint32_t numRects = 2; + RECT buffer[2]; + SRgnGetRectsi(region, &numRects, buffer); + + CHECK_THAT(buffer[0], MatchesRecti({ -2000, -1000, -1000, -2000 })); + CHECK_THAT(buffer[1], MatchesRecti({ 0, 1000, 1000, 0 })); } } diff --git a/test/RegionTest.hpp b/test/RegionTest.hpp index 5a29152..f7f2b8a 100644 --- a/test/RegionTest.hpp +++ b/test/RegionTest.hpp @@ -26,6 +26,12 @@ std::ostream& operator <<(std::ostream& os, RECTF const& value) { return os; } +// Helpers for comparing RECTF structs +std::ostream& operator <<(std::ostream& os, RECT const& value) { + os << "{ " << value.left << ", " << value.top << ", " << value.right << ", " << value.bottom << " }"; + return os; +} + template class RECTMatcher : public Catch::MatcherBase { private: @@ -45,6 +51,10 @@ class RECTMatcher : public Catch::MatcherBase { } }; -RECTMatcher MatchesRect(RECTF arg) { +RECTMatcher MatchesRectf(RECTF arg) { + return { arg }; +} + +RECTMatcher MatchesRecti(RECT arg) { return { arg }; }