1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2024-11-14 03:37:16 +01:00
SqMod/module/Misc/Areas.cpp
2020-03-22 06:53:04 +02:00

478 lines
16 KiB
C++

// ------------------------------------------------------------------------------------------------
#include "Misc/Areas.hpp"
// ------------------------------------------------------------------------------------------------
#include <algorithm>
// ------------------------------------------------------------------------------------------------
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) mutable -> bool {
// 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 true;
});
}
#pragma clang diagnostic pop
// ------------------------------------------------------------------------------------------------
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, DefaultVM::Get());
// 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(Object & env, Function & func, float x, float y)
{
// Is the function valid?
if (func.IsNull())
{
STHROWF("Invalid callback object");
}
// Should we use a custom environment?
else if (!env.IsNull())
{
func = Function(env.GetVM(), env, func.GetFunc());
}
// Begin testing
AreaManager::Get().TestPoint([&func](AreaCell::Areas::reference ap) -> void {
func.Execute(ap.second);
}, x, y);
}
// ------------------------------------------------------------------------------------------------
static void Areas_TestPoint(Object & env, Function & func, const Vector2 & v)
{
Areas_TestPointEx(env, func, v.x, v.y);
}
// ------------------------------------------------------------------------------------------------
static void Areas_TestPointOnEx(Object & ctx, Object & env, Function & func, float x, float y)
{
// Is the function valid?
if (func.IsNull())
{
STHROWF("Invalid callback object");
}
// Should we use a custom environment?
else if (!env.IsNull())
{
func = Function(env.GetVM(), env, func.GetFunc());
}
// Begin testing
AreaManager::Get().TestPoint([&ctx, &func](AreaCell::Areas::reference ap) -> void {
func.Execute(ctx, ap.second);
}, x, y);
}
// ------------------------------------------------------------------------------------------------
static void Areas_TestPointOn(Object & ctx, Object & env, Function & func, const Vector2 & v)
{
Areas_TestPointOnEx(ctx, env, func, v.x, v.y);
}
// ------------------------------------------------------------------------------------------------
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("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