Browse Source

Fix up scene loading. pugixml is a breadth-first parser while irrxml is
a depth first. This only parses scene structure, no mesh loading yet.

dog 2 years ago
parent
commit
3e1fd74940

+ 405 - 389
code/AssetLib/Irr/IRRLoader.cpp

@@ -43,6 +43,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *  @brief Implementation of the Irr importer class
  */
 
+#include "assimp/StringComparison.h"
 #ifndef ASSIMP_BUILD_NO_IRR_IMPORTER
 
 #include "AssetLib/Irr/IRRLoader.h"
@@ -62,7 +63,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/DefaultLogger.hpp>
 #include <assimp/IOSystem.hpp>
 
+#include <csignal>
+#include <iostream>
 #include <memory>
+#include <queue>
+#include <stack>
 
 using namespace Assimp;
 
@@ -835,11 +840,381 @@ void IRRImporter::GenerateGraph(Node *root, aiNode *rootOut, aiScene *scene,
     }
 }
 
+void IRRImporter::ParseNodeAttributes(pugi::xml_node &attributesNode, IRRImporter::Node *nd, BatchLoader &batch) {
+    ai_assert(!ASSIMP_stricmp(attributesNode.name(), "attributes")); // Node must be <attributes>
+    ai_assert(nd != nullptr); // dude
+
+    // Big switch statement that tests for various tags inside <attributes>
+    // and applies them to nd
+    // I don't believe nodes have boolean attributes
+    for (pugi::xml_node &attribute : attributesNode.children()) {
+        if (attribute.type() != pugi::node_element) continue;
+        if (!ASSIMP_stricmp(attribute.name(), "vector3d")) { // <vector3d />
+            VectorProperty prop;
+            ReadVectorProperty(prop, attribute);
+            if (prop.name == "Position") {
+                nd->position = prop.value;
+            } else if (prop.name == "Rotation") {
+                nd->rotation = prop.value;
+            } else if (prop.name == "Scale") {
+                nd->scaling = prop.value;
+            } else if (Node::CAMERA == nd->type) {
+                aiCamera *cam = cameras.back();
+                if (prop.name == "Target") {
+                    cam->mLookAt = prop.value;
+                } else if (prop.name == "UpVector") {
+                    cam->mUp = prop.value;
+                }
+            }
+        } else if (!ASSIMP_stricmp(attribute.name(), "float")) { // <float />
+            FloatProperty prop;
+            ReadFloatProperty(prop, attribute);
+            if (prop.name == "FramesPerSecond" && Node::ANIMMESH == nd->type) {
+                nd->framesPerSecond = prop.value;
+            } else if (Node::CAMERA == nd->type) {
+                /*  This is the vertical, not the horizontal FOV.
+                 *  We need to compute the right FOV from the
+                 *  screen aspect which we don't know yet.
+                 */
+                if (prop.name == "Fovy") {
+                    cameras.back()->mHorizontalFOV = prop.value;
+                } else if (prop.name == "Aspect") {
+                    cameras.back()->mAspect = prop.value;
+                } else if (prop.name == "ZNear") {
+                    cameras.back()->mClipPlaneNear = prop.value;
+                } else if (prop.name == "ZFar") {
+                    cameras.back()->mClipPlaneFar = prop.value;
+                }
+            } else if (Node::LIGHT == nd->type) {
+                /*  Additional light information
+                 */
+                if (prop.name == "Attenuation") {
+                    lights.back()->mAttenuationLinear = prop.value;
+                } else if (prop.name == "OuterCone") {
+                    lights.back()->mAngleOuterCone = AI_DEG_TO_RAD(prop.value);
+                } else if (prop.name == "InnerCone") {
+                    lights.back()->mAngleInnerCone = AI_DEG_TO_RAD(prop.value);
+                }
+            }
+            // radius of the sphere to be generated -
+            // or alternatively, size of the cube
+            else if ((Node::SPHERE == nd->type && prop.name == "Radius") ||
+                     (Node::CUBE == nd->type && prop.name == "Size")) {
+                nd->sphereRadius = prop.value;
+            }
+        } else if (!ASSIMP_stricmp(attribute.name(), "int")) { // <int />
+            // Only sphere nodes make use of integer attributes
+            if (Node::SPHERE == nd->type) {
+                IntProperty prop;
+                ReadIntProperty(prop, attribute);
+                if (prop.name == "PolyCountX") {
+                    nd->spherePolyCountX = prop.value;
+                } else if (prop.name == "PolyCountY") {
+                    nd->spherePolyCountY = prop.value;
+                }
+            }
+        } else if (!ASSIMP_stricmp(attribute.name(), "string") || !ASSIMP_stricmp(attribute.name(), "enum")) { // <string /> or < enum />
+            StringProperty prop;
+            ReadStringProperty(prop, attribute);
+            if (prop.value.length() == 0) continue; // skip empty strings
+            if (prop.name == "Name") {
+                nd->name = prop.value;
+
+                /*  If we're either a camera or a light source
+                 *  we need to update the name in the aiLight/
+                 *  aiCamera structure, too.
+                 */
+                if (Node::CAMERA == nd->type) {
+                    cameras.back()->mName.Set(prop.value);
+                } else if (Node::LIGHT == nd->type) {
+                    lights.back()->mName.Set(prop.value);
+                }
+            } else if (Node::LIGHT == nd->type && "LightType" == prop.name) {
+                if (prop.value == "Spot")
+                    lights.back()->mType = aiLightSource_SPOT;
+                else if (prop.value == "Point")
+                    lights.back()->mType = aiLightSource_POINT;
+                else if (prop.value == "Directional")
+                    lights.back()->mType = aiLightSource_DIRECTIONAL;
+                else {
+                    // We won't pass the validation with aiLightSourceType_UNDEFINED,
+                    // so we remove the light and replace it with a silly dummy node
+                    delete lights.back();
+                    lights.pop_back();
+                    nd->type = Node::DUMMY;
+
+                    ASSIMP_LOG_ERROR("Ignoring light of unknown type: ", prop.value);
+                }
+            } else if ((prop.name == "Mesh" && Node::MESH == nd->type) ||
+                       Node::ANIMMESH == nd->type) {
+                /*  This is the file name of the mesh - either
+                 *  animated or not. We need to make sure we setup
+                 *  the correct post-processing settings here.
+                 */
+                unsigned int pp = 0;
+                BatchLoader::PropertyMap map;
+
+                /* If the mesh is a static one remove all animations from the impor data
+                 */
+                if (Node::ANIMMESH != nd->type) {
+                    pp |= aiProcess_RemoveComponent;
+                    SetGenericProperty<int>(map.ints, AI_CONFIG_PP_RVC_FLAGS,
+                            aiComponent_ANIMATIONS | aiComponent_BONEWEIGHTS);
+                }
+
+                /*  TODO: maybe implement the protection against recursive
+                 *  loading calls directly in BatchLoader? The current
+                 *  implementation is not absolutely safe. A LWS and an IRR
+                 *  file referencing each other *could* cause the system to
+                 *  recurse forever.
+                 */
+
+                const std::string extension = GetExtension(prop.value);
+                if ("irr" == extension) {
+                    ASSIMP_LOG_ERROR("IRR: Can't load another IRR file recursively");
+                } else {
+                    nd->id = batch.AddLoadRequest(prop.value, pp, &map);
+                    nd->meshPath = prop.value;
+                }
+            }
+        }
+    }
+}
+
+void IRRImporter::ParseAnimators(pugi::xml_node &animatorNode, IRRImporter::Node *nd) {
+    Animator *curAnim = nullptr;
+    // Make empty animator
+    nd->animators.emplace_back();
+    curAnim = &nd->animators.back(); // Push it back
+    pugi::xml_node attributes = animatorNode.child("attributes");
+    if (!attributes) {
+        ASSIMP_LOG_WARN("Animator node does not contain attributes. ");
+        return;
+    }
+
+    for (pugi::xml_node attrib : attributes.children()) {
+        // XML may contain useless noes like CDATA
+        if (!ASSIMP_stricmp(attrib.name(), "vector3d")) {
+            VectorProperty prop;
+            ReadVectorProperty(prop, attrib);
+
+            if (curAnim->type == Animator::ROTATION && prop.name == "Rotation") {
+                // We store the rotation euler angles in 'direction'
+                curAnim->direction = prop.value;
+            } else if (curAnim->type == Animator::FOLLOW_SPLINE) {
+                // Check whether the vector follows the PointN naming scheme,
+                // here N is the ONE-based index of the point
+                if (prop.name.length() >= 6 && prop.name.substr(0, 5) == "Point") {
+                    // Add a new key to the list
+                    curAnim->splineKeys.emplace_back();
+                    aiVectorKey &key = curAnim->splineKeys.back();
+
+                    // and parse its properties
+                    key.mValue = prop.value;
+                    key.mTime = strtoul10(&prop.name[5]);
+                }
+            } else if (curAnim->type == Animator::FLY_CIRCLE) {
+                if (prop.name == "Center") {
+                    curAnim->circleCenter = prop.value;
+                } else if (prop.name == "Direction") {
+                    curAnim->direction = prop.value;
+
+                    // From Irrlicht's source - a workaround for backward compatibility with Irrlicht 1.1
+                    if (curAnim->direction == aiVector3D()) {
+                        curAnim->direction = aiVector3D(0.f, 1.f, 0.f);
+                    } else
+                        curAnim->direction.Normalize();
+                }
+            } else if (curAnim->type == Animator::FLY_STRAIGHT) {
+                if (prop.name == "Start") {
+                    // We reuse the field here
+                    curAnim->circleCenter = prop.value;
+                } else if (prop.name == "End") {
+                    // We reuse the field here
+                    curAnim->direction = prop.value;
+                }
+            }
+
+            //} else if (!ASSIMP_stricmp(reader->getNodeName(), "bool")) {
+        } else if (!ASSIMP_stricmp(attrib.name(), "bool")) {
+            BoolProperty prop;
+            ReadBoolProperty(prop, attrib);
+
+            if (curAnim->type == Animator::FLY_CIRCLE && prop.name == "Loop") {
+                curAnim->loop = prop.value;
+            }
+            //} else if (!ASSIMP_stricmp(reader->getNodeName(), "float")) {
+        } else if (!ASSIMP_stricmp(attrib.name(), "float")) {
+            FloatProperty prop;
+            ReadFloatProperty(prop, attrib);
+
+            // The speed property exists for several animators
+            if (prop.name == "Speed") {
+                curAnim->speed = prop.value;
+            } else if (curAnim->type == Animator::FLY_CIRCLE && prop.name == "Radius") {
+                curAnim->circleRadius = prop.value;
+            } else if (curAnim->type == Animator::FOLLOW_SPLINE && prop.name == "Tightness") {
+                curAnim->tightness = prop.value;
+            }
+            //} else if (!ASSIMP_stricmp(reader->getNodeName(), "int")) {
+        } else if (!ASSIMP_stricmp(attrib.name(), "int")) {
+            IntProperty prop;
+            ReadIntProperty(prop, attrib);
+
+            if (curAnim->type == Animator::FLY_STRAIGHT && prop.name == "TimeForWay") {
+                curAnim->timeForWay = prop.value;
+            }
+            //} else if (!ASSIMP_stricmp(reader->getNodeName(), "string") || !ASSIMP_stricmp(reader->getNodeName(), "enum")) {
+        } else if (!ASSIMP_stricmp(attrib.name(), "string") || !ASSIMP_stricmp(attrib.name(), "enum")) {
+            StringProperty prop;
+            ReadStringProperty(prop, attrib);
+
+            if (prop.name == "Type") {
+                // type of the animator
+                if (prop.value == "rotation") {
+                    curAnim->type = Animator::ROTATION;
+                } else if (prop.value == "flyCircle") {
+                    curAnim->type = Animator::FLY_CIRCLE;
+                } else if (prop.value == "flyStraight") {
+                    curAnim->type = Animator::FLY_CIRCLE;
+                } else if (prop.value == "followSpline") {
+                    curAnim->type = Animator::FOLLOW_SPLINE;
+                } else {
+                    ASSIMP_LOG_WARN("IRR: Ignoring unknown animator: ", prop.value);
+
+                    curAnim->type = Animator::UNKNOWN;
+                }
+            }
+        }
+    }
+}
+
+IRRImporter::Node *IRRImporter::ParseNode(pugi::xml_node &node, BatchLoader &batch) {
+    // Parse <node> tags.
+    // <node> tags have various types
+    // <node> tags can contain <attribute>, <material>
+    // they can also contain other <node> tags, (and can reference other files as well?)
+    // ***********************************************************************
+    /*  What we're going to do with the node depends
+     *  on its type:
+     *
+     *  "mesh" - Load a mesh from an external file
+     *  "cube" - Generate a cube
+     *  "skybox" - Generate a skybox
+     *  "light" - A light source
+     *  "sphere" - Generate a sphere mesh
+     *  "animatedMesh" - Load an animated mesh from an external file
+     *    and join its animation channels with ours.
+     *  "empty" - A dummy node
+     *  "camera" - A camera
+     *  "terrain" - a terrain node (data comes from a heightmap)
+     *  "billboard", ""
+     *
+     *  Each of these nodes can be animated and all can have multiple
+     *  materials assigned (except lights, cameras and dummies, of course).
+     *  Said materials and animators are all collected at the bottom
+     */
+    // ***********************************************************************
+    Node *nd;
+    pugi::xml_attribute nodeTypeAttrib = node.attribute("type");
+    if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "mesh") || !ASSIMP_stricmp(nodeTypeAttrib.value(), "octTree")) {
+        // OctTree's and meshes are treated equally
+        nd = new Node(Node::MESH);
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "cube")) {
+        nd = new Node(Node::CUBE);
+        guessedMeshCnt += 1; // Cube is only one mesh
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "skybox")) {
+        nd = new Node(Node::SKYBOX);
+        guessedMeshCnt += 6; // Skybox is a box, with 6 meshes?
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "camera")) {
+        nd = new Node(Node::CAMERA);
+        // Setup a temporary name for the camera
+        aiCamera *cam = new aiCamera();
+        cam->mName.Set(nd->name);
+        cameras.push_back(cam);
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "light")) {
+        nd = new Node(Node::LIGHT);
+        // Setup a temporary name for the light
+        aiLight *cam = new aiLight();
+        cam->mName.Set(nd->name);
+        lights.push_back(cam);
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "sphere")) {
+        nd = new Node(Node::SPHERE);
+        guessedMeshCnt += 1;
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "animatedMesh")) {
+        nd = new Node(Node::ANIMMESH);
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "empty")) {
+        nd = new Node(Node::DUMMY);
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "terrain")) {
+        nd = new Node(Node::TERRAIN);
+    } else if (!ASSIMP_stricmp(nodeTypeAttrib.value(), "billBoard")) {
+        // We don't support billboards, so ignore them
+        ASSIMP_LOG_ERROR("IRR: Billboards are not supported by Assimp");
+        nd = new Node(Node::DUMMY);
+    } else {
+        ASSIMP_LOG_WARN("IRR: Found unknown node: ", nodeTypeAttrib.value());
+
+        /*  We skip the contents of nodes we don't know.
+         *  We parse the transformation and all animators
+         *  and skip the rest.
+         */
+        nd = new Node(Node::DUMMY);
+    }
+
+    // TODO: consolidate all into one loop
+    // Collect node attributes first
+    for (pugi::xml_node attr_node : node.children()) {
+        if (!ASSIMP_stricmp(attr_node.name(), "attributes")) {
+            ParseNodeAttributes(attr_node, nd, batch); // Parse attributes into this node
+        }
+    }
+
+    // Then parse any materials
+    // Materials are available to almost all node types
+    if (nd->type != Node::DUMMY) {
+        for (pugi::xml_node materialNode : node.children()) {
+            if (!ASSIMP_stricmp(materialNode.name(), "materials")) {
+                // Parse material description directly
+                // Each material should contain an <attributes> node
+                // with everything specified
+                nd->materials.emplace_back();
+                std::pair<aiMaterial *, unsigned int> &p = nd->materials.back();
+                p.first = ParseMaterial(materialNode, p.second);
+                guessedMatCnt += 1;
+            }
+        }
+    }
+
+    // Then parse any animators
+    for (pugi::xml_node animatorNode : node.children()) {
+        if (!ASSIMP_stricmp(animatorNode.name(), "animators")) {
+            // All animators should contain an <attributes> tag
+            //  This is an animation path - add a new animator
+            //  to the list.
+            ParseAnimators(animatorNode, nd); // Function modifies nd's animator vector
+            guessedAnimCnt += 1;
+        }
+    }
+    // Then parse any child nodes
+    /* Attach the newly created node to the scene-graph
+     */
+    // curNode = nd;
+    // nd->parent = curParent;
+    // curParent->children.push_back(nd);
+    for (pugi::xml_node child : node.children()) {
+        if (!ASSIMP_stricmp(child.name(), "node")) { // Is a child node
+            Node *childNd = ParseNode(child, batch); // Repeat this function for all children
+            nd->children.push_back(childNd);
+        };
+    }
+
+    return nd;
+}
+
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure.
 void IRRImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
     std::unique_ptr<IOStream> file(pIOHandler->Open(pFile));
