// ------------------------------------------------------------------------------------------------ #include "Misc/Areas.hpp" // ------------------------------------------------------------------------------------------------ #include // ------------------------------------------------------------------------------------------------ namespace SqMod { // ------------------------------------------------------------------------------------------------ SQMODE_DECL_TYPENAME(AreaTypename, _SC("SqArea")) // ------------------------------------------------------------------------------------------------ AreaManager AreaManager::s_Inst; #pragma clang diagnostic push #pragma ide diagnostic ignored "UnusedValue" // ------------------------------------------------------------------------------------------------ void Area::AddArray(const Sqrat::Array & a) { float values[2]; a.Foreach([this, &values, n = int(0)](HSQUIRRELVM vm, SQInteger i) mutable -> SQRESULT { // Retrieve the type of the value const SQObjectType type = sq_gettype(vm, -1); // Are we dealing with a vector? if (type == OT_INSTANCE) { // Next time, start again for floats n = 0; // Grab the instance from the stack this->AddPoint(*ClassType< Vector2 >::GetInstance(vm, -1)); } else if (type & SQOBJECT_NUMERIC) { // Retrieve the value from the stack values[n] = Var< float >(vm, -1).value; // Do we have enough to form a vector? if (++n == 2) { this->AddPointEx(values[0], values[1]); // Reset the counter n = 0; } } // Ignore anything else return SQ_OK; }); } #pragma clang diagnostic pop // ------------------------------------------------------------------------------------------------ void Area::AddCircleEx(SQFloat cx, SQFloat cy, SQFloat cr, SQInteger num_segments) { for(SQInteger i = 0; i < num_segments; ++i) { CheckLock(); // Get the current angle #ifdef SQUSEDOUBLE SQFloat theta = 2.0d * SQMOD_PI64 * static_cast< SQFloat >(i) / static_cast< SQFloat >(num_segments); #else SQFloat theta = 2.0f * SQMOD_PI * static_cast< SQFloat >(i) / static_cast< SQFloat >(num_segments); #endif // SQUSEDOUBLE // Calculate the x component SQFloat x = (cr * std::cos(theta)) + cx; // Calculate the y component SQFloat y = (cr * std::sin(theta)) + cy; // Insert the point into the list mPoints.emplace_back(x, y); // Update the bounding box Expand(x, y); } } // ------------------------------------------------------------------------------------------------ bool Area::Manage() { // Are we connected to any cells? if (!mCells.empty()) { STHROWF("The area is already managed"); } // We expect this to be called only from the script so that the first parameter in the vm // is the area instance LightObj obj(1, SqVM()); // Attempt to manage this area AreaManager::Get().InsertArea(*this, obj); // Return whether the area is now managed by any cells return !mCells.empty(); } // ------------------------------------------------------------------------------------------------ bool Area::Unmanage() { // Are we connected to any cells? if (mCells.empty()) { return true; // Already unmanaged } // Attempt to unmanage this area AreaManager::Get().RemoveArea(*this); // Return whether the area is not managed by any cells return mCells.empty(); } // ------------------------------------------------------------------------------------------------ bool Area::IsInside(float x, float y) const { // Is the an area to test? if (mPoints.size() < 3) { return false; // Can't possibly be in an area that doesn't exist } // http://sidvind.com/wiki/Point-in-polygon:_Jordan_Curve_Theorem // The points creating the polygon float x1, x2; // How many times the ray crosses a line segment int crossings = 0; // Iterate through each line for (Uint32 i = 0, n = static_cast< Uint32 >(mPoints.size()); i < n; ++i) { Points::const_reference a = mPoints[i]; Points::const_reference b = mPoints[(i + 1) % n]; // This is done to ensure that we get the same result when // the line goes from left to right and right to left. if (a.x < b.x) { x1 = a.x; x2 = b.x; } else { x1 = b.x; x2 = a.x; } // First check if the ray is able to cross the line if (x > x1 && x <= x2 && (y < a.y || y <= b.y)) { // Calculate the equation of the line const float dx = (b.x - a.x); const float dy = (b.y - a.y); float k; if (fabsf(dx) < 0.000001f) { k = 0xffffffffu; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) } else { k = (dy / dx); } const float m = (a.y - k * a.x); const float y2 = (k * x + m); // Does the ray cross the line? if (y <= y2) { ++crossings; } } } // Return if the crossings are not even return (crossings % 2 == 1); } // ------------------------------------------------------------------------------------------------ AreaManager::AreaManager(size_t sz) noexcept : m_Queue(), m_ProcList(), m_Grid{} { // Negative half grid size (left) int l = (-GRIDH * CELLD); // Positive half grid size minus one cell (bottom) int b = (abs(l) - CELLD); // Negative half grid size minus one cell (right) int r = (l + CELLD); // Positive half grid size (top) int t = abs(l); // Initialize the grid cells for (auto & a : m_Grid) { for (auto & c : a) { // Grab a reference to the cell // Configure the range of the cell c.mL = static_cast< float >(l); c.mB = static_cast< float >(b); c.mR = static_cast< float >(r); c.mT = static_cast< float >(t); // Reserve area memory if requested c.mAreas.reserve(sz); // Reset the locks on this area c.mLocks = 0; // Advance the left side l = r; // Advance the right side r += CELLD; // Should we advance to the next row? if (r > (GRIDH * CELLD)) { // Reset the left side l = (-GRIDH * CELLD); // Reset the right side r = (l + CELLD); // Advance the bottom b -= CELLD; // Advance the top t -= CELLD; } } } // Reserve some space in the queue m_Queue.reserve(128); m_ProcList.reserve(128); } // ------------------------------------------------------------------------------------------------ void AreaManager::Insert(AreaCell & c, Area & a, LightObj & obj) { // Is this cell currently locked? if (c.mLocks) { m_Queue.emplace_back(c, a, obj); // Queue this request for now } else { c.mAreas.emplace_back(&a, obj); } // Associate the area with this cell so it can't be managed again (even while in the queue) a.mCells.push_back(&c); } // ------------------------------------------------------------------------------------------------ void AreaManager::Remove(AreaCell & c, Area & a) { // Is this cell currently locked? if (c.mLocks) { m_Queue.emplace_back(c, a); // Queue this request for now } else { // Attempt to locate this area in the cell auto itr = std::find_if(c.mAreas.begin(), c.mAreas.end(), [&a](AreaCell::Areas::reference p) -> bool { return (p.first == &a); }); // Have we found it? if (itr != c.mAreas.end()) { c.mAreas.erase(itr); // Erase it } } // Dissociate the area with this cell so it can be managed again (even while in the queue) auto itr = std::find(a.mCells.begin(), a.mCells.end(), &c); // Was is associated? if (itr != a.mCells.end()) { a.mCells.erase(itr); // Dissociate them } } // ------------------------------------------------------------------------------------------------ void AreaManager::ProcQueue() { // Look for actions that can be completed for (auto itr = m_Queue.begin(); itr != m_Queue.end(); ++itr) { // Was this cell unlocked in the meantime? if (itr->mCell->mLocks <= 0) { m_ProcList.push_back(itr); } } // Process the actions that are ready for (auto & itr : m_ProcList) { // Was this a remove request? if (itr->mObj.IsNull()) { Remove(*(itr->mCell), *(itr->mArea)); } else { Insert(*(itr->mCell), *(itr->mArea), itr->mObj); } } // Remove processed requests for (auto & itr : m_ProcList) { m_Queue.erase(itr); } // Actions were processed m_ProcList.clear(); } // ------------------------------------------------------------------------------------------------ void AreaManager::Clear() { // Clear the cells for (AreaCell (&row)[GRIDN] : m_Grid) { for (AreaCell & c : row) { c.mAreas.clear(); } } // Clear the queue as well m_Queue.clear(); m_ProcList.clear(); } // ------------------------------------------------------------------------------------------------ void AreaManager::InsertArea(Area & a, LightObj & obj) { // See if this area is already managed if (!a.mCells.empty() || a.mPoints.empty()) { return; // Already managed or nothing to manage } // Go through each cell and check if the area touches it for (auto & y : m_Grid) { for (auto & c : y) { // Does the bounding box of this cell intersect with the one of the area? if (a.mL <= c.mR && c.mL <= a.mR && a.mB <= c.mT && c.mB <= a.mT) { Insert(c, a, obj); // Attempt to insert the area into this cell } } } } // ------------------------------------------------------------------------------------------------ void AreaManager::RemoveArea(Area & a) { // Just remove the associated cells for (auto c : a.mCells) { Remove(*c, a); } } // ------------------------------------------------------------------------------------------------ Vector2i AreaManager::LocateCell(float x, float y) { // Transform the world coordinates into a cell coordinates // and cast to integral after rounding the value int xc = static_cast< int >(std::round(x / CELLD)); int yc = static_cast< int >(std::round(y / CELLD)); // Grab the absolute cell coordinates for range checking const int xca = std::abs(xc); const int yca = std::abs(yc); // Make sure the cell coordinates are within range if (xca > (GRIDH+1) || yca > (GRIDH+1)) { return {NOCELL, NOCELL}; // Out of our scanning area } // Clamp the x coordinate if necessary if (xca >= (GRIDH)) { xc = xc < 0 ? -(GRIDH-1) : (GRIDH-1); } // Clamp the y coordinate if necessary if (yca >= (GRIDH)) { yc = xc < 0 ? -(GRIDH-1) : (GRIDH-1); } // Return the identified cell row and column return {GRIDH+xc, GRIDH-yc}; } // ------------------------------------------------------------------------------------------------ static void Areas_TestPointEx(float x, float y, Function & func) { // Is the function valid? if (func.IsNull()) { STHROWF("Invalid callback object"); } // Begin testing AreaManager::Get().TestPoint([&func](AreaCell::Areas::reference ap) -> void { func.Execute(ap.second); }, x, y); } // ------------------------------------------------------------------------------------------------ static void Areas_TestPoint(const Vector2 & v, Function & func) { Areas_TestPointEx(v.x, v.y, func); } // ------------------------------------------------------------------------------------------------ static void Areas_TestPointOnEx(float x, float y, Object & ctx, Function & func) { // Begin testing AreaManager::Get().TestPoint([&ctx, &func](AreaCell::Areas::reference ap) -> void { func.Execute(ctx, ap.second); }, x, y); } // ------------------------------------------------------------------------------------------------ static void Areas_TestPointOn(const Vector2 & v, Object & ctx, Function & func) { Areas_TestPointOnEx(v.x, v.y, ctx, func); } // ------------------------------------------------------------------------------------------------ static Vector2i Areas_LocatePointCell(const Vector2 & v) { return AreaManager::Get().LocateCell(v.x, v.y); } // ------------------------------------------------------------------------------------------------ static Vector2i Areas_LocatePointCellEx(float x, float y) { return AreaManager::Get().LocateCell(x, y); } // ------------------------------------------------------------------------------------------------ void TerminateAreas() { AreaManager::Get().Clear(); } // ================================================================================================ void Register_Areas(HSQUIRRELVM vm) { RootTable(vm).Bind(_SC("SqArea"), Class< Area >(vm, AreaTypename::Str) // Constructors .Ctor() .Ctor< StackStrF & >() .Ctor< SQInteger, StackStrF & >() .Ctor< const Vector2 &, const Vector2 &, const Vector2 & >() .Ctor< const Vector2 &, const Vector2 &, const Vector2 &, StackStrF & >() .Ctor< const Vector2 &, const Vector2 &, const Vector2 &, SQInteger, StackStrF & >() .Ctor< float, float, float, float, float, float >() .Ctor< float, float, float, float, float, float, StackStrF & >() .Ctor< float, float, float, float, float, float, SQInteger, StackStrF & >() // Meta-methods .SquirrelFunc(_SC("_typename"), &AreaTypename::Fn) .Func(_SC("_tostring"), &Area::ToString) // Member Properties .Prop(_SC("Name"), &Area::GetName, &Area::SetName) .Prop(_SC("ID"), &Area::GetID, &Area::SetID) .Prop(_SC("Locked"), &Area::IsLocked) .Prop(_SC("IsLocked"), &Area::IsLocked) .Prop(_SC("Center"), &Area::GetCenter) .Prop(_SC("Box"), &Area::GetBoundingBox, &Area::SetBoundingBox) .Prop(_SC("Empty"), &Area::Empty) .Prop(_SC("Empty"), &Area::Empty) .Prop(_SC("Size"), &Area::Size) .Prop(_SC("Points"), &Area::Size) .Prop(_SC("Capacity"), &Area::Capacity) // Member Methods .FmtFunc(_SC("SetName"), &Area::ApplyName) .Func(_SC("SetID"), &Area::ApplyID) .Func(_SC("Clear"), &Area::Clear) .Func(_SC("Reserve"), &Area::Reserve) .Func(_SC("SetBox"), &Area::SetBoundingBoxEx) .Func(_SC("SetBoundingBox"), &Area::SetBoundingBoxEx) .Func(_SC("Add"), &Area::AddPoint) .Func(_SC("AddEx"), &Area::AddPointEx) .Func(_SC("AddVirtual"), &Area::AddVirtualPoint) .Func(_SC("AddVirtualEx"), &Area::AddVirtualPointEx) .Func(_SC("AddCircle"), &Area::AddCircle) .Func(_SC("AddCircleEx"), &Area::AddCircleEx) .Func(_SC("AddFake"), &Area::AddVirtualPoint) .Func(_SC("AddFakeEx"), &Area::AddVirtualPointEx) .Func(_SC("AddArray"), &Area::AddArray) .Func(_SC("Test"), &Area::Test) .Func(_SC("TestEx"), &Area::TestEx) .Func(_SC("Manage"), &Area::Manage) .Func(_SC("Unmanage"), &Area::Unmanage) // Static Functions .StaticFunc(_SC("GlobalTest"), &Areas_TestPoint) .StaticFunc(_SC("GlobalTestEx"), &Areas_TestPointEx) .StaticFunc(_SC("GlobalTestOn"), &Areas_TestPointOn) .StaticFunc(_SC("GlobalTestOnEx"), &Areas_TestPointOnEx) .StaticFunc(_SC("LocatePointCell"), &Areas_LocatePointCell) .StaticFunc(_SC("LocatePointCellEx"), &Areas_LocatePointCellEx) .StaticFunc(_SC("UnmanageAll"), &TerminateAreas) ); } } // Namespace:: SqMod