diff --git a/storm/Region.cpp b/storm/Region.cpp new file mode 100644 index 0000000..d25b387 --- /dev/null +++ b/storm/Region.cpp @@ -0,0 +1,238 @@ +#include "storm/Region.hpp" +#include "storm/region/RGN.hpp" +#include "storm/Error.hpp" +#include "storm/Hash.hpp" +#include "storm/Thread.hpp" +#include + +static TSExportTableSyncReuse s_rgntable; + +void AddSourceRect(TSGrowableArray* sourceArray, const RECTF* rect, void* param, int32_t sequence, uint32_t flags) { + auto source = sourceArray->New(); + + source->rect = *rect; + source->param = param; + source->sequence = sequence; + source->flags = flags; +} + +int32_t CheckForIntersection(const RECTF* sourceRect, const RECTF* targetRect) { + return sourceRect->left < targetRect->right + && sourceRect->bottom < targetRect->top + && sourceRect->right > targetRect->left + && sourceRect->top > targetRect->bottom; +} + +int32_t CompareRects(const RECTF* rect1, const RECTF* rect2) { + return rect1->left == rect2->left + && rect1->bottom == rect2->bottom + && rect1->right == rect2->right + && rect1->top == rect2->top; +} + +void DeleteRect(RECTF* rect) { + rect->left = std::numeric_limits::max(); + rect->bottom = std::numeric_limits::max(); + rect->right = std::numeric_limits::max(); + rect->top = std::numeric_limits::max(); +} + +void FragmentSourceRectangles(TSGrowableArray* 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, 0x1 | (previousOverlap ? 0x2 : 0x0)); + return; + } + + auto overlapsExisting = previousOverlap; + + for (uint32_t i = firstIndex; i < lastIndex; i++) { + auto source = &(*sourceArray)[i]; + + if (CheckForIntersection(rect, &source->rect)) { + if (!CompareRects(rect, &source->rect)) { + break; + } + + source->flags |= 0x2; + overlapsExisting = 1; + } + + if (i + 1 == lastIndex) { + AddSourceRect(sourceArray, rect, param, sequence, 0x1 | (previousOverlap ? 0x2 : 0x0)); + return; + } + } + + // TODO +} + +int32_t IsNullRect(RECTF* rect) { + return rect->left >= rect->right || rect->bottom >= rect->top; +} + +void ClearRegion(RGN* rgn) { + rgn->source.SetCount(0); + rgn->combined.SetCount(0); + rgn->foundparams.SetCount(0); + + DeleteRect(&rgn->foundparamsrect); + + rgn->sequence = 0; + rgn->dirty = 0; +} + +void InvalidateRegion(RGN* rgn) { + rgn->dirty = 1; + DeleteRect(&rgn->foundparamsrect); +} + +void DeleteSourceRect(TSGrowableArray* sourceArray, uint32_t index) { + auto source = &(*sourceArray)[index]; + + DeleteRect(&source->rect); + source->param = nullptr; + source->sequence = -1; + source->flags = 0x0; +} + +void OptimizeSource(TSGrowableArray* sourceArray) { + for (uint32_t i = 0; i < sourceArray->Count(); i++) { + auto source = &(*sourceArray)[i]; + + 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--; + + // Shrink by 1 (to account for the removal of the null element) + sourceArray->SetCount(sourceArray->Count() - 1); + } + } +} + +void ProcessBooleanOperation(TSGrowableArray* sourceArray, int32_t combineMode) { + for (uint32_t i = 0; i < sourceArray->Count(); i++) { + auto source = &(*sourceArray)[i]; + + switch (combineMode) { + case 1: { + if ((~source->flags >> 1) & 0x1) { + DeleteSourceRect(sourceArray, i); + } + } + + case 3: { + if (source->flags & 0x2) { + DeleteSourceRect(sourceArray, i); + } + } + + case 4: { + if (source->flags & (0x1 | 0x2)) { + DeleteSourceRect(sourceArray, i); + } + } + + case 5: { + if (source->flags & 0x1) { + DeleteSourceRect(sourceArray, i); + } + } + + default: + break; + } + + source->flags = 0x0; + } +} + +void SRgnCombineRectf(HSRGN handle, RECTF* rect, void* param, int32_t combineMode) { + STORM_ASSERT(handle); + STORM_ASSERT(rect); + STORM_ASSERT(combineMode >= 1); + STORM_ASSERT(combineMode <= 6); + + HLOCKEDRGN lockedHandle; + auto rgn = s_rgntable.Lock(handle, &lockedHandle, 0); + + if (rgn) { + if (combineMode == 2 || combineMode == 6) { + if (!IsNullRect(rect)) { + rgn->sequence++; + AddSourceRect(&rgn->source, rect, param, rgn->sequence, combineMode == 6 ? 0x10000 : 0x0); + } + } else { + if (!IsNullRect(rect)) { + rgn->sequence++; + FragmentSourceRectangles(&rgn->source, 0, rgn->source.Count(), 0, rect, param, rgn->sequence); + } + + ProcessBooleanOperation(&rgn->source, combineMode); + OptimizeSource(&rgn->source); + } + + InvalidateRegion(rgn); + } + + s_rgntable.Unlock(lockedHandle); +} + +void SRgnCreate(HSRGN* handlePtr, uint32_t reserved) { + STORM_ASSERT(handlePtr); + STORM_ASSERT(!reserved); + + HLOCKEDRGN lockedHandle = nullptr; + auto rgn = s_rgntable.NewLock(handlePtr, &lockedHandle); + + ClearRegion(rgn); + + s_rgntable.Unlock(lockedHandle); +} + +void SRgnDelete(HSRGN handle) { + STORM_ASSERT(handle); + + s_rgntable.Delete(handle); +} + +void SRgnGetBoundingRectf(HSRGN handle, RECTF* rect) { + STORM_ASSERT(handle); + STORM_ASSERT(rect); + + rect->left = std::numeric_limits::max(); + rect->bottom = std::numeric_limits::max(); + rect->right = std::numeric_limits::min(); + rect->top = std::numeric_limits::min(); + + HLOCKEDRGN lockedHandle; + auto rgn = s_rgntable.Lock(handle, &lockedHandle, 0); + + if (!rgn) { + s_rgntable.Unlock(lockedHandle); + return; + } + + for (uint32_t i = 0; i < rgn->source.Count(); i++) { + auto source = &rgn->source[i]; + + if (!(source->flags & 0x10000)) { + rect->left = std::min(source->rect.left, rect->left); + rect->bottom = std::min(source->rect.bottom, rect->bottom); + rect->right = std::max(source->rect.right, rect->right); + rect->top = std::max(source->rect.top, rect->top); + } + } + + s_rgntable.Unlock(lockedHandle); + + if (IsNullRect(rect)) { + rect->left = 0.0f; + rect->bottom = 0.0f; + rect->right = 0.0f; + rect->top = 0.0f; + } +} diff --git a/storm/Region.hpp b/storm/Region.hpp new file mode 100644 index 0000000..e156141 --- /dev/null +++ b/storm/Region.hpp @@ -0,0 +1,15 @@ +#ifndef STORM_REGION_HPP +#define STORM_REGION_HPP + +#include "storm/region/Types.hpp" +#include + +void SRgnCombineRectf(HSRGN handle, RECTF* rect, void* param, int32_t combineMode); + +void SRgnCreate(HSRGN* handlePtr, uint32_t reserved); + +void SRgnDelete(HSRGN handle); + +void SRgnGetBoundingRectf(HSRGN handle, RECTF* rect); + +#endif diff --git a/storm/region/RGN.hpp b/storm/region/RGN.hpp new file mode 100644 index 0000000..2958d5c --- /dev/null +++ b/storm/region/RGN.hpp @@ -0,0 +1,32 @@ +#ifndef STORM_REGION_RGN_HPP +#define STORM_REGION_RGN_HPP + +#include "storm/region/Types.hpp" +#include "storm/Array.hpp" +#include "storm/Hash.hpp" +#include + +struct SOURCE { + RECTF rect; + void* param; + int32_t sequence; + uint32_t flags; +}; + +struct FOUNDPARAM { + void* param; + int32_t sequence; +}; + +class RGN : public TSHashObject { + public: + // Member variables + TSGrowableArray source; + TSGrowableArray combined; + TSGrowableArray foundparams; + RECTF foundparamsrect; + int32_t sequence; + int32_t dirty; +}; + +#endif diff --git a/storm/region/Types.hpp b/storm/region/Types.hpp new file mode 100644 index 0000000..116cf30 --- /dev/null +++ b/storm/region/Types.hpp @@ -0,0 +1,18 @@ +#ifndef STORM_REGION_TYPES_HPP +#define STORM_REGION_TYPES_HPP + +#include "storm/Handle.hpp" +#include + +DECLARE_HANDLE(HSRGN); + +DECLARE_HANDLE(HLOCKEDRGN); + +struct RECTF { + float left; + float bottom; + float right; + float top; +}; + +#endif diff --git a/test/Region.cpp b/test/Region.cpp new file mode 100644 index 0000000..249ddf7 --- /dev/null +++ b/test/Region.cpp @@ -0,0 +1,54 @@ +#include "storm/Region.hpp" +#include "test/Test.hpp" + +TEST_CASE("SRgnCreate", "[region]") { + SECTION("sets handle pointer to new region handle") { + HSRGN region; + SRgnCreate(®ion, 0); + + REQUIRE(region != nullptr); + + SRgnDelete(region); + } +} + +TEST_CASE("SRgnCombineRectf", "[region]") { + SECTION("combines the region with the given rect using combine mode 2") { + HSRGN region; + SRgnCreate(®ion, 0); + + RECTF baseRect = { 0.0f, 0.0f, 1.0f, 1.0f }; + SRgnCombineRectf(region, &baseRect, nullptr, 2); + + RECTF boundingRect = { 0.0f, 0.0f, 0.0f, 0.0f }; + SRgnGetBoundingRectf(region, &boundingRect); + + REQUIRE(boundingRect.left == 0.0f); + REQUIRE(boundingRect.bottom == 0.0f); + REQUIRE(boundingRect.right == 1.0f); + REQUIRE(boundingRect.top == 1.0f); + + SRgnDelete(region); + } + + SECTION("combines the region with the given rects using combine modes 2 and 4") { + HSRGN region; + SRgnCreate(®ion, 0); + + RECTF baseRect = { 0.0f, 0.0f, 1.0f, 1.0f }; + SRgnCombineRectf(region, &baseRect, nullptr, 2); + + RECTF newRect = { 0.0f, 0.0f, 0.0f, 0.0f }; + SRgnCombineRectf(region, &newRect, nullptr, 4); + + RECTF boundingRect = { 0.0f, 0.0f, 0.0f, 0.0f }; + SRgnGetBoundingRectf(region, &boundingRect); + + REQUIRE(boundingRect.left == 0.0f); + REQUIRE(boundingRect.bottom == 0.0f); + REQUIRE(boundingRect.right == 1.0f); + REQUIRE(boundingRect.top == 1.0f); + + SRgnDelete(region); + } +}