-
     // Check whether we can read from the file
     if (file == nullptr) {
         throw DeadlyImportError("Failed to open IRR file ", pFile);
@@ -852,405 +1227,47 @@ void IRRImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
     }
     pugi::xml_node rootElement = st.getRootNode();
 
+    std::stringstream ss;
+    ss << "Document name: " << rootElement.name() << std::endl;
+    ss << "Document content: " << std::endl;
+    rootElement.print(ss);
+    ss << std::endl;
+    std::cout << "IrrImporter with";
+    std::cout << ss.str() << std::endl;
     // The root node of the scene
+    // TODO: Appearantly root node is specified somewhere?
     Node *root = new Node(Node::DUMMY);
     root->parent = nullptr;
     root->name = "<IRRSceneRoot>";
 
-    // Current node parent
-    Node *curParent = root;
-
-    // Scene-graph node we're currently working on
-    Node *curNode = nullptr;
-
-    // List of output cameras
-    std::vector<aiCamera *> cameras;
-
-    // List of output lights
-    std::vector<aiLight *> lights;
-
     // Batch loader used to load external models
     BatchLoader batch(pIOHandler);
     // batch.SetBasePath(pFile);
 
-    cameras.reserve(5);
+    cameras.reserve(1); // Probably only one camera in entire scene
     lights.reserve(5);
 
