1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2024-11-14 11:47:15 +01:00
SqMod/source/Misc/Areas.hpp

717 lines
24 KiB
C++

#ifndef _AREAS_HPP_
#define _AREAS_HPP_
// ------------------------------------------------------------------------------------------------
#include "Base/Shared.hpp"
#include "Base/Vector2.hpp"
#include "Base/Vector4.hpp"
#include "Base/Vector2i.hpp"
// ------------------------------------------------------------------------------------------------
#include <vector>
#include <utility>
// ------------------------------------------------------------------------------------------------
namespace SqMod {
/* ------------------------------------------------------------------------------------------------
* Various information associated with an area cell.
*/
struct AreaCell
{
// --------------------------------------------------------------------------------------------
typedef std::pair< Area *, LightObj > AreaPair; // A reference to an area object.
typedef std::vector< AreaPair > Areas; // A list of area objects.
// --------------------------------------------------------------------------------------------
float mL, mB, mR, mT; // Left-Bottom, Right-Top components of the cell bounding box.
// --------------------------------------------------------------------------------------------
Areas mAreas; // Areas that intersect with the cell.
// --------------------------------------------------------------------------------------------
int mLocks; // The amount of locks on the cell.
/* --------------------------------------------------------------------------------------------
* Default constructor.
*/
AreaCell()
: mL(0), mB(0), mR(0), mT(0), mAreas(0), mLocks(0)
{
//...
}
};
/* ------------------------------------------------------------------------------------------------
* Area implementation used to store area points.
*/
struct Area
{
typedef std::vector< Vector2 > Points;
typedef std::vector< AreaCell * > Cells;
// --------------------------------------------------------------------------------------------
static constexpr float DEF_L = std::numeric_limits< float >::infinity();
static constexpr float DEF_B = std::numeric_limits< float >::infinity();
static constexpr float DEF_R = -std::numeric_limits< float >::infinity();
static constexpr float DEF_T = -std::numeric_limits< float >::infinity();
// --------------------------------------------------------------------------------------------
float mL, mB, mR, mT; // Left-Bottom, Right-Top components of the bounding box.
// --------------------------------------------------------------------------------------------
Points mPoints; // Collection of points that make up the area.
// --------------------------------------------------------------------------------------------
SQInteger mID; // The user identifier given to this area.
// --------------------------------------------------------------------------------------------
Cells mCells; // The cells covered by this area.
// --------------------------------------------------------------------------------------------
String mName; // The user name given to this area.
/* --------------------------------------------------------------------------------------------
* Default constructor.
*/
Area()
: mL(DEF_L), mB(DEF_B), mR(DEF_R), mT(DEF_T), mPoints(), mID(0), mCells(), mName()
{
//...
}
/* --------------------------------------------------------------------------------------------
* Named constructor.
*/
Area(StackStrF & name)
: Area(16, name)
{
//...
}
/* --------------------------------------------------------------------------------------------
* Default constructor.
*/
Area(SQInteger sz, StackStrF & name)
: mL(DEF_L), mB(DEF_B), mR(DEF_R), mT(DEF_T), mPoints(), mID(0), mCells()
, mName(name.mPtr, name.mLen <= 0 ? 0 : name.mLen)
{
// Should we reserve some space for points in advance?
if (sz > 0)
{
mPoints.reserve(static_cast< size_t >(sz));
}
}
/* --------------------------------------------------------------------------------------------
* Vector2 constructor.
*/
Area(const Vector2 & a, const Vector2 & b, const Vector2 & c)
: Area(a.x, a.y, b.x, b.y, c.x, c.y, 16, StackStrF::Dummy())
{
//...
}
/* --------------------------------------------------------------------------------------------
* Vector2 constructor with name.
*/
Area(const Vector2 & a, const Vector2 & b, const Vector2 & c, StackStrF & name)
: Area(a.x, a.y, b.x, b.y, c.x, c.y, 16, name)
{
//...
}
/* --------------------------------------------------------------------------------------------
* Vector2 constructor with name and memory to reserve.
*/
Area(const Vector2 & a, const Vector2 & b, const Vector2 & c, SQInteger sz, StackStrF & name)
: Area(a.x, a.y, b.x, b.y, c.x, c.y, sz, name)
{
//...
}
/* --------------------------------------------------------------------------------------------
* Extended constructor.
*/
Area(float ax, float ay, float bx, float by, float cx, float cy)
: Area(ax, ay, bx, by, cx, cy, 16, StackStrF::Dummy())
{
//...
}
/* --------------------------------------------------------------------------------------------
* Extended constructor with name.
*/
Area(float ax, float ay, float bx, float by, float cx, float cy, StackStrF & name)
: Area(ax, ay, bx, by, cx, cy, 16, name)
{
//...
}
/* --------------------------------------------------------------------------------------------
* Base constructor.
*/
Area(float ax, float ay, float bx, float by, float cx, float cy, SQInteger sz, StackStrF & name)
: mL(DEF_L), mB(DEF_B), mR(DEF_R), mT(DEF_T), mPoints(), mID(0), mCells()
, mName(name.mPtr, name.mLen <= 0 ? 0 : name.mLen)
{
// Should we reserve some space for points in advance?
if (sz > 0)
{
mPoints.reserve(static_cast< size_t >(sz));
}
// Insert the given points
AddPointEx(ax, ay);
AddPointEx(bx, by);
AddPointEx(cx, cy);
}
/* --------------------------------------------------------------------------------------------
* Copy constructor.
*/
Area(const Area & o)
: mL(o.mL), mB(o.mB), mR(o.mR), mT(o.mT), mPoints(o.mPoints), mID(o.mID), mCells(0), mName(o.mName)
{
//...
}
/* --------------------------------------------------------------------------------------------
* Move constructor. (disabled)
*/
Area(Area && o) = delete;
/* --------------------------------------------------------------------------------------------
* Destructor.
*/
~Area()
{
//...
}
/* --------------------------------------------------------------------------------------------
* Copy assignment operator. (disabled)
*/
Area & operator = (const Area & o) = delete;
/* --------------------------------------------------------------------------------------------
* Move assignment operator. (disabled)
*/
Area & operator = (Area && o) = delete;
/* --------------------------------------------------------------------------------------------
* Used by the script engine to convert this instance to a string.
*/
const String & ToString() const
{
return mName;
}
/* --------------------------------------------------------------------------------------------
* Checks if the area is locked from changes and throws an exception if it is.
*/
void CheckLock()
{
// Are we connected to any cells?
if (!mCells.empty())
{
STHROWF("The area cannot be modified while being managed");
}
}
/* --------------------------------------------------------------------------------------------
* Retrieve the name of this area.
*/
const String & GetName() const
{
return mName;
}
/* --------------------------------------------------------------------------------------------
* Modify the name of this area.
*/
void SetName(StackStrF & name)
{
if (name.mLen <= 0)
{
mName.clear();
}
else
{
mName.assign(name.mPtr, static_cast< size_t >(name.mLen));
}
}
/* --------------------------------------------------------------------------------------------
* Modify the name of this area. (allows chaining function calls)
*/
Area & ApplyName(StackStrF & name)
{
SetName(name);
return *this;
}
/* --------------------------------------------------------------------------------------------
* Retrieve the identifier of this area.
*/
SQInteger GetID() const
{
return mID;
}
/* --------------------------------------------------------------------------------------------
* Modify the identifier of this area.
*/
void SetID(SQInteger id)
{
mID = id;
}
/* --------------------------------------------------------------------------------------------
* Modify the identifier of this area. (allows chaining function calls)
*/
Area & ApplyID(SQInteger id)
{
mID = id;
return *this;
}
/* --------------------------------------------------------------------------------------------
* Check if the area is locked from changes
*/
bool IsLocked() const
{
return mCells.empty();
}
/* --------------------------------------------------------------------------------------------
* Retrieve the center of this area.
*/
Vector2 GetCenter() const
{
return Vector2((mL * 0.5f) + (mR * 0.5f), (mB * 0.5f) + (mT * 0.5f));
}
/* --------------------------------------------------------------------------------------------
* Retrieve the bounding box of this area.
*/
Vector4 GetBoundingBox() const
{
return Vector4(mL, mB, mR, mT);
}
/* --------------------------------------------------------------------------------------------
* Modify the bounding box of this area.
*/
void SetBoundingBox(const Vector4 & b)
{
CheckLock();
// Apply the given bounding box
mL = b.x;
mB = b.y;
mR = b.z;
mT = b.w;
}
/* --------------------------------------------------------------------------------------------
* Modify the bounding box of this area.
*/
void SetBoundingBoxEx(float l, float b, float r, float t)
{
CheckLock();
// Apply the given bounding box
mL = l;
mB = b;
mR = r;
mT = t;
}
/* --------------------------------------------------------------------------------------------
* See whether the area has no points.
*/
bool Empty() const
{
return mPoints.empty();
}
/* --------------------------------------------------------------------------------------------
* Retrieve the number of points in this area.
*/
SQInteger Size() const
{
return ConvTo< SQInteger >::From(mPoints.size());
}
/* --------------------------------------------------------------------------------------------
* Retrieve the number of points this area has allocated for.
*/
SQInteger Capacity() const
{
return ConvTo< SQInteger >::From(mPoints.capacity());
}
/* --------------------------------------------------------------------------------------------
* Clear all points in this area.
*/
void Clear()
{
CheckLock();
// Perform the requested action
mPoints.clear();
}
/* --------------------------------------------------------------------------------------------
* Clear all points in this area.
*/
void Reserve(SQInteger sz)
{
// Perform the requested action
if (sz > 0)
{
mPoints.reserve(static_cast< size_t >(sz));
}
}
/* --------------------------------------------------------------------------------------------
* Add a 2D vector to the point list.
*/
void AddPoint(const Vector2 & v)
{
CheckLock();
// Perform the requested action
mPoints.emplace_back(v);
// Update the bounding box
Expand(v.x, v.y);
}
/* --------------------------------------------------------------------------------------------
* Add a point to the point list.
*/
void AddPointEx(float x, float y)
{
CheckLock();
// Perform the requested action
mPoints.emplace_back(x, y);
// Update the bounding box
Expand(x, y);
}
/* --------------------------------------------------------------------------------------------
* Add a 2D vector to the bounding box only. Not stored in the list.
*/
void AddVirtualPoint(const Vector2 & v)
{
CheckLock();
// Update the bounding box
Expand(v.x, v.y);
}
/* --------------------------------------------------------------------------------------------
* Add a point to the bounding box only. Not stored in the list.
*/
void AddVirtualPointEx(float x, float y)
{
CheckLock();
// Update the bounding box
Expand(x, y);
}
/* --------------------------------------------------------------------------------------------
* Add an array of points to the point list.
*/
void AddArray(const Sqrat::Array & a);
/* --------------------------------------------------------------------------------------------
* Test if a point is inside the bounding box and then the area.
*/
bool Test(const Vector2 & v)
{
// Is the given point in this bounding box at least?
if (mL <= v.x && mR >= v.x && mB <= v.y && mT >= v.y)
{
return mPoints.empty() ? true : IsInside(v.x, v.y);
}
// Not in this area
return false;
}
/* --------------------------------------------------------------------------------------------
* Test if a point is inside the bounding box and then the area.
*/
bool TestEx(float x, float y)
{
// Is the given point in this bounding box at least?
if (mL <= x && mR >= x && mB <= y && mT >= y)
{
return mPoints.empty() ? true : IsInside(x, y);
}
// Not in this area
return false;
}
/* --------------------------------------------------------------------------------------------
* Add this area to the manager to be scanned. MUST BE CALLED ONLY FROM SCRIPT!
*/
bool Manage();
/* --------------------------------------------------------------------------------------------
* Remove this area from the manager to no longer be scanned.
*/
bool Unmanage();
protected:
/* --------------------------------------------------------------------------------------------
* Test if a point is inside the area.
*/
bool IsInside(float x, float y) const;
/* --------------------------------------------------------------------------------------------
* Expand the bounding box area to include the given point.
*/
void Expand(float x, float y)
{
mL = std::fmin(mL, x);
mB = std::fmin(mB, y);
mR = std::fmax(mR, x);
mT = std::fmax(mT, y);
}
};
/* ------------------------------------------------------------------------------------------------
* Manager responsible for storing and partitioning areas.
*/
class AreaManager
{
private:
// --------------------------------------------------------------------------------------------
static AreaManager s_Inst; // Manager instance.
/* --------------------------------------------------------------------------------------------
* Base constructor.
*/
AreaManager(size_t sz = 16);
/* --------------------------------------------------------------------------------------------
* Copy constructor. (disabled)
*/
AreaManager(const AreaManager & o) = delete;
/* --------------------------------------------------------------------------------------------
* Move constructor. (disabled)
*/
AreaManager(AreaManager && o) = delete;
/* --------------------------------------------------------------------------------------------
* Destructor.
*/
~AreaManager()
{
//...
}
/* --------------------------------------------------------------------------------------------
* Copy assignment operator. (disabled)
*/
AreaManager & operator = (const AreaManager & o) = delete;
/* --------------------------------------------------------------------------------------------
* Move assignment operator. (disabled)
*/
AreaManager & operator = (AreaManager && o) = delete;
protected:
/* --------------------------------------------------------------------------------------------
* Helper used to make sure a cell is properly processed after leaving the scope.
*/
struct CellGuard
{
AreaCell & mCell;
/* ----------------------------------------------------------------------------------------
* Base constructor.
*/
CellGuard(AreaCell & cell)
: mCell(cell)
{
++(cell.mLocks); // Place a lock on the cell to prevent iterator invalidation
}
/* ----------------------------------------------------------------------------------------
* Destructor.
*/
~CellGuard()
{
// Remove the lock from the cell so it can be processed
--(mCell.mLocks);
// Process requested actions during the lock
AreaManager::Get().ProcQueue();
}
};
// --------------------------------------------------------------------------------------------
static constexpr int GRIDN = 16; // Number of horizontal and vertical number of cells.
static constexpr int GRIDH = GRIDN/2; // Half of the grid horizontal and vertical size.
static constexpr int CELLS = GRIDN*GRIDN; // Total number of cells in the grid.
static constexpr int CELLH = CELLS/2; // Half total number of cells in the grid.
static constexpr int CELLD = 256; // Area covered by a cell in the world.
static constexpr int NOCELL = std::numeric_limits< int >::max(); // Inexistent cell index.
/* --------------------------------------------------------------------------------------------
* Helper used to queue a certain action if the cell is locked.
*/
struct QueueElement
{
// ----------------------------------------------------------------------------------------
AreaCell * mCell; // The cell to be affected.
Area * mArea; // The area that made the request.
LightObj mObj; // Strong reference to the object.
/* ----------------------------------------------------------------------------------------
* Base constructor.
*/
QueueElement(AreaCell & cell, Area & area)
: mCell(&cell), mArea(&area), mObj()
{
//...
}
/* ----------------------------------------------------------------------------------------
* Base constructor.
*/
QueueElement(AreaCell & cell, Area & area, LightObj & obj)
: mCell(&cell), mArea(&area), mObj(obj)
{
//...
}
/* --------------------------------------------------------------------------------------------
* Copy constructor. (disabled)
*/
QueueElement(const QueueElement & o) = delete;
/* --------------------------------------------------------------------------------------------
* Move constructor.
*/
QueueElement(QueueElement && o)
: mCell(o.mCell), mArea(o.mArea), mObj(std::move(o.mObj))
{
// Take ownership
o.mCell = nullptr;
o.mArea = nullptr;
}
/* --------------------------------------------------------------------------------------------
* Destructor.
*/
~QueueElement()
{
//...
}
/* --------------------------------------------------------------------------------------------
* Copy assignment operator. (disabled)
*/
QueueElement & operator = (const QueueElement & o) = delete;
/* --------------------------------------------------------------------------------------------
* Move assignment operator.
*/
QueueElement & operator = (QueueElement && o)
{
// Avoid self assignment
if (this != &o)
{
// Transfer values
mCell = o.mCell;
mArea = o.mArea;
mObj = std::move(o.mObj);
// Take ownership
o.mCell = nullptr;
o.mArea = nullptr;
}
return *this;
}
};
// --------------------------------------------------------------------------------------------
typedef std::vector< QueueElement > Queue; // Queued actions.
typedef std::vector< Queue::iterator > ProcList; // Elements in the queue redy to process.
/* --------------------------------------------------------------------------------------------
* Attempt to insert an area into a cell or queue the action if not possible.
*/
void Insert(AreaCell & c, Area & a, LightObj & obj);
/* --------------------------------------------------------------------------------------------
* Attempt to remove an area from a cell or queue the action if not possible.
*/
void Remove(AreaCell & c, Area & a);
private:
// --------------------------------------------------------------------------------------------
Queue m_Queue; // Actions currently queued.
ProcList m_ProcList; // Actions ready to be completed.
// --------------------------------------------------------------------------------------------
AreaCell m_Grid[GRIDN][GRIDN]; // A grid of area lists.
public:
/* --------------------------------------------------------------------------------------------
* Retrieve the core instance.
*/
static AreaManager & Get()
{
return s_Inst;
}
/* --------------------------------------------------------------------------------------------
* Attempt to process elements in the queue that can be completed.
*/
void ProcQueue();
/* --------------------------------------------------------------------------------------------
* Clear all cell lists and release any script references.
*/
void Clear();
/* --------------------------------------------------------------------------------------------
* Add an area to be managed.
*/
void InsertArea(Area & a, LightObj & obj);
/* --------------------------------------------------------------------------------------------
* Add an area to be managed.
*/
void RemoveArea(Area & a);
/* --------------------------------------------------------------------------------------------
* Clear all cell lists and release any script references.
*/
Vector2i LocateCell(float x, float y);
/* --------------------------------------------------------------------------------------------
* Test a point to see whether it intersects with any areas
*/
template < typename F > void TestPoint(F && f, float x, float y)
{
// Transform the world coordinates into a cell coordinates
const Vector2i cc(LocateCell(x, y));
// Were these coordinates valid?
if (cc.x == NOCELL)
{
return; // Not our problem
}
// Retrieve a reference to the identified cell
AreaCell & c = m_Grid[cc.y][cc.x];
// Is this cell empty?
if (c.mAreas.empty())
{
return; // Nothing to test
}
// Guard the cell while processing
const CellGuard cg(c);
// Finally, begin processing the areas in this cell
for (auto & a : c.mAreas)
{
if (a.first->TestEx(x, y))
{
f(a);
}
}
}
};
} // Namespace:: SqMod
#endif // _AREAS_HPP_