////////////////////////////////////////////////////////////// // // 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 "wrap-vector.hpp" #include "wrap-map.hpp" #include "wrap-string.hpp" #include "wrap-bytell-hash-map.hpp" #include "util.hpp" #include "luastack.hpp" #include #include #include class PlaneMap; class PlaneTree; class PlaneScan : public eng::nevernew { public: friend class PlaneMap; friend class PlaneTree; enum Shape { BOX, CYLINDER, SPHERE }; private: // The plane to scan. // // Note: the reason this uses std::string instead of eng::string // is that we want plane scans to not touch the engine heap. // std::string plane_; // The bounding box of the scan. util::XYZ center_, radius_; // If you scan a cylinder or SPHERE, it actually // scans the bounding box first, then clips out // the parts that aren't correct. Shape shape_; // When true, the items are sorted by ID. // WARNING: setting this to false can create // nondeterminism. Scans by lua should always be sorted. bool sorted_; // The near ID, if nonzero, is either PREPENDED to the // results, or OMITTED from the results, depending on include_near_. int64_t near_; bool include_near_; // If this is true, items on the nowhere plane are not scanned. bool omit_nowhere_; public: void clear() { plane_.clear(); shape_ = BOX; sorted_ = true; near_ = 0; include_near_ = false; omit_nowhere_ = false; radius_ = util::XYZ(); center_ = util::XYZ(); } PlaneScan() { clear(); } // Convert a lua table into a scan configuration. // // If there is an error in the configuration, // throws a lua error message. // // Caution: if this detects the configuration flag 'near', // then it stores the near ID. However, it doesn't fetch // the center and plane. It is the caller's responsibility // to check if 'near' has been set, and if so, store a center // and plane. This is admittedly ugly, but it eliminates // a dependency on the world module. // void configure(LuaKeywordParser &kw); void set_center_and_radius(const util::XYZ ¢er, float r) { set_center(center); set_radius(r); } void set_whole_plane() { shape_ = BOX; center_ = util::XYZ(); radius_.x = std::numeric_limits::infinity(); radius_.y = radius_.z = radius_.x; } void set_center(const util::XYZ ¢er) { center_ = center; } void set_radius(const util::XYZ &radius) { radius_ = radius; } void set_radius(float f) { radius_.x = radius_.y = radius_.z = f; } void set_plane(std::string_view p) { plane_ = p; } void set_shape(Shape s) { shape_ = s; } void set_sorted(bool s) { sorted_ = s; } void set_near(int64_t id, bool inc) { near_ = id; include_near_ = inc; } void set_omit_nowhere(bool b) { omit_nowhere_ = b; } int64_t near() const { return near_; } // Reveal the contents of the PlaneScan as a string. eng::string debug_string() const; }; class PlaneItem : public eng::nevernew { friend class PlaneTree; friend class PlaneMap; private: PlaneTree *tree_; PlaneItem *prev_; PlaneItem *next_; int64_t id_; eng::string plane_; float x_, y_, z_; public: PlaneItem(); ~PlaneItem(); // You may modify the ID at any time. void set_id(int64_t id) { id_ = id; } int64_t id() const { return id_; } const eng::string &plane() const { return plane_; } const float x() const { return x_; } const float y() const { return y_; } const float z() const { return z_; } void track(PlaneMap *pmap); void untrack() { track(nullptr); } void set_pos(const eng::string &plane, float x, float y, float z); void set_xyz(float x, float y, float z); }; class PlaneMap : public eng::nevernew { friend class PlaneItem; friend class PlaneTree; private: float default_radius_; // Plane Trees table. // eng::map, std::less<>> planes_; public: // No special code is needed for construction or destruction. PlaneMap(); ~PlaneMap(); // The 'insert' and 'remove' operators are inside class // PlaneItem. See PlaneItem::track and PlaneItem::untrack. // Scan the PlaneMap for items, return their IDs. // // Note: the reason this uses IdVector instead of IdVector // is that we want scans to not touch the engine heap. // void scan(const PlaneScan &sc, util::IdVector *into) const; // Set the default radius for all planes. // Maybe we'll make it adaptive some day. void set_default_radius(float r) { default_radius_ = r; } // Return a debug string for the specified plane. // This is for unit testing. eng::string tree_debug_string(const eng::string &plane); eng::string outliers_debug_string(const eng::string &plane); eng::string search_bboxes_debug_string(const PlaneScan &scan); eng::string scan_steps_debug_string(const PlaneScan &scan); // Untrack all planeitems. This is for unit testing. void untrack_all(); private: // unit testing stuff. friend int lfn_unittests_planemap(lua_State *L); }; #endif // PLANEMAP_HPP