-    bool inMaterials = false, inAnimator = false;
-    unsigned int guessedAnimCnt = 0, guessedMeshCnt = 0, guessedMatCnt = 0;
-
-    // Parse the XML file
-
-    // while (reader->read())  {
-    for (pugi::xml_node child : rootElement.children())
-        switch (child.type()) {
-        case pugi::node_element:
-            if (!ASSIMP_stricmp(child.name(), "node")) {
-                // ***********************************************************************
-                /*  What we're going to do with the node depends
-                 *  on its type:
-                 *
-                 *  "mesh" - Load a mesh from an external file
-                 *  "cube" - Generate a cube
-                 *  "skybox" - Generate a skybox
-                 *  "light" - A light source
-                 *  "sphere" - Generate a sphere mesh
-                 *  "animatedMesh" - Load an animated mesh from an external file
-                 *    and join its animation channels with ours.
-                 *  "empty" - A dummy node
-                 *  "camera" - A camera
-                 *  "terrain" - a terrain node (data comes from a heightmap)
-                 *  "billboard", ""
-                 *
-                 *  Each of these nodes can be animated and all can have multiple
-                 *  materials assigned (except lights, cameras and dummies, of course).
-                 */
-                // ***********************************************************************
-                // const char *sz = reader->getAttributeValueSafe("type");
-                pugi::xml_attribute attrib = child.attribute("type");
-                Node *nd;
-                if (!ASSIMP_stricmp(attrib.name(), "mesh") || !ASSIMP_stricmp(attrib.name(), "octTree")) {
-                    // OctTree's and meshes are treated equally
-                    nd = new Node(Node::MESH);
-                } else if (!ASSIMP_stricmp(attrib.name(), "cube")) {
-                    nd = new Node(Node::CUBE);
-                    ++guessedMeshCnt;
-                } else if (!ASSIMP_stricmp(attrib.name(), "skybox")) {
-                    nd = new Node(Node::SKYBOX);
-                    guessedMeshCnt += 6;
-                } else if (!ASSIMP_stricmp(attrib.name(), "camera")) {
-                    nd = new Node(Node::CAMERA);
-
-                    // Setup a temporary name for the camera
-                    aiCamera *cam = new aiCamera();
-                    cam->mName.Set(nd->name);
-                    cameras.push_back(cam);
-                } else if (!ASSIMP_stricmp(attrib.name(), "light")) {
-                    nd = new Node(Node::LIGHT);
-
-                    // Setup a temporary name for the light
-                    aiLight *cam = new aiLight();
-                    cam->mName.Set(nd->name);
-                    lights.push_back(cam);
-                } else if (!ASSIMP_stricmp(attrib.name(), "sphere")) {
-                    nd = new Node(Node::SPHERE);
-                    ++guessedMeshCnt;
-                } else if (!ASSIMP_stricmp(attrib.name(), "animatedMesh")) {
-                    nd = new Node(Node::ANIMMESH);
-                } else if (!ASSIMP_stricmp(attrib.name(), "empty")) {
-                    nd = new Node(Node::DUMMY);
-                } else if (!ASSIMP_stricmp(attrib.name(), "terrain")) {
-                    nd = new Node(Node::TERRAIN);
-                } else if (!ASSIMP_stricmp(attrib.name(), "billBoard")) {
-                    // We don't support billboards, so ignore them
-                    ASSIMP_LOG_ERROR("IRR: Billboards are not supported by Assimp");
-                    nd = new Node(Node::DUMMY);
-                } else {
-                    ASSIMP_LOG_WARN("IRR: Found unknown node: ", attrib.name());
-
-                    /*  We skip the contents of nodes we don't know.
-                     *  We parse the transformation and all animators
-                     *  and skip the rest.
-                     */
-                    nd = new Node(Node::DUMMY);
-                }
+    this->guessedAnimCnt = 0;
+    this->guessedMeshCnt = 0;
+    this->guessedMatCnt = 0;
 
-                /* Attach the newly created node to the scene-graph
-                 */
-                curNode = nd;
-                nd->parent = curParent;
-                curParent->children.push_back(nd);
-            } else if (!ASSIMP_stricmp(child.name(), "materials")) {
-                inMaterials = true;
-            } else if (!ASSIMP_stricmp(child.name(), "animators")) {
-                inAnimator = true;
-            } else if (!ASSIMP_stricmp(child.name(), "attributes")) {
-                //  We should have a valid node here
-                //  FIX: no ... the scene root node is also contained in an attributes block
-                if (!curNode) {
-                    continue;
-                }
-
-                Animator *curAnim = nullptr;
-
-                // Materials can occur for nearly any type of node
-                if (inMaterials && curNode->type != Node::DUMMY) {
-                    //  This is a material description - parse it!
-                    curNode->materials.emplace_back();
-                    std::pair<aiMaterial *, unsigned int> &p = curNode->materials.back();
-
-                    p.first = ParseMaterial(p.second);
-                    ++guessedMatCnt;
-                    continue;
-                } else if (inAnimator) {
-                    //  This is an animation path - add a new animator
-                    //  to the list.
-                    curNode->animators.emplace_back();
-                    curAnim = &curNode->animators.back();
-
-                    ++guessedAnimCnt;
-                }
-
-                /*  Parse all elements in the attributes block
-                 *  and process them.
-                 */
-                //					while (reader->read()) {
-                for (pugi::xml_node attrib : child.children()) {
-                    if (attrib.type() == pugi::node_element) {
-                        // if (reader->getNodeType() == EXN_ELEMENT) {
-                        // if (!ASSIMP_stricmp(reader->getNodeName(), "vector3d")) {
-                        if (!ASSIMP_stricmp(attrib.name(), "vector3d")) {
-                            VectorProperty prop;
-                            ReadVectorProperty(prop);
-
-                            if (inAnimator) {
-                                if (curAnim->type == Animator::ROTATION && prop.name == "Rotation") {
-                                    // We store the rotation euler angles in 'direction'
-                                    curAnim->direction = prop.value;
-                                } else if (curAnim->type == Animator::FOLLOW_SPLINE) {
-                                    // Check whether the vector follows the PointN naming scheme,
-                                    // here N is the ONE-based index of the point
-                                    if (prop.name.length() >= 6 && prop.name.substr(0, 5) == "Point") {
-                                        // Add a new key to the list
-                                        curAnim->splineKeys.emplace_back();
-                                        aiVectorKey &key = curAnim->splineKeys.back();
-
-                                        // and parse its properties
-                                        key.mValue = prop.value;
-                                        key.mTime = strtoul10(&prop.name[5]);
-                                    }
-                                } else if (curAnim->type == Animator::FLY_CIRCLE) {
-                                    if (prop.name == "Center") {
-                                        curAnim->circleCenter = prop.value;
-                                    } else if (prop.name == "Direction") {
-                                        curAnim->direction = prop.value;
-
-                                        // From Irrlicht's source - a workaround for backward compatibility with Irrlicht 1.1
-                                        if (curAnim->direction == aiVector3D()) {
-                                            curAnim->direction = aiVector3D(0.f, 1.f, 0.f);
-                                        } else
-                                            curAnim->direction.Normalize();
-                                    }
-                                } else if (curAnim->type == Animator::FLY_STRAIGHT) {
-                                    if (prop.name == "Start") {
-                                        // We reuse the field here
-                                        curAnim->circleCenter = prop.value;
-                                    } else if (prop.name == "End") {
-                                        // We reuse the field here
-                                        curAnim->direction = prop.value;
-                                    }
-                                }
-                            } else {
-                                if (prop.name == "Position") {
-                                    curNode->position = prop.value;
-                                } else if (prop.name == "Rotation") {
-                                    curNode->rotation = prop.value;
-                                } else if (prop.name == "Scale") {
-                                    curNode->scaling = prop.value;
-                                } else if (Node::CAMERA == curNode->type) {
-                                    aiCamera *cam = cameras.back();
-                                    if (prop.name == "Target") {
-                                        cam->mLookAt = prop.value;
-                                    } else if (prop.name == "UpVector") {
-                                        cam->mUp = prop.value;
-                                    }
-                                }
-                            }
-                            //} else if (!ASSIMP_stricmp(reader->getNodeName(), "bool")) {
-                        } else if (!ASSIMP_stricmp(attrib.name(), "bool")) {
-                            BoolProperty prop;
-                            ReadBoolProperty(prop);
-
-                            if (inAnimator && curAnim->type == Animator::FLY_CIRCLE && prop.name == "Loop") {
-                                curAnim->loop = prop.value;
-                            }
-                            //} else if (!ASSIMP_stricmp(reader->getNodeName(), "float")) {
-                        } else if (!ASSIMP_stricmp(attrib.name(), "float")) {
-                            FloatProperty prop;
-                            ReadFloatProperty(prop);
-
-                            if (inAnimator) {
-                                // The speed property exists for several animators
-                                if (prop.name == "Speed") {
-                                    curAnim->speed = prop.value;
-                                } else if (curAnim->type == Animator::FLY_CIRCLE && prop.name == "Radius") {
-                                    curAnim->circleRadius = prop.value;
-                                } else if (curAnim->type == Animator::FOLLOW_SPLINE && prop.name == "Tightness") {
-                                    curAnim->tightness = prop.value;
-                                }
-                            } else {
-                                if (prop.name == "FramesPerSecond" && Node::ANIMMESH == curNode->type) {
-                                    curNode->framesPerSecond = prop.value;
-                                } else if (Node::CAMERA == curNode->type) {
-                                    /*  This is the vertical, not the horizontal FOV.
-                                     *  We need to compute the right FOV from the
-                                     *  screen aspect which we don't know yet.
-                                     */
-                                    if (prop.name == "Fovy") {
-                                        cameras.back()->mHorizontalFOV = prop.value;
-                                    } else if (prop.name == "Aspect") {
-                                        cameras.back()->mAspect = prop.value;
-                                    } else if (prop.name == "ZNear") {
-                                        cameras.back()->mClipPlaneNear = prop.value;
-                                    } else if (prop.name == "ZFar") {
-                                        cameras.back()->mClipPlaneFar = prop.value;
-                                    }
-                                } else if (Node::LIGHT == curNode->type) {
-                                    /*  Additional light information
-                                     */
-                                    if (prop.name == "Attenuation") {
-                                        lights.back()->mAttenuationLinear = prop.value;
-                                    } else if (prop.name == "OuterCone") {
-                                        lights.back()->mAngleOuterCone = AI_DEG_TO_RAD(prop.value);
-                                    } else if (prop.name == "InnerCone") {
-                                        lights.back()->mAngleInnerCone = AI_DEG_TO_RAD(prop.value);
-                                    }
-                                }
-                                // radius of the sphere to be generated -
-                                // or alternatively, size of the cube
-                                else if ((Node::SPHERE == curNode->type && prop.name == "Radius") || (Node::CUBE == curNode->type && prop.name == "Size")) {
-
-                                    curNode->sphereRadius = prop.value;
-                                }
-                            }
-                            //} else if (!ASSIMP_stricmp(reader->getNodeName(), "int")) {
-                        } else if (!ASSIMP_stricmp(attrib.name(), "int")) {
-                            IntProperty prop;
-                            ReadIntProperty(prop);
-
-                            if (inAnimator) {
-                                if (curAnim->type == Animator::FLY_STRAIGHT && prop.name == "TimeForWay") {
-                                    curAnim->timeForWay = prop.value;
-                                }
-                            } else {
-                                // sphere polygon numbers in each direction
-                                if (Node::SPHERE == curNode->type) {
-
-                                    if (prop.name == "PolyCountX") {
-                                        curNode->spherePolyCountX = prop.value;
-                                    } else if (prop.name == "PolyCountY") {
-                                        curNode->spherePolyCountY = prop.value;
-                                    }
-                                }
-                            }
-                            //} else if (!ASSIMP_stricmp(reader->getNodeName(), "string") || !ASSIMP_stricmp(reader->getNodeName(), "enum")) {
-                        } else if (!ASSIMP_stricmp(attrib.name(), "string") || !ASSIMP_stricmp(attrib.name(), "enum")) {
-                            StringProperty prop;
-                            ReadStringProperty(prop);
-                            if (prop.value.length()) {
-                                if (prop.name == "Name") {
-                                    curNode->name = prop.value;
-
-                                    /*  If we're either a camera or a light source
-                                     *  we need to update the name in the aiLight/
-                                     *  aiCamera structure, too.
-                                     */
-                                    if (Node::CAMERA == curNode->type) {
-                                        cameras.back()->mName.Set(prop.value);
-                                    } else if (Node::LIGHT == curNode->type) {
-                                        lights.back()->mName.Set(prop.value);
-                                    }
-                                } else if (Node::LIGHT == curNode->type && "LightType" == prop.name) {
-                                    if (prop.value == "Spot")
-                                        lights.back()->mType = aiLightSource_SPOT;
-                                    else if (prop.value == "Point")
-                                        lights.back()->mType = aiLightSource_POINT;
-                                    else if (prop.value == "Directional")
-                                        lights.back()->mType = aiLightSource_DIRECTIONAL;
-                                    else {
-                                        // We won't pass the validation with aiLightSourceType_UNDEFINED,
-                                        // so we remove the light and replace it with a silly dummy node
-                                        delete lights.back();
-                                        lights.pop_back();
-                                        curNode->type = Node::DUMMY;
-
-                                        ASSIMP_LOG_ERROR("Ignoring light of unknown type: ", prop.value);
-                                    }
-                                } else if ((prop.name == "Mesh" && Node::MESH == curNode->type) ||
-                                           Node::ANIMMESH == curNode->type) {
-                                    /*  This is the file name of the mesh - either
-                                     *  animated or not. We need to make sure we setup
-                                     *  the correct post-processing settings here.
-                                     */
-                                    unsigned int pp = 0;
-                                    BatchLoader::PropertyMap map;
-
-                                    /* If the mesh is a static one remove all animations from the impor data
-                                     */
-                                    if (Node::ANIMMESH != curNode->type) {
-                                        pp |= aiProcess_RemoveComponent;
-                                        SetGenericProperty<int>(map.ints, AI_CONFIG_PP_RVC_FLAGS,
-                                                aiComponent_ANIMATIONS | aiComponent_BONEWEIGHTS);
-                                    }
-
-                                    /*  TODO: maybe implement the protection against recursive
-                                     *  loading calls directly in BatchLoader? The current
-                                     *  implementation is not absolutely safe. A LWS and an IRR
-                                     *  file referencing each other *could* cause the system to
-                                     *  recurse forever.
-                                     */
-
-                                    const std::string extension = GetExtension(prop.value);
-                                    if ("irr" == extension) {
-                                        ASSIMP_LOG_ERROR("IRR: Can't load another IRR file recursively");
-                                    } else {
-                                        curNode->id = batch.AddLoadRequest(prop.value, pp, &map);
-                                        curNode->meshPath = prop.value;
-                                    }
-                                } else if (inAnimator && prop.name == "Type") {
-                                    // type of the animator
-                                    if (prop.value == "rotation") {
-                                        curAnim->type = Animator::ROTATION;
-                                    } else if (prop.value == "flyCircle") {
-                                        curAnim->type = Animator::FLY_CIRCLE;
-                                    } else if (prop.value == "flyStraight") {
-                                        curAnim->type = Animator::FLY_CIRCLE;
-                                    } else if (prop.value == "followSpline") {
-                                        curAnim->type = Animator::FOLLOW_SPLINE;
-                                    } else {
-                                        ASSIMP_LOG_WARN("IRR: Ignoring unknown animator: ", prop.value);
-
-                                        curAnim->type = Animator::UNKNOWN;
-                                    }
-                                }
-                            }
-                        }
-                        //} else if (reader->getNodeType() == EXN_ELEMENT_END && !ASSIMP_stricmp(reader->getNodeName(), "attributes")) {
-                    } else if (attrib.type() == pugi::node_null && !ASSIMP_stricmp(attrib.name(), "attributes")) {
-                        break;
-                    }
-                }
-            }
-            break;
-
-            /*case EXN_ELEMENT_END:
-
-            // If we reached the end of a node, we need to continue processing its parent
-            if (!ASSIMP_stricmp(reader->getNodeName(), "node")) {
-                if (!curNode) {
-                    // currently is no node set. We need to go
-                    // back in the node hierarchy
-                    if (!curParent) {
-                        curParent = root;
-                        ASSIMP_LOG_ERROR("IRR: Too many closing <node> elements");
-                    } else
-                        curParent = curParent->parent;
-                } else
-                    curNode = nullptr;
-            }
-            // clear all flags
-            else if (!ASSIMP_stricmp(reader->getNodeName(), "materials")) {
-                inMaterials = false;
-            } else if (!ASSIMP_stricmp(reader->getNodeName(), "animators")) {
-                inAnimator = false;
-            }
-            break;*/
-
-        default:
-            // GCC complains that not all enumeration values are handled
-            break;
+    // Parse the XML
+    // First node is the xml header. Awkwardly skip to sibling's children
+    // I don't like recursion
+    std::vector<pugi::xml_node> nextNodes;
+    for (auto &node : rootElement.children().begin()->next_sibling().children()) {
+        nextNodes.push_back(node); // Find second node, <irr_scene>, and push it's children to queue
+    }
+    for (pugi::xml_node &child : nextNodes) {
+        if (child.type() != pugi::node_element) continue; // Only semantically valuable nodes
+        // XML elements are either nodes, animators, attributes, or materials
+        if (!ASSIMP_stricmp(child.name(), "node")) {
+            // Recursive ollect subtree children
+            Node *nd = ParseNode(child, batch);
+            // Attach to root
+            root->children.push_back(nd);
         }
-    //}
+    }
 
     //  Now iterate through all cameras and compute their final (horizontal) FOV
     for (aiCamera *cam : cameras) {
@@ -1336,8 +1353,7 @@ void IRRImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
     //  Now merge all sub scenes and attach them to the correct
     //  attachment points in the scenegraph.
     SceneCombiner::MergeScenes(&pScene, tempScene, attach,
-            AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | (!configSpeedFlag ? (
-                                                                              AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY | AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) :
+            AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | (!configSpeedFlag ? (AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY | AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) :
                                                                       0));
 
     // If we have no meshes | no materials now set the INCOMPLETE

+ 24 - 0
code/AssetLib/Irr/IRRLoader.h

@@ -207,6 +207,24 @@ private:
         aiVector3D position, normal, uv;
     };
 
+    // -------------------------------------------------------------------
+    // Parse <node> tag from XML file and extract child node
+    // @param node XML node
+    // @param guessedMeshesContained number of extra guessed meshes
+    IRRImporter::Node *ParseNode(pugi::xml_node &node, BatchLoader& batch);
+
+    // -------------------------------------------------------------------
+    // Parse <attributes> tags within <node> tags and apply to scene node
+    // @param attributeNode XML child node
+    // @param nd Attributed scene node
+    void ParseNodeAttributes(pugi::xml_node &attributeNode, IRRImporter::Node *nd, BatchLoader& batch);
+
+    // -------------------------------------------------------------------
+    // Parse an <animator> node and attach an animator to a node
+    // @param animatorNode XML animator node
+    // @param nd Animated scene node
+    void ParseAnimators(pugi::xml_node &animatorNode, IRRImporter::Node *nd);
+
     // -------------------------------------------------------------------
     /// Fill the scene-graph recursively
     void GenerateGraph(Node *root, aiNode *rootOut, aiScene *scene,
@@ -261,6 +279,12 @@ private:
 
     /// Configuration option: speed flag was set?
     bool configSpeedFlag;
+
+    std::vector<aiCamera*> cameras;
+    std::vector<aiLight*> lights;
+    unsigned int guessedMeshCnt;
+    unsigned int guessedMatCnt;
+    unsigned int guessedAnimCnt;
 };
 
 } // end of namespace Assimp

+ 5 - 1
code/AssetLib/Irr/IRRMeshLoader.cpp

@@ -176,7 +176,7 @@ void IRRMeshImporter::InternReadFile(const std::string &pFile,
                     ASSIMP_LOG_WARN("IRRMESH: Only one material description per buffer, please");
                     releaseMaterial(&curMat);
                 }
-                curMat = ParseMaterial(curMatFlags);
+                // curMat = ParseMaterial(curMatFlags);
             }
             /* no else here! */ if (!ASSIMP_stricmp(child.name(), "vertices")) {
                 pugi::xml_attribute attr = child.attribute("vertexCount");
@@ -495,4 +495,8 @@ void IRRMeshImporter::InternReadFile(const std::string &pFile,
     }
 }
 
