mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2024-11-09 01:07:16 +01:00
487 lines
16 KiB
C++
487 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, 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
|