diff --git a/include/ieclass.h b/include/ieclass.h index 9a384ac1c4..e048d51eaa 100644 --- a/include/ieclass.h +++ b/include/ieclass.h @@ -200,9 +200,10 @@ typedef std::shared_ptr IEntityClassPtr; typedef std::shared_ptr IEntityClassConstPtr; /** - * Entity class interface. An entity class represents a single type - * of entity that can be created by the EntityCreator. Entity classes are parsed - * from .DEF files during startup. + * \brief Entity class interface. + * + * An entity class represents a single type of entity that can be created by + * the EntityCreator. Entity classes are parsed from .DEF files during startup. * * Entity class attribute names are compared case-insensitively, as in the * Entity class. @@ -226,35 +227,6 @@ class IEntityClass /// Query whether this entity class represents a light. virtual bool isLight() const = 0; - /* ENTITY ATTACHMENTS */ - - /// Details of an attached entity - struct Attachment - { - /// Entity class of the attached entity - std::string eclass; - - /// Vector offset where the attached entity should appear - Vector3 offset; - }; - - /// A functor which can received Attachments - using AttachmentFunc = std::function; - - /** - * \brief - * Iterate over attached entities, if any. - * - * Each entity class can define one or more attached entities, which should - * appear at specific offsets relative to the parent entity. Such attached - * entities are for visualisation only, and should not be saved into the - * map as genuine map entities. - * - * \param func - * Functor to receive attachment information. - */ - virtual void forEachAttachment(AttachmentFunc func) const = 0; - /* ENTITY CLASS SIZE */ /// Query whether this entity has a fixed size. diff --git a/include/ientity.h b/include/ientity.h index 4510f29eeb..04e1a8b635 100644 --- a/include/ientity.h +++ b/include/ientity.h @@ -209,6 +209,34 @@ class Entity * given entity class name. className is treated case-sensitively. */ virtual bool isOfType(const std::string& className) = 0; + + /* ENTITY ATTACHMENTS */ + + /// Details of an attached entity + struct Attachment + { + /// Entity class of the attached entity + std::string eclass; + + /// Vector offset where the attached entity should appear + Vector3 offset; + }; + + /// A functor which can receive Attachment objects + using AttachmentFunc = std::function; + + /** + * \brief Iterate over attached entities, if any. + * + * Each entity can define one or more attached entities, which should + * appear at specific offsets relative to the parent entity. Such attached + * entities are for visualisation only, and should not be saved into the + * map as genuine map entities. + * + * \param func + * Functor to receive attachment information. + */ + virtual void forEachAttachment(AttachmentFunc func) const = 0; }; /// Interface for a INode subclass that contains an Entity diff --git a/radiantcore/CMakeLists.txt b/radiantcore/CMakeLists.txt index f77b15c9ac..56a43c06ac 100644 --- a/radiantcore/CMakeLists.txt +++ b/radiantcore/CMakeLists.txt @@ -28,6 +28,7 @@ add_library(radiantcore MODULE eclass/EClassColourManager.cpp eclass/EClassManager.cpp entity/AngleKey.cpp + entity/AttachmentData.cpp entity/curve/CurveCatmullRom.cpp entity/curve/Curve.cpp entity/curve/CurveEditInstance.cpp diff --git a/radiantcore/eclass/Doom3EntityClass.cpp b/radiantcore/eclass/Doom3EntityClass.cpp index 0cb34f8ba3..d016e21951 100644 --- a/radiantcore/eclass/Doom3EntityClass.cpp +++ b/radiantcore/eclass/Doom3EntityClass.cpp @@ -11,199 +11,6 @@ namespace eclass { -namespace -{ - -// Constants -const std::string DEF_ATTACH = "def_attach"; -const std::string NAME_ATTACH = "name_attach"; -const std::string POS_ATTACH = "pos_attach"; - -const std::string ATTACH_POS_NAME = "attach_pos_name"; -const std::string ATTACH_POS_ORIGIN = "attach_pos_origin"; -const std::string ATTACH_POS_JOINT = "attach_pos_joint"; -const std::string ATTACH_POS_ANGLES = "attach_pos_angles"; - -// Extract and return the string suffix for a key (which might be the empty -// string if there is no suffix). Returns false if the key did not match -// the prefix. -bool tryGetSuffixedKey(const std::string& key, const std::string& prefix, std::string& suffixedOutput) -{ - if (string::istarts_with(key, prefix)) - { - suffixedOutput = key.substr(prefix.length()); - return true; - } - - suffixedOutput.clear(); - return false; -} - -} // namespace - -// Attachment helper object -class Doom3EntityClass::Attachments -{ - // Name of the entity class being parsed (for debug/error purposes) - std::string _parentClassname; - - // Any def_attached entities. Each attachment has an entity class, a - // position and optionally a name. - struct Attachment - { - // Class of entity that is attached - std::string className; - - // Name of the entity that is attached - std::string name; - - // Name of the position (AttachPos) at which the entity should be - // attached - std::string posName; - }; - - // Attached object map initially indexed by key suffix (e.g. "1" for - // "name_attach1"), then by name. - typedef std::map AttachedObjects; - AttachedObjects _objects; - - // Positions at which def_attached entities can be attached. - struct AttachPos - { - // Name of this attachment position (referred to in the - // Attachment::posName variable) - std::string name; - - // 3D offset position from our origin or the model joint, if a joint is - // specified - Vector3 origin; - - // Rotation of the attached entity - Vector3 angles; - - // Optional model joint relative to which the origin should be - // calculated - std::string joint; - }; - - // Attach position map initially indexed by key suffix (e.g. "_zhandr" for - // "attach_pos_name_zhandr"), then by name. It appears that only attachpos - // keys are using arbitrary strings instead of numeric suffixes, but we - // might as well treat everything the same way. - typedef std::map AttachPositions; - AttachPositions _positions; - -private: - - template void reindexMapByName(Map& inputMap) - { - Map copy(inputMap); - inputMap.clear(); - - // Take each item from the copied map, and insert it into the original - // map using the name as the key. - for (typename Map::value_type pair : copy) - { - if (!pair.second.name.empty()) // ignore empty names - { - inputMap.insert( - typename Map::value_type(pair.second.name, pair.second) - ); - } - } - } - -public: - - // Initialise and set classname - Attachments(const std::string& name) - : _parentClassname(name) - { } - - // Clear all data - void clear() - { - _objects.clear(); - _positions.clear(); - } - - // Attempt to extract attachment data from the given key/value pair - void parseDefAttachKeys(const std::string& key, const std::string& value) - { - std::string suffix; - - if (tryGetSuffixedKey(key, DEF_ATTACH, suffix)) - { - _objects[suffix].className = value; - } - else if (tryGetSuffixedKey(key, NAME_ATTACH, suffix)) - { - _objects[suffix].name = value; - } - else if (tryGetSuffixedKey(key, POS_ATTACH, suffix)) - { - _objects[suffix].posName = value; - } - else if (tryGetSuffixedKey(key, ATTACH_POS_NAME, suffix)) - { - _positions[suffix].name = value; - } - else if (tryGetSuffixedKey(key, ATTACH_POS_ORIGIN, suffix)) - { - _positions[suffix].origin = string::convert(value); - } - else if (tryGetSuffixedKey(key, ATTACH_POS_ANGLES, suffix)) - { - _positions[suffix].angles = string::convert(value); - } - else if (tryGetSuffixedKey(key, ATTACH_POS_JOINT, suffix)) - { - _positions[suffix].joint = value; - } - } - - // Post-process after attachment parsing - void validateAttachments() - { - // During parsing we indexed spawnargs by string suffix so that matching - // keys could be found. From now on we are no longer interested in the - // suffixes so we will re-build the maps indexed by name instead. - reindexMapByName(_objects); - reindexMapByName(_positions); - - // Drop any attached objects that specify a non-existent position (I - // assume new positions cannot be dynamically created in game). - for (AttachedObjects::iterator i = _objects.begin(); - i != _objects.end(); - /* in-loop increment */) - { - if (_positions.find(i->second.posName) == _positions.end()) - { - rWarning() - << "[eclassmgr] Entity class '" << _parentClassname - << "' tries to attach '" << i->first << "' at non-existent " - << "position '" << i->second.posName << "'\n"; - - _objects.erase(i++); - } - else - { - ++i; - } - } - } - - // Iterate over attachments - void forEachAttachment(IEntityClass::AttachmentFunc func) - { - for (auto i = _objects.begin(); i != _objects.end(); ++i) - { - IEntityClass::Attachment a; - a.eclass = i->second.className; - } - } -}; - const std::string Doom3EntityClass::DefaultWireShader("<0.3 0.3 1>"); const std::string Doom3EntityClass::DefaultFillShader("(0.3 0.3 1)"); const Vector3 Doom3EntityClass::DefaultEntityColour(0.3, 0.3, 1); @@ -225,7 +32,6 @@ Doom3EntityClass::Doom3EntityClass(const std::string& name, const vfs::FileInfo& _inheritanceResolved(false), _modName("base"), _emptyAttribute("", "", ""), - _attachments(new Attachments(name)), _parseStamp(0) {} @@ -247,11 +53,6 @@ sigc::signal& Doom3EntityClass::changedSignal() return _changedSignal; } -void Doom3EntityClass::forEachAttachment(AttachmentFunc func) const -{ - _attachments->forEachAttachment(func); -} - bool Doom3EntityClass::isFixedSize() const { if (_fixedSize) { @@ -519,8 +320,6 @@ void Doom3EntityClass::clear() _inheritanceResolved = false; _modName = "base"; - - _attachments->clear(); } void Doom3EntityClass::parseEditorSpawnarg(const std::string& key, @@ -594,9 +393,6 @@ void Doom3EntityClass::parseFromTokens(parser::DefTokeniser& tokeniser) parseEditorSpawnarg(key, value); } - // Try parsing this key/value with the Attachments manager - _attachments->parseDefAttachKeys(key, value); - // Add the EntityClassAttribute for this key/val if (getAttribute(key).getType().empty()) { @@ -619,8 +415,6 @@ void Doom3EntityClass::parseFromTokens(parser::DefTokeniser& tokeniser) } } // while true - _attachments->validateAttachments(); - // Notify the observers _changedSignal.emit(); } diff --git a/radiantcore/eclass/Doom3EntityClass.h b/radiantcore/eclass/Doom3EntityClass.h index 898e9815ca..94b535a181 100644 --- a/radiantcore/eclass/Doom3EntityClass.h +++ b/radiantcore/eclass/Doom3EntityClass.h @@ -101,10 +101,6 @@ class Doom3EntityClass // The empty attribute EntityClassAttribute _emptyAttribute; - // Helper object to manage attached entities - class Attachments; - std::unique_ptr _attachments; - // The time this def has been parsed std::size_t _parseStamp; @@ -159,7 +155,6 @@ class Doom3EntityClass std::string getName() const override; const IEntityClass* getParent() const override; sigc::signal& changedSignal() override; - void forEachAttachment(AttachmentFunc func) const override; bool isFixedSize() const override; AABB getBounds() const override; bool isLight() const override; diff --git a/radiantcore/entity/AttachmentData.cpp b/radiantcore/entity/AttachmentData.cpp new file mode 100644 index 0000000000..e08241aa25 --- /dev/null +++ b/radiantcore/entity/AttachmentData.cpp @@ -0,0 +1,104 @@ +#include "AttachmentData.h" + +#include +#include + +namespace entity +{ + +namespace +{ + +// Constants +const std::string DEF_ATTACH = "def_attach"; +const std::string NAME_ATTACH = "name_attach"; +const std::string POS_ATTACH = "pos_attach"; + +const std::string ATTACH_POS_NAME = "attach_pos_name"; +const std::string ATTACH_POS_ORIGIN = "attach_pos_origin"; +const std::string ATTACH_POS_JOINT = "attach_pos_joint"; +const std::string ATTACH_POS_ANGLES = "attach_pos_angles"; + +// Extract and return the string suffix for a key (which might be the empty +// string if there is no suffix). Returns false if the key did not match +// the prefix. +bool tryGetSuffixedKey(const std::string& key, const std::string& prefix, std::string& suffixedOutput) +{ + if (string::istarts_with(key, prefix)) + { + suffixedOutput = key.substr(prefix.length()); + return true; + } + + suffixedOutput.clear(); + return false; +} + +} // namespace + +void AttachmentData::parseDefAttachKeys(const std::string& key, + const std::string& value) +{ + std::string suffix; + + if (tryGetSuffixedKey(key, DEF_ATTACH, suffix)) + { + _objects[suffix].className = value; + } + else if (tryGetSuffixedKey(key, NAME_ATTACH, suffix)) + { + _objects[suffix].name = value; + } + else if (tryGetSuffixedKey(key, POS_ATTACH, suffix)) + { + _objects[suffix].posName = value; + } + else if (tryGetSuffixedKey(key, ATTACH_POS_NAME, suffix)) + { + _positions[suffix].name = value; + } + else if (tryGetSuffixedKey(key, ATTACH_POS_ORIGIN, suffix)) + { + _positions[suffix].origin = string::convert(value); + } + else if (tryGetSuffixedKey(key, ATTACH_POS_ANGLES, suffix)) + { + _positions[suffix].angles = string::convert(value); + } + else if (tryGetSuffixedKey(key, ATTACH_POS_JOINT, suffix)) + { + _positions[suffix].joint = value; + } +} + +void AttachmentData::validateAttachments() +{ + // During parsing we indexed spawnargs by string suffix so that matching + // keys could be found. From now on we are no longer interested in the + // suffixes so we will re-build the maps indexed by name instead. + reindexMapByName(_objects); + reindexMapByName(_positions); + + // Drop any attached objects that specify a non-existent position (I + // assume new positions cannot be dynamically created in game). + for (AttachedObjects::iterator i = _objects.begin(); + i != _objects.end(); + /* in-loop increment */) + { + if (_positions.find(i->second.posName) == _positions.end()) + { + rWarning() + << "[eclassmgr] Entity class '" << _parentClassname + << "' tries to attach '" << i->first << "' at non-existent " + << "position '" << i->second.posName << "'\n"; + + _objects.erase(i++); + } + else + { + ++i; + } + } +} + +} \ No newline at end of file diff --git a/radiantcore/entity/AttachmentData.h b/radiantcore/entity/AttachmentData.h new file mode 100644 index 0000000000..9c81282eef --- /dev/null +++ b/radiantcore/entity/AttachmentData.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include "math/Vector3.h" + +#include + +namespace entity +{ + +/// Representation of parsed `def_attach` and related keys +class AttachmentData +{ + // Name of the entity class being parsed (for debug/error purposes) + std::string _parentClassname; + + // Any def_attached entities. Each attachment has an entity class, a + // position and optionally a name. + struct Attachment + { + // Class of entity that is attached + std::string className; + + // Name of the entity that is attached + std::string name; + + // Name of the position (AttachPos) at which the entity should be + // attached + std::string posName; + }; + + // Attached object map initially indexed by key suffix (e.g. "1" for + // "name_attach1"), then by name. + typedef std::map AttachedObjects; + AttachedObjects _objects; + + // Positions at which def_attached entities can be attached. + struct AttachPos + { + // Name of this attachment position (referred to in the + // Attachment::posName variable) + std::string name; + + // 3D offset position from our origin or the model joint, if a joint is + // specified + Vector3 origin; + + // Rotation of the attached entity + Vector3 angles; + + // Optional model joint relative to which the origin should be + // calculated + std::string joint; + }; + + // Attach position map initially indexed by key suffix (e.g. "_zhandr" for + // "attach_pos_name_zhandr"), then by name. It appears that only attachpos + // keys are using arbitrary strings instead of numeric suffixes, but we + // might as well treat everything the same way. + typedef std::map AttachPositions; + AttachPositions _positions; + +private: + + template void reindexMapByName(Map& inputMap) + { + Map copy(inputMap); + inputMap.clear(); + + // Take each item from the copied map, and insert it into the original + // map using the name as the key. + for (typename Map::value_type pair : copy) + { + if (!pair.second.name.empty()) // ignore empty names + { + inputMap.insert( + typename Map::value_type(pair.second.name, pair.second) + ); + } + } + } + +public: + + /// Initialise and set classname + AttachmentData(const std::string& name) + : _parentClassname(name) + { } + + /// Clear all data + void clear() + { + _objects.clear(); + _positions.clear(); + } + + /// Attempt to extract attachment data from the given key/value pair + void parseDefAttachKeys(const std::string& key, const std::string& value); + + /// Sanitise and resolve attachments and their named positions + void validateAttachments(); + + /// Invoke a functor for each attachment + template + void forEachAttachment(Functor func) const + { + for (auto i = _objects.begin(); i != _objects.end(); ++i) + { + Entity::Attachment a; + a.eclass = i->second.className; + func(a); + } + } +}; + +} \ No newline at end of file diff --git a/radiantcore/entity/SpawnArgs.cpp b/radiantcore/entity/SpawnArgs.cpp index 8092a32830..7d41ba591c 100644 --- a/radiantcore/entity/SpawnArgs.cpp +++ b/radiantcore/entity/SpawnArgs.cpp @@ -13,7 +13,8 @@ SpawnArgs::SpawnArgs(const IEntityClassPtr& eclass) : _undo(_keyValues, std::bind(&SpawnArgs::importState, this, std::placeholders::_1), "EntityKeyValues"), _instanced(false), _observerMutex(false), - _isContainer(!eclass->isFixedSize()) + _isContainer(!eclass->isFixedSize()), + _attachments(eclass->getName()) {} SpawnArgs::SpawnArgs(const SpawnArgs& other) : @@ -22,7 +23,8 @@ SpawnArgs::SpawnArgs(const SpawnArgs& other) : _undo(_keyValues, std::bind(&SpawnArgs::importState, this, std::placeholders::_1), "EntityKeyValues"), _instanced(false), _observerMutex(false), - _isContainer(other._isContainer) + _isContainer(other._isContainer), + _attachments(other._attachments) { for (KeyValues::const_iterator i = other._keyValues.begin(); i != other._keyValues.end(); @@ -193,6 +195,11 @@ bool SpawnArgs::isInherited(const std::string& key) const return (!definedLocally && !_eclass->getAttribute(key).getValue().empty()); } +void SpawnArgs::forEachAttachment(AttachmentFunc func) const +{ + _attachments.forEachAttachment(func); +} + Entity::KeyValuePairs SpawnArgs::getKeyValuePairs(const std::string& prefix) const { KeyValuePairs list; diff --git a/radiantcore/entity/SpawnArgs.h b/radiantcore/entity/SpawnArgs.h index a8b9107114..8512bea797 100644 --- a/radiantcore/entity/SpawnArgs.h +++ b/radiantcore/entity/SpawnArgs.h @@ -1,5 +1,7 @@ #pragma once +#include "AttachmentData.h" + #include #include "KeyValue.h" #include @@ -39,6 +41,9 @@ class SpawnArgs: public Entity bool _isContainer; + // Store attachment information + AttachmentData _attachments; + public: // Constructor, pass the according entity class SpawnArgs(const IEntityClassPtr& eclass); @@ -51,27 +56,15 @@ class SpawnArgs: public Entity /* Entity implementation */ void attachObserver(Observer* observer) override; void detachObserver(Observer* observer) override; - void connectUndoSystem(IMapFileChangeTracker& changeTracker); void disconnectUndoSystem(IMapFileChangeTracker& changeTracker); - - /** Return the EntityClass associated with this entity. - */ IEntityClassPtr getEntityClass() const override; - void forEachKeyValue(const KeyValueVisitFunctor& func) const override; void forEachEntityKeyValue(const EntityKeyValueVisitFunctor& visitor) override; - - /** Set a keyvalue on the entity. - */ void setKeyValue(const std::string& key, const std::string& value) override; - - /** Retrieve a keyvalue from the entity. - */ std::string getKeyValue(const std::string& key) const override; - - // Returns true if the given key is inherited bool isInherited(const std::string& key) const override; + void forEachAttachment(AttachmentFunc func) const override; // Get all KeyValues matching the given prefix. KeyValuePairs getKeyValuePairs(const std::string& prefix) const override;