+void IRRMeshImporter::ParseMaterialBuffer(pugi::xml_node& bufferNode) {
+
+}
+
 #endif // !! ASSIMP_BUILD_NO_IRRMESH_IMPORTER

+ 2 - 0
code/AssetLib/Irr/IRRMeshLoader.h

@@ -85,6 +85,8 @@ protected:
      */
     void InternReadFile(const std::string &pFile, aiScene *pScene,
             IOSystem *pIOHandler) override;
+ private:
+    void ParseMaterialBuffer(pugi::xml_node& bufferNode);
 };
 
 } // end of namespace Assimp

+ 22 - 22
code/AssetLib/Irr/IRRShared.cpp

@@ -63,34 +63,34 @@ const aiMatrix4x4 Assimp::AI_TO_IRR_MATRIX = aiMatrix4x4(
 
 // ------------------------------------------------------------------------------------------------
 // read a property in hexadecimal format (i.e. ffffffff)
-void IrrlichtBase::ReadHexProperty(HexProperty &out) {
-    for (pugi::xml_attribute attrib : mNode->attributes()) {
+void IrrlichtBase::ReadHexProperty(HexProperty &out, pugi::xml_node& hexnode) {
+    for (pugi::xml_attribute attrib : hexnode.attributes()) {
         if (!ASSIMP_stricmp(attrib.name(), "name")) {
             out.name = std::string(attrib.value());
         } else if (!ASSIMP_stricmp(attrib.name(), "value")) {
             // parse the hexadecimal value
-            out.value = strtoul16(attrib.name());
+            out.value = strtoul16(attrib.value());
         }
     }
 }
 
 // ------------------------------------------------------------------------------------------------
 // read a decimal property
-void IrrlichtBase::ReadIntProperty(IntProperty &out) {
-    for (pugi::xml_attribute attrib : mNode->attributes()) {
+void IrrlichtBase::ReadIntProperty(IntProperty &out, pugi::xml_node& intnode) {
+    for (pugi::xml_attribute attrib : intnode.attributes()) {
         if (!ASSIMP_stricmp(attrib.name(), "name")) {
             out.name = std::string(attrib.value());
-        } else if (!ASSIMP_stricmp(attrib.value(), "value")) {
+        } else if (!ASSIMP_stricmp(attrib.name(), "value")) {
             // parse the int value
-            out.value = strtol10(attrib.name());
+            out.value = strtol10(attrib.value());
         }
     }
 }
 
 // ------------------------------------------------------------------------------------------------
 // read a string property
-void IrrlichtBase::ReadStringProperty(StringProperty &out) {
-    for (pugi::xml_attribute attrib : mNode->attributes()) {
+void IrrlichtBase::ReadStringProperty(StringProperty &out, pugi::xml_node& stringnode) {
+    for (pugi::xml_attribute attrib : stringnode.attributes()) {
         if (!ASSIMP_stricmp(attrib.name(), "name")) {
             out.name = std::string(attrib.value());
         } else if (!ASSIMP_stricmp(attrib.name(), "value")) {
@@ -102,8 +102,8 @@ void IrrlichtBase::ReadStringProperty(StringProperty &out) {
 
 // ------------------------------------------------------------------------------------------------
 // read a boolean property
-void IrrlichtBase::ReadBoolProperty(BoolProperty &out) {
-    for (pugi::xml_attribute attrib : mNode->attributes()) {
+void IrrlichtBase::ReadBoolProperty(BoolProperty &out, pugi::xml_node& boolnode) {
+    for (pugi::xml_attribute attrib : boolnode.attributes()) {
         if (!ASSIMP_stricmp(attrib.name(), "name")) {
             out.name = std::string(attrib.value());
         } else if (!ASSIMP_stricmp(attrib.name(), "value")) {
@@ -115,8 +115,8 @@ void IrrlichtBase::ReadBoolProperty(BoolProperty &out) {
 
 // ------------------------------------------------------------------------------------------------
 // read a float property
-void IrrlichtBase::ReadFloatProperty(FloatProperty &out) {
-    for (pugi::xml_attribute attrib : mNode->attributes()) {
+void IrrlichtBase::ReadFloatProperty(FloatProperty &out, pugi::xml_node &floatnode) {
+    for (pugi::xml_attribute attrib : floatnode.attributes()) {
         if (!ASSIMP_stricmp(attrib.name(), "name")) {
             out.name = std::string(attrib.value());
         } else if (!ASSIMP_stricmp(attrib.name(), "value")) {
@@ -128,8 +128,8 @@ void IrrlichtBase::ReadFloatProperty(FloatProperty &out) {
 
 // ------------------------------------------------------------------------------------------------
 // read a vector property
-void IrrlichtBase::ReadVectorProperty(VectorProperty &out) {
-    for (pugi::xml_attribute attrib : mNode->attributes()) {
+void IrrlichtBase::ReadVectorProperty(VectorProperty &out, pugi::xml_node& vectornode) {
+    for (pugi::xml_attribute attrib : vectornode.attributes()) {
         if (!ASSIMP_stricmp(attrib.name(), "name")) {
             out.name = std::string(attrib.value());
         } else if (!ASSIMP_stricmp(attrib.name(), "value")) {
@@ -170,7 +170,7 @@ int ConvertMappingMode(const std::string &mode) {
 
 // ------------------------------------------------------------------------------------------------
 // Parse a material from the XML file
-aiMaterial *IrrlichtBase::ParseMaterial(unsigned int &matFlags) {
+aiMaterial *IrrlichtBase::ParseMaterial(pugi::xml_node& materialNode, unsigned int &matFlags) {
     aiMaterial *mat = new aiMaterial();
     aiColor4D clr;
     aiString s;
@@ -179,10 +179,10 @@ aiMaterial *IrrlichtBase::ParseMaterial(unsigned int &matFlags) {
     int cnt = 0; // number of used texture channels
     unsigned int nd = 0;
 
-    for (pugi::xml_node child : mNode->children()) {
+    for (pugi::xml_node child : materialNode.children()) {
         if (!ASSIMP_stricmp(child.name(), "color")) { // Hex properties
             HexProperty prop;
-            ReadHexProperty(prop);
+            ReadHexProperty(prop, child);
             if (prop.name == "Diffuse") {
                 ColorFromARGBPacked(prop.value, clr);
                 mat->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE);
@@ -206,13 +206,13 @@ aiMaterial *IrrlichtBase::ParseMaterial(unsigned int &matFlags) {
 #endif
         } else if (!ASSIMP_stricmp(child.name(), "float")) { // Float properties
             FloatProperty prop;
-            ReadFloatProperty(prop);
+            ReadFloatProperty(prop, child);
             if (prop.name == "Shininess") {
                 mat->AddProperty(&prop.value, 1, AI_MATKEY_SHININESS);
             }
         } else if (!ASSIMP_stricmp(child.name(), "bool")) { // Bool properties
             BoolProperty prop;
-            ReadBoolProperty(prop);
+            ReadBoolProperty(prop, child);
             if (prop.name == "Wireframe") {
                 int val = (prop.value ? true : false);
                 mat->AddProperty(&val, 1, AI_MATKEY_ENABLE_WIREFRAME);
@@ -226,7 +226,7 @@ aiMaterial *IrrlichtBase::ParseMaterial(unsigned int &matFlags) {
         } else if (!ASSIMP_stricmp(child.name(), "texture") ||
                    !ASSIMP_stricmp(child.name(), "enum")) { // String properties - textures and texture related properties
             StringProperty prop;
-            ReadStringProperty(prop);
+            ReadStringProperty(prop, child);
             if (prop.value.length()) {
                 // material type (shader)
                 if (prop.name == "Type") {
@@ -379,7 +379,7 @@ aiMaterial *IrrlichtBase::ParseMaterial(unsigned int &matFlags) {
         }
     }*/
     }
-    ASSIMP_LOG_ERROR("IRRMESH: Unexpected end of file. Material is not complete");
+    //ASSIMP_LOG_ERROR("IRRMESH: Unexpected end of file. Material is not complete");
 
     return mat;
 }

+ 9 - 10
code/AssetLib/Irr/IRRShared.h

@@ -58,8 +58,7 @@ extern const aiMatrix4x4 AI_TO_IRR_MATRIX;
  */
 class IrrlichtBase {
 protected:
-    IrrlichtBase() :
-            mNode(nullptr) {
+    IrrlichtBase() {
         // empty
     }
 
@@ -82,25 +81,25 @@ protected:
 
     /// XML reader instance
     XmlParser mParser;
-    pugi::xml_node *mNode;
 
     // -------------------------------------------------------------------
     /** Parse a material description from the XML
      *  @return The created material
      *  @param matFlags Receives AI_IRRMESH_MAT_XX flags
      */
-    aiMaterial *ParseMaterial(unsigned int &matFlags);
+    aiMaterial *ParseMaterial(pugi::xml_node &materialNode, unsigned int &matFlags);
 
     // -------------------------------------------------------------------
     /** Read a property of the specified type from the current XML element.
      *  @param out Receives output data
+     *  @param node XML attribute element data
      */
-    void ReadHexProperty(HexProperty &out);
-    void ReadStringProperty(StringProperty &out);
-    void ReadBoolProperty(BoolProperty &out);
-    void ReadFloatProperty(FloatProperty &out);
-    void ReadVectorProperty(VectorProperty &out);
-    void ReadIntProperty(IntProperty &out);
+    void ReadHexProperty(HexProperty &out, pugi::xml_node& hexnode);
+    void ReadStringProperty(StringProperty &out, pugi::xml_node& stringnode);
+    void ReadBoolProperty(BoolProperty &out, pugi::xml_node& boolnode);
+    void ReadFloatProperty(FloatProperty &out, pugi::xml_node& floatnode);
+    void ReadVectorProperty(VectorProperty &out, pugi::xml_node& vectornode);
+    void ReadIntProperty(IntProperty &out, pugi::xml_node& intnode);
 };
 
 // ------------------------------------------------------------------------------------------------