////////////////////////////////////////////////////////////// // // PLANEMAP // // This module defines two classes: PlaneMap, and PlaneItem. A // PlaneItem is an object that has a plane (a string) and an // XYZ position. A PlaneMap keeps track of potentially thousands // of PlaneItem objects, allowing you to search for PlaneItems // by scanning a geographic region. // // CLASS SPRITE DERIVES FROM PLANEITEM // // A PlaneMap records the positions of PlaneItems. The intent is // that class Sprite should derive from PlaneItem. That way, the // PlaneMap can record the positions of sprites. // // When you put derived types like Sprite into a PlaneMap, the // PlaneMap will work fine. However, when you scan the PlaneMap, // it will give return PlaneItem pointers, not Sprite pointers. // // If you're sure that the PlaneMap contains only sprites, then // you can use static_cast to convert those PlaneItem pointers to // Sprite pointers. Sadly, there's no simple way to avoid the casting. // // THE SCANNABLE REGION IS FINITE // // A plane is a rectangle, with a finite size: if you stray more // than 80,000,000 meters from the origin, then that PlaneItem // is beyond the scannable region. // // There's nothing stopping you from moving a PlaneItem outside // the scannable region. It is not an error to do so. // However, if you do move a PlaneItem outside the scannable // region, then that PlaneItem will not show up for scan_radius. // // PLANES CANNOT BE DESTROYED BUT THEY CAN BE UNINHABITED // // You can't "create" or "destroy" planes. Instead, we say // that a plane is "uninhabited" until you put a PlaneItem // there. Initially, all possible planes exist, but they're // all uninhabited until you put a PlaneItem there. Planes // that are uninhabited take no memory. // // THE NOWHERE PLANE // // If you use the literal "nowhere" as a plane name, a special case is // triggered: you're "nowhere." Radius scans can never find items // whose plane is "nowhere." The same also applies when you use the // empty string as a plane name. // // THE Z COORDINATE IS IGNORED // // Class PlaneItem stores X, Y, and Z. The Z coordinate is // ignored for all plane-scanning operations. In other words, // planes are 2D. The only reason we store a Z coordinate // is for the consistency of storing the model's X, Y, and Z // all in the same place. // // MEMORY MANAGEMENT // // PlaneMaps do not own PlaneItems. This is a deliberate choice. // We assume that sprites will be owned by the sprite ID table. // So therefore, the PlaneMaps shouldn't own the sprites. // // So instead, we use this rule: a PlaneMap has a function 'track' // that causes it to start tracking the location of a PlaneItem. // However, the PlaneMap still doesn't own the PlaneItem. // If you destroy a PlaneMap, all the PlaneItems // will automatically be untracked, but they won't be deleted. // ////////////////////////////////////////////////////////////// #ifndef PLANEMAP_HPP #define PLANEMAP_HPP #include #include #include #include #include "util.hpp" class PlaneMap; class PlaneItem { friend class PlaneMap; private: PlaneMap *pmap_; std::string plane_; util::XYZ xyz_; public: PlaneItem(); ~PlaneItem(); const std::string &plane() const { return plane_; } const util::XYZ &xyz() const { return xyz_; } const float x() const { return xyz_.x; } const float y() const { return xyz_.y; } const float z() const { return xyz_.z; } void untrack(); void set_pos(const std::string &plane, const util::XYZ &xyz); void set_pos(const std::string &plane, float x, float y, float z) { set_pos(plane, util::XYZ(x,y,z)); } void set_xyz(const util::XYZ &xyz) { set_pos(plane_, xyz); } void set_xyz(float x, float y, float z) { set_pos(plane_, util::XYZ(x, y, z)); } // Assume that some other object has methods 'get_plane' and 'get_xyz'. // Get the plane and XYZ from that object and store it in this PlaneItem. template void set_pos_from(const T &obj) { set_pos(obj.get_plane(), obj.get_xyz()); } // Assume that the PlaneItem is a field in an object of class T. // Static cast the PlaneItem pointer to a type T pointer. template T *static_field_cast() { const std::size_t offset = offsetof(T, plane_item_); return reinterpret_cast(reinterpret_cast(this) - offset); } }; class PlaneMap { friend class PlaneItem; private: using EltVec = std::vector; using Plane = std::map; std::map planes_; void remove(const std::string &plane, int64_t cell, PlaneItem *client); void insert(const std::string &plane, int64_t cell, PlaneItem *client); public: PlaneMap(); ~PlaneMap(); void track(PlaneItem *item); EltVec scan_radius(const std::string &plane, float x, float y, float radius) const; private: // unit testing stuff. friend int unittests_planemap(lua_State *L); EltVec get_cell(const std::string &plane, int64_t cell) const; int total_cells() const; void clear() { planes_.clear(); } }; #endif // PLANEMAP_HPP