diff --git a/assets/blender/edited-normal-female.blend b/assets/blender/edited-normal-female.blend index d5d8f56..93b739e 100644 --- a/assets/blender/edited-normal-female.blend +++ b/assets/blender/edited-normal-female.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f514e33a12de8bc54ade17ec9c790ea63810c7224d80c1f466f612a4a5737e78 -size 15796919 +oid sha256:e3ffa6224a7bb323fe36038de5c4539c5a305c5b1bc0a9cf4d605a3504f0fcc3 +size 16289842 diff --git a/assets/blender/edited-normal-male.blend b/assets/blender/edited-normal-male.blend index 92aad87..e4fb9c7 100644 --- a/assets/blender/edited-normal-male.blend +++ b/assets/blender/edited-normal-male.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6014b658f78ff6fe4364cdfb8e441c4008ce84beaa7ea46699f5093d90d02959 -size 11630206 +oid sha256:49e1ea4ee70f3e37347c4301cb9aaf794597192555b5d5feb835bf8d3504b70f +size 12063340 diff --git a/src/sceneloader/CMakeLists.txt b/src/sceneloader/CMakeLists.txt new file mode 100644 index 0000000..5567b7b --- /dev/null +++ b/src/sceneloader/CMakeLists.txt @@ -0,0 +1,6 @@ +project(sceneloader) +add_library(sceneloader loader.cpp) +target_include_directories(sceneloader PUBLIC .) +target_link_libraries(sceneloader PUBLIC OgreMain PRIVATE pugixml) +target_link_libraries(sceneloader PUBLIC OgreTerrain) + diff --git a/src/sceneloader/loader.cpp b/src/sceneloader/loader.cpp new file mode 100644 index 0000000..901a70a --- /dev/null +++ b/src/sceneloader/loader.cpp @@ -0,0 +1,1245 @@ +#include +#include +#include +#include +#include +#include "loader.h" + +using namespace Ogre; +#if 0 +namespace Ogre { + class TerrainGroup {}; // appease unique_ptr destructor +} +#endif +static String getAttrib(const pugi::xml_node &XMLNode, const String &attrib, + const String &defaultValue = "") +{ + if (auto anode = XMLNode.attribute(attrib.c_str())) + return anode.value(); + else + return defaultValue; +} + +static Real getAttribReal(const pugi::xml_node &XMLNode, const String &attrib, + Real defaultValue = 0) +{ + if (auto anode = XMLNode.attribute(attrib.c_str())) + return StringConverter::parseReal(anode.value()); + else + return defaultValue; +} + +static bool getAttribBool(const pugi::xml_node &XMLNode, const String &attrib, + bool defaultValue = false) +{ + if (auto anode = XMLNode.attribute(attrib.c_str())) + return anode.as_bool(); + else + return defaultValue; + + return false; +} + +static Vector3 parseVector3(const pugi::xml_node &XMLNode) +{ + return Vector3( + StringConverter::parseReal(XMLNode.attribute("x").value()), + StringConverter::parseReal(XMLNode.attribute("y").value()), + StringConverter::parseReal(XMLNode.attribute("z").value())); +} + +static Quaternion parseQuaternion(const pugi::xml_node &XMLNode) +{ + //! @todo Fix this crap! + + Quaternion orientation; + + if (XMLNode.attribute("qw")) { + orientation.w = StringConverter::parseReal( + XMLNode.attribute("qw").value()); + orientation.x = StringConverter::parseReal( + XMLNode.attribute("qx").value()); + orientation.y = StringConverter::parseReal( + XMLNode.attribute("qy").value()); + orientation.z = StringConverter::parseReal( + XMLNode.attribute("qz").value()); + } else if (XMLNode.attribute("axisX")) { + Vector3 axis; + axis.x = StringConverter::parseReal( + XMLNode.attribute("axisX").value()); + axis.y = StringConverter::parseReal( + XMLNode.attribute("axisY").value()); + axis.z = StringConverter::parseReal( + XMLNode.attribute("axisZ").value()); + Real angle = StringConverter::parseReal( + XMLNode.attribute("angle").value()); + + orientation.FromAngleAxis(Radian(angle), axis); + } else if (XMLNode.attribute("angleX")) { + Matrix3 rot; + rot.FromEulerAnglesXYZ( + StringConverter::parseAngle( + XMLNode.attribute("angleX").value()), + StringConverter::parseAngle( + XMLNode.attribute("angleY").value()), + StringConverter::parseAngle( + XMLNode.attribute("angleZ").value())); + orientation.FromRotationMatrix(rot); + } else if (XMLNode.attribute("x")) { + orientation.x = StringConverter::parseReal( + XMLNode.attribute("x").value()); + orientation.y = StringConverter::parseReal( + XMLNode.attribute("y").value()); + orientation.z = StringConverter::parseReal( + XMLNode.attribute("z").value()); + orientation.w = StringConverter::parseReal( + XMLNode.attribute("w").value()); + } else if (XMLNode.attribute("w")) { + orientation.w = StringConverter::parseReal( + XMLNode.attribute("w").value()); + orientation.x = StringConverter::parseReal( + XMLNode.attribute("x").value()); + orientation.y = StringConverter::parseReal( + XMLNode.attribute("y").value()); + orientation.z = StringConverter::parseReal( + XMLNode.attribute("z").value()); + } + + return orientation; +} + +static ColourValue parseColour(pugi::xml_node &XMLNode) +{ + return ColourValue( + StringConverter::parseReal(XMLNode.attribute("r").value()), + StringConverter::parseReal(XMLNode.attribute("g").value()), + StringConverter::parseReal(XMLNode.attribute("b").value()), + XMLNode.attribute("a") != NULL ? + StringConverter::parseReal( + XMLNode.attribute("a").value()) : + 1); +} + +struct SceneCodec : public Codec { + String magicNumberToFileExt(const char *magicNumberPtr, + size_t maxbytes) const override + { + return ""; + } + String getType() const override + { + return "scene"; + } + void decode(const DataStreamPtr &stream, + const Any &output) const override + { + DataStreamPtr _stream(stream); + SceneLoader loader; + loader.load(_stream, + ResourceGroupManager::getSingleton() + .getWorldResourceGroupName(), + any_cast(output), 0); + } + + void encodeToFile(const Any &input, + const String &outFileName) const override + { + SceneLoader loader; + loader.exportScene(any_cast(input), outFileName); + } +}; + +SceneLoader::SceneLoader() + : mSceneMgr(0) + , mBackgroundColour(ColourValue::Black) +{ +} + +SceneLoader::~SceneLoader() +{ +} + +void SceneLoader::load(DataStreamPtr &stream, const String &groupName, + SceneNode *rootNode, int counter) +{ + m_sGroupName = groupName; + mSceneMgr = rootNode->getCreator(); + mNamePrefix = rootNode->getName(); + this->counter = counter; + if (counter >= 0) { + if (mNamePrefix.length() > 0) + mNamePrefix += "/"; + mNamePrefix += std::to_string(counter); + } + if (mNamePrefix.length() > 0) + mNamePrefix += "/"; + + pugi::xml_document XMLDoc; // character type defaults to char + + auto result = XMLDoc.load_buffer(stream->getAsString().c_str(), + stream->size()); + if (!result) { + LogManager::getSingleton().logError( + "SceneLoader - " + String(result.description())); + return; + } + + // Grab the scene node + auto XMLRoot = XMLDoc.child("scene"); + + // Validate the File + if (!XMLRoot.attribute("formatVersion")) { + LogManager::getSingleton().logError( + "SceneLoader - Invalid .scene File. Missing "); + return; + } + + // figure out where to attach any nodes we create + mAttachNode = rootNode; + + // Process the scene + processScene(XMLRoot); +} + +void SceneLoader::processScene(pugi::xml_node &XMLRoot) +{ + // Process the scene parameters + String version = getAttrib(XMLRoot, "formatVersion", "unknown"); + + String message = + "[SceneLoader] Parsing dotScene file with version " + version; + if (XMLRoot.attribute("sceneManager")) + message += ", scene manager " + + String(XMLRoot.attribute("sceneManager").value()); + if (XMLRoot.attribute("minOgreVersion")) + message += ", min. Ogre version " + + String(XMLRoot.attribute("minOgreVersion").value()); + if (XMLRoot.attribute("author")) + message += ", author " + + String(XMLRoot.attribute("author").value()); + + LogManager::getSingleton().logMessage(message); + + // Process environment (?) + if (auto pElement = XMLRoot.child("environment")) + processEnvironment(pElement); + + // Process nodes (?) + if (auto pElement = XMLRoot.child("nodes")) + processNodes(pElement); + + // Process externals (?) + if (auto pElement = XMLRoot.child("externals")) + processExternals(pElement); + + // Process userDataReference (?) + if (auto pElement = XMLRoot.child("userData")) + processUserData(pElement, mAttachNode->getUserObjectBindings()); + + // Process light (?) + if (auto pElement = XMLRoot.child("light")) + processLight(pElement); + + // Process camera (?) + if (auto pElement = XMLRoot.child("camera")) + processCamera(pElement); + + // Process terrain (?) + if (auto pElement = XMLRoot.child("terrainGroup")) + processTerrainGroup(pElement); +} + +void SceneLoader::processNodes(pugi::xml_node &XMLNode) +{ + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Nodes...", LML_TRIVIAL); + + // Process node (*) + for (auto pElement : XMLNode.children("node")) { + processNode(pElement); + } + + // Process position (?) + if (auto pElement = XMLNode.child("position")) { + mAttachNode->setPosition(parseVector3(pElement)); + } + + // Process rotation (?) + if (auto pElement = XMLNode.child("rotation")) { + mAttachNode->setOrientation(parseQuaternion(pElement)); + } + + // Process scale (?) + if (auto pElement = XMLNode.child("scale")) { + mAttachNode->setScale(parseVector3(pElement)); + } +} + +void SceneLoader::processExternals(pugi::xml_node &XMLNode) +{ + //! @todo Implement this +} + +void SceneLoader::processEnvironment(pugi::xml_node &XMLNode) +{ + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Environment...", LML_TRIVIAL); + + // Process camera (?) + if (auto pElement = XMLNode.child("camera")) + processCamera(pElement); + + // Process fog (?) + if (auto pElement = XMLNode.child("fog")) + processFog(pElement); + + // Process skyBox (?) + if (auto pElement = XMLNode.child("skyBox")) + processSkyBox(pElement); + + // Process skyDome (?) + if (auto pElement = XMLNode.child("skyDome")) + processSkyDome(pElement); + + // Process skyPlane (?) + if (auto pElement = XMLNode.child("skyPlane")) + processSkyPlane(pElement); + + // Process colourAmbient (?) + if (auto pElement = XMLNode.child("colourAmbient")) + mSceneMgr->setAmbientLight(parseColour(pElement)); + + // Process colourBackground (?) + if (auto pElement = XMLNode.child("colourBackground")) + mBackgroundColour = parseColour(pElement); +} + +void SceneLoader::processTerrainGroup(pugi::xml_node &XMLNode) +{ + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Terrain Group...", LML_TRIVIAL); + + Real worldSize = getAttribReal(XMLNode, "worldSize"); + int mapSize = + StringConverter::parseInt(XMLNode.attribute("size").value()); + int compositeMapDistance = StringConverter::parseInt( + XMLNode.attribute("tuningCompositeMapDistance").value()); + int maxPixelError = StringConverter::parseInt( + XMLNode.attribute("tuningMaxPixelError").value()); + + auto terrainGlobalOptions = TerrainGlobalOptions::getSingletonPtr(); + OgreAssert(terrainGlobalOptions, "TerrainGlobalOptions not available"); + + terrainGlobalOptions->setMaxPixelError((Real)maxPixelError); + terrainGlobalOptions->setCompositeMapDistance( + (Real)compositeMapDistance); + + auto terrainGroup = std::make_shared( + mSceneMgr, Terrain::ALIGN_X_Z, mapSize, worldSize); + terrainGroup->setOrigin(Vector3::ZERO); + terrainGroup->setResourceGroup(m_sGroupName); + + // Process terrain pages (*) + for (auto pPageElement : XMLNode.children("terrain")) { + int pageX = StringConverter::parseInt( + pPageElement.attribute("x").value()); + int pageY = StringConverter::parseInt( + pPageElement.attribute("y").value()); + + terrainGroup->defineTerrain( + pageX, pageY, + pPageElement.attribute("dataFile").value()); + } + terrainGroup->loadAllTerrains(true); + + terrainGroup->freeTemporaryResources(); + + mAttachNode->getUserObjectBindings().setUserAny("TerrainGroup", + terrainGroup); +} + +void SceneLoader::processLight(pugi::xml_node &XMLNode, SceneNode *pParent) +{ + // Process attributes + String name = getAttrib(XMLNode, "name"); + + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Light: " + name, LML_TRIVIAL); + + // Create the light + Light *pLight = mSceneMgr->createLight(name); + if (pParent) + pParent->attachObject(pLight); + + String sValue = getAttrib(XMLNode, "type"); + if (sValue == "point") + pLight->setType(Light::LT_POINT); + else if (sValue == "directional") + pLight->setType(Light::LT_DIRECTIONAL); + else if (sValue == "spot") + pLight->setType(Light::LT_SPOTLIGHT); + else if (sValue == "radPoint") + pLight->setType(Light::LT_POINT); + else if (sValue == "rect") + pLight->setType(Light::LT_RECTLIGHT); + + pLight->setVisible(getAttribBool(XMLNode, "visible", true)); + pLight->setCastShadows(getAttribBool(XMLNode, "castShadows", true)); + pLight->setPowerScale(getAttribReal(XMLNode, "powerScale", 1.0)); + + // Process colourDiffuse (?) + if (auto pElement = XMLNode.child("colourDiffuse")) + pLight->setDiffuseColour(parseColour(pElement)); + + // Process colourSpecular (?) + if (auto pElement = XMLNode.child("colourSpecular")) + pLight->setSpecularColour(parseColour(pElement)); + + if (sValue != "directional") { + // Process lightRange (?) + if (auto pElement = XMLNode.child("lightRange")) + processLightRange(pElement, pLight); + + // Process lightAttenuation (?) + if (auto pElement = XMLNode.child("lightAttenuation")) + processLightAttenuation(pElement, pLight); + } + + if (sValue == "rect") { + // Process lightSourceSize (?) + if (auto pElement = XMLNode.child("lightSourceSize")) + processLightSourceSize(pElement, pLight); + } + + // Process userDataReference (?) + if (auto pElement = XMLNode.child("userData")) + processUserData(pElement, pLight->getUserObjectBindings()); +} + +void SceneLoader::processCamera(pugi::xml_node &XMLNode, SceneNode *pParent) +{ + // Process attributes + String name = getAttrib(XMLNode, "name"); + + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Camera: " + name, LML_TRIVIAL); + + // Real fov = getAttribReal(XMLNode, "fov", 45); + Real aspectRatio = getAttribReal(XMLNode, "aspectRatio", 1.3333); + String projectionType = + getAttrib(XMLNode, "projectionType", "perspective"); + + // Create the camera + Camera *pCamera = mSceneMgr->createCamera(name); + + // construct a scenenode is no parent + if (!pParent) + pParent = mAttachNode->createChildSceneNode(name); + + pParent->attachObject(pCamera); + + // Set the field-of-view + //! @todo Is this always in degrees? + // pCamera->setFOVy(Degree(fov)); + + // Set the aspect ratio + pCamera->setAspectRatio(aspectRatio); + + // Set the projection type + if (projectionType == "perspective") + pCamera->setProjectionType(PT_PERSPECTIVE); + else if (projectionType == "orthographic") + pCamera->setProjectionType(PT_ORTHOGRAPHIC); + + // Process clipping (?) + if (auto pElement = XMLNode.child("clipping")) { + Real nearDist = getAttribReal(pElement, "near"); + pCamera->setNearClipDistance(nearDist); + + Real farDist = getAttribReal(pElement, "far"); + pCamera->setFarClipDistance(farDist); + } + + // Process userDataReference (?) + if (auto pElement = XMLNode.child("userData")) + processUserData(pElement, static_cast(pCamera) + ->getUserObjectBindings()); +} + +void SceneLoader::processNode(pugi::xml_node &XMLNode, SceneNode *pParent) +{ + // Construct the node's name + String name = getAttrib(XMLNode, "name"); + + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Node: " + name, LML_TRIVIAL); + + // Create the scene node + SceneNode *pNode; + if (name.empty()) { + // Let Ogre choose the name + if (pParent) + pNode = pParent->createChildSceneNode(); + else + pNode = mAttachNode->createChildSceneNode(); + } else { + if (mNamePrefix.length() > 0) { + // Provide the name + if (pParent) + pNode = pParent->createChildSceneNode( + mNamePrefix + "/" + name); + else + pNode = mAttachNode->createChildSceneNode( + mNamePrefix + "/" + name); + } else { + // Let Ogre choose the name + Ogre::String pname = mNamePrefix += name; + if (pParent) + pNode = pParent->createChildSceneNode(pname); + else + pNode = mAttachNode->createChildSceneNode( + pname); + pNode->getUserObjectBindings().setUserAny("object_name", + name); + } + } + + // Process other attributes + + // Process position (?) + if (auto pElement = XMLNode.child("position")) { + pNode->setPosition(parseVector3(pElement)); + } + + // Process rotation (?) + if (auto pElement = XMLNode.child("rotation")) { + pNode->setOrientation(parseQuaternion(pElement)); + } + + // Process scale (?) + if (auto pElement = XMLNode.child("scale")) { + pNode->setScale(parseVector3(pElement)); + } + + // Process lookTarget (?) + if (auto pElement = XMLNode.child("lookTarget")) + processLookTarget(pElement, pNode); + + // Process trackTarget (?) + if (auto pElement = XMLNode.child("trackTarget")) + processTrackTarget(pElement, pNode); + + // Process node (*) + for (auto pElement : XMLNode.children("node")) { + processNode(pElement, pNode); + } + + // Process entity (*) + for (auto pElement : XMLNode.children("entity")) { + processEntity(pElement, pNode); + } + + // Process light (*) + for (auto pElement : XMLNode.children("light")) { + processLight(pElement, pNode); + } + + // Process camera (*) + for (auto pElement : XMLNode.children("camera")) { + processCamera(pElement, pNode); + } + + // Process particleSystem (*) + for (auto pElement : XMLNode.children("particleSystem")) { + processParticleSystem(pElement, pNode); + } + + // Process billboardSet (*) + for (auto pElement : XMLNode.children("billboardSet")) { + processBillboardSet(pElement, pNode); + } + + // Process plane (*) + for (auto pElement : XMLNode.children("plane")) { + processPlane(pElement, pNode); + } + + // Process userDataReference (?) + if (auto pElement = XMLNode.child("userData")) + processUserData(pElement, pNode->getUserObjectBindings()); + + // Process node animations (?) + if (auto pElement = XMLNode.child("animations")) + processNodeAnimations(pElement, pNode); +} + +void SceneLoader::processLookTarget(pugi::xml_node &XMLNode, SceneNode *pParent) +{ + //! @todo Is this correct? Cause I don't have a clue actually + + // Process attributes + String nodeName = getAttrib(XMLNode, "nodeName"); + + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Look Target, nodeName: " + nodeName, + LML_TRIVIAL); + + Node::TransformSpace relativeTo = Node::TS_PARENT; + String sValue = getAttrib(XMLNode, "relativeTo"); + if (sValue == "local") + relativeTo = Node::TS_LOCAL; + else if (sValue == "parent") + relativeTo = Node::TS_PARENT; + else if (sValue == "world") + relativeTo = Node::TS_WORLD; + + // Process position (?) + Vector3 position; + if (auto pElement = XMLNode.child("position")) + position = parseVector3(pElement); + + // Process localDirection (?) + Vector3 localDirection = Vector3::NEGATIVE_UNIT_Z; + if (auto pElement = XMLNode.child("localDirection")) + localDirection = parseVector3(pElement); + + // Setup the look target + try { + if (!nodeName.empty()) { + SceneNode *pLookNode = + mSceneMgr->getSceneNode(nodeName); + position = pLookNode->_getDerivedPosition(); + } + + pParent->lookAt(position, relativeTo, localDirection); + } catch (const Exception &e) { + LogManager::getSingleton().logError("SceneLoader - " + + e.getDescription()); + } +} + +void SceneLoader::processTrackTarget(pugi::xml_node &XMLNode, + SceneNode *pParent) +{ + // Process attributes + String nodeName = getAttrib(XMLNode, "nodeName"); + + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Track Target, nodeName: " + nodeName, + LML_TRIVIAL); + + // Process localDirection (?) + Vector3 localDirection = Vector3::NEGATIVE_UNIT_Z; + if (auto pElement = XMLNode.child("localDirection")) + localDirection = parseVector3(pElement); + + // Process offset (?) + Vector3 offset = Vector3::ZERO; + if (auto pElement = XMLNode.child("offset")) + offset = parseVector3(pElement); + + // Setup the track target + try { + SceneNode *pTrackNode = mSceneMgr->getSceneNode(nodeName); + pParent->setAutoTracking(true, pTrackNode, localDirection, + offset); + } catch (const Exception &e) { + LogManager::getSingleton().logError("SceneLoader - " + + e.getDescription()); + } +} + +void SceneLoader::processEntity(pugi::xml_node &XMLNode, SceneNode *pParent) +{ + // Process attributes + String name = getAttrib(XMLNode, "name"); + + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Entity: " + name, LML_TRIVIAL); + + String meshFile = getAttrib(XMLNode, "meshFile"); + String staticGeometry = getAttrib(XMLNode, "static"); + String instancedManager = getAttrib(XMLNode, "instanced"); + String material = getAttrib(XMLNode, "material"); + bool castShadows = getAttribBool(XMLNode, "castShadows", true); + bool visible = getAttribBool(XMLNode, "visible", true); + + // Create the entity + MovableObject *pEntity = 0; + Ogre::String pname = mNamePrefix + name; + try { + // If the Entity is instanced then the creation path is different + if (!instancedManager.empty()) { + LogManager::getSingleton().logMessage( + "[SceneLoader] Adding entity: " + name + + " to Instance Manager: " + + instancedManager, + LML_TRIVIAL); + + // Load the Mesh to get the material name of the first submesh + Ogre::MeshPtr mesh = + MeshManager::getSingletonPtr()->load( + meshFile, m_sGroupName); + + // Get the material name of the entity + if (!material.empty()) + pEntity = mSceneMgr->createInstancedEntity( + material, instancedManager); + else + pEntity = mSceneMgr->createInstancedEntity( + mesh->getSubMesh(0)->getMaterialName(), + instancedManager); + + pParent->attachObject( + static_cast(pEntity)); + } else { + pEntity = mSceneMgr->createEntity(pname, meshFile, + m_sGroupName); + + static_cast(pEntity)->setCastShadows( + castShadows); + static_cast(pEntity)->setVisible(visible); + + if (!material.empty()) + static_cast(pEntity)->setMaterialName( + material); + + // If the Entity belongs to a Static Geometry group then it doesn't get attached to a node + // * TODO * : Clean up nodes without attached entities or children nodes? (should be done afterwards if the hierarchy is being processed) + if (!staticGeometry.empty()) { + LogManager::getSingleton().logMessage( + "[SceneLoader] Adding entity: " + name + + " to Static Group: " + + staticGeometry, + LML_TRIVIAL); + mSceneMgr->getStaticGeometry(staticGeometry) + ->addEntity( + static_cast(pEntity), + pParent->_getDerivedPosition(), + pParent->_getDerivedOrientation(), + pParent->_getDerivedScale()); + } else { + LogManager::getSingleton().logMessage( + "[SceneLoader] pParent->attachObject(): " + + name, + LML_TRIVIAL); + pParent->attachObject( + static_cast(pEntity)); + } + } + } catch (const Exception &e) { + LogManager::getSingleton().logError("SceneLoader - " + + e.getDescription()); + return; + } + + // Process userDataReference (?) + if (auto pElement = XMLNode.child("userData")) + processUserData(pElement, pEntity->getUserObjectBindings()); +} + +void SceneLoader::processParticleSystem(pugi::xml_node &XMLNode, + SceneNode *pParent) +{ + // Process attributes + String name = getAttrib(XMLNode, "name"); + + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Particle System: " + name, + LML_TRIVIAL); + + String templateName = getAttrib(XMLNode, "template"); + + if (templateName.empty()) + templateName = getAttrib( + XMLNode, "file"); // compatibility with old scenes + + Ogre::String pname = mNamePrefix + name; + // Create the particle system + try { + ParticleSystem *pParticles = + mSceneMgr->createParticleSystem(pname, templateName); + pParent->attachObject(pParticles); + } catch (const Exception &e) { + LogManager::getSingleton().logError("SceneLoader - " + + e.getDescription()); + } +} + +void SceneLoader::processBillboardSet(pugi::xml_node &XMLNode, + SceneNode *pParent) +{ + //! @todo Implement this +} + +void SceneLoader::processPlane(pugi::xml_node &XMLNode, SceneNode *pParent) +{ + String name = getAttrib(XMLNode, "name"); + + Ogre::String pname = mNamePrefix + name; + + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Plane: " + name, LML_TRIVIAL); + Real distance = getAttribReal(XMLNode, "distance"); + Real width = getAttribReal(XMLNode, "width"); + Real height = getAttribReal(XMLNode, "height"); + int xSegments = + StringConverter::parseInt(getAttrib(XMLNode, "xSegments")); + int ySegments = + StringConverter::parseInt(getAttrib(XMLNode, "ySegments")); + int numTexCoordSets = StringConverter::parseInt( + getAttrib(XMLNode, "numTexCoordSets")); + Real uTile = getAttribReal(XMLNode, "uTile"); + Real vTile = getAttribReal(XMLNode, "vTile"); + String material = getAttrib(XMLNode, "material"); + bool hasNormals = getAttribBool(XMLNode, "hasNormals"); + Vector3 normal = parseVector3(XMLNode.child("normal")); + Vector3 up = parseVector3(XMLNode.child("upVector")); + + Plane plane(normal, distance); + MeshPtr res = MeshManager::getSingletonPtr()->createPlane( + pname + "mesh", m_sGroupName, plane, width, height, xSegments, + ySegments, hasNormals, numTexCoordSets, uTile, vTile, up); + Entity *ent = mSceneMgr->createEntity(pname, pname + "mesh"); + + ent->setMaterialName(material); + + pParent->attachObject(ent); +} + +void SceneLoader::processFog(pugi::xml_node &XMLNode) +{ + LogManager::getSingleton().logMessage("[SceneLoader] Processing Fog...", + LML_TRIVIAL); + + // Process attributes + Real expDensity = getAttribReal(XMLNode, "density", 0.001); + Real linearStart = getAttribReal(XMLNode, "start", 0.0); + Real linearEnd = getAttribReal(XMLNode, "end", 1.0); + + FogMode mode = FOG_NONE; + String sMode = getAttrib(XMLNode, "mode"); + if (sMode == "none") + mode = FOG_NONE; + else if (sMode == "exp") + mode = FOG_EXP; + else if (sMode == "exp2") + mode = FOG_EXP2; + else if (sMode == "linear") + mode = FOG_LINEAR; + else + mode = (FogMode)StringConverter::parseInt(sMode); + + // Process colourDiffuse (?) + ColourValue colourDiffuse = ColourValue::White; + + if (auto pElement = XMLNode.child("colour")) + colourDiffuse = parseColour(pElement); + + // Setup the fog + mSceneMgr->setFog(mode, colourDiffuse, expDensity, linearStart, + linearEnd); +} + +void SceneLoader::processSkyBox(pugi::xml_node &XMLNode) +{ + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing SkyBox...", LML_TRIVIAL); + + // Process attributes + String material = getAttrib(XMLNode, "material", "BaseWhite"); + Real distance = getAttribReal(XMLNode, "distance", 5000); + bool drawFirst = getAttribBool(XMLNode, "drawFirst", true); + + // Process rotation (?) + Quaternion rotation = Quaternion::IDENTITY; + + if (auto pElement = XMLNode.child("rotation")) + rotation = parseQuaternion(pElement); + + // Setup the sky box + mSceneMgr->setSkyBox(true, material, distance, drawFirst, rotation, + m_sGroupName); +} + +void SceneLoader::processSkyDome(pugi::xml_node &XMLNode) +{ + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing SkyDome...", LML_TRIVIAL); + + // Process attributes + String material = XMLNode.attribute("material").value(); + Real curvature = getAttribReal(XMLNode, "curvature", 10); + Real tiling = getAttribReal(XMLNode, "tiling", 8); + Real distance = getAttribReal(XMLNode, "distance", 4000); + bool drawFirst = getAttribBool(XMLNode, "drawFirst", true); + bool active = getAttribBool(XMLNode, "active", false); + if (!active) + return; + + // Process rotation (?) + Quaternion rotation = Quaternion::IDENTITY; + if (auto pElement = XMLNode.child("rotation")) + rotation = parseQuaternion(pElement); + + // Setup the sky dome + mSceneMgr->setSkyDome(true, material, curvature, tiling, distance, + drawFirst, rotation, 16, 16, -1, m_sGroupName); +} + +void SceneLoader::processSkyPlane(pugi::xml_node &XMLNode) +{ + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing SkyPlane...", LML_TRIVIAL); + + // Process attributes + String material = getAttrib(XMLNode, "material"); + Real planeX = getAttribReal(XMLNode, "planeX", 0); + Real planeY = getAttribReal(XMLNode, "planeY", -1); + Real planeZ = getAttribReal(XMLNode, "planeZ", 0); + Real planeD = getAttribReal(XMLNode, "planeD", 5000); + Real scale = getAttribReal(XMLNode, "scale", 1000); + Real bow = getAttribReal(XMLNode, "bow", 0); + Real tiling = getAttribReal(XMLNode, "tiling", 10); + bool drawFirst = getAttribBool(XMLNode, "drawFirst", true); + + // Setup the sky plane + Plane plane; + plane.normal = Vector3(planeX, planeY, planeZ); + plane.d = planeD; + mSceneMgr->setSkyPlane(true, plane, material, scale, tiling, drawFirst, + bow, 1, 1, m_sGroupName); +} + +void SceneLoader::processLightRange(pugi::xml_node &XMLNode, Light *pLight) +{ + // Process attributes + Real inner = getAttribReal(XMLNode, "inner"); + Real outer = getAttribReal(XMLNode, "outer"); + Real falloff = getAttribReal(XMLNode, "falloff", 1.0); + + // Setup the light range + pLight->setSpotlightRange(Radian(inner), Radian(outer), falloff); +} + +void SceneLoader::processLightAttenuation(pugi::xml_node &XMLNode, + Light *pLight) +{ + // Process attributes + Real range = getAttribReal(XMLNode, "range"); + Real constant = getAttribReal(XMLNode, "constant"); + Real linear = getAttribReal(XMLNode, "linear"); + Real quadratic = getAttribReal(XMLNode, "quadratic"); + + // Setup the light attenuation + pLight->setAttenuation(range, constant, linear, quadratic); +} + +void SceneLoader::processLightSourceSize(pugi::xml_node &XMLNode, Light *pLight) +{ + // Process attributes + Real width = getAttribReal(XMLNode, "width"); + Real height = getAttribReal(XMLNode, "height"); + + // Setup the light range + pLight->setSourceSize(width, height); +} + +void SceneLoader::processUserData(pugi::xml_node &XMLNode, + UserObjectBindings &userData) +{ + // Process node (*) + for (auto pElement : XMLNode.children("property")) { + String name = getAttrib(pElement, "name"); + String type = getAttrib(pElement, "type"); + String data = getAttrib(pElement, "data"); + + Any value; + if (type == "bool") + value = StringConverter::parseBool(data); + else if (type == "float") + value = StringConverter::parseReal(data); + else if (type == "int") + value = StringConverter::parseInt(data); + else + value = data; + + userData.setUserAny(name, value); + } +} + +void SceneLoader::processNodeAnimations(pugi::xml_node &XMLNode, + SceneNode *pParent) +{ + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Node Animations for SceneNode: " + + pParent->getName(), + LML_TRIVIAL); + + // Process node animations (*) + for (auto pElement : XMLNode.children("animation")) { + processNodeAnimation(pElement, pParent); + } +} + +void SceneLoader::processNodeAnimation(pugi::xml_node &XMLNode, + SceneNode *pParent) +{ + // Process node animation (*) + + // Construct the animation name + String name = getAttrib(XMLNode, "name"); + + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Node Animation: " + name, + LML_TRIVIAL); + + Real length = getAttribReal(XMLNode, "length"); + + Animation *anim = mSceneMgr->createAnimation(name, length); + + bool enable = getAttribBool(XMLNode, "enable", false); + bool loop = getAttribBool(XMLNode, "loop", false); + + String interpolationMode = getAttrib(XMLNode, "interpolationMode"); + + if (interpolationMode == "linear") + anim->setInterpolationMode(Animation::IM_LINEAR); + else if (interpolationMode == "spline") + anim->setInterpolationMode(Animation::IM_SPLINE); + else + LogManager::getSingleton().logError( + "SceneLoader - Invalid interpolationMode: " + + interpolationMode); + + String rotationInterpolationMode = + getAttrib(XMLNode, "rotationInterpolationMode"); + + if (rotationInterpolationMode == "linear") + anim->setRotationInterpolationMode(Animation::RIM_LINEAR); + else if (rotationInterpolationMode == "spherical") + anim->setRotationInterpolationMode(Animation::RIM_SPHERICAL); + else + LogManager::getSingleton().logError( + "SceneLoader - Invalid rotationInterpolationMode: " + + rotationInterpolationMode); + + // create a track to animate the camera's node + NodeAnimationTrack *track = anim->createNodeTrack(0, pParent); + pParent->setInitialState(); + + // Process keyframes (*) + for (auto pElement : XMLNode.children("keyframe")) { + processKeyframe(pElement, track); + } + + // create a new animation state to track this + auto animState = mSceneMgr->createAnimationState(name); + animState->setEnabled(enable); + animState->setLoop(loop); +} + +void SceneLoader::processKeyframe(pugi::xml_node &XMLNode, + NodeAnimationTrack *pTrack) +{ + // Process node animation keyframe (*) + Real time = getAttribReal(XMLNode, "time"); + + LogManager::getSingleton().logMessage( + "[SceneLoader] Processing Keyframe: " + + StringConverter::toString(time), + LML_TRIVIAL); + + auto keyframe = pTrack->createNodeKeyFrame(time); + + // Process translation (?) + if (auto pElement = XMLNode.child("position")) { + Vector3 translation = parseVector3(pElement); + keyframe->setTranslate(translation); + } + + // Process rotation (?) + //Quaternion rotation = Quaternion::IDENTITY; + if (auto pElement = XMLNode.child("rotation")) { + Quaternion rotation = parseQuaternion(pElement); + keyframe->setRotation(rotation); + } + + // Process scale (?) + //Vector3 scale = parseVector3(XMLNode.child("scale")); + if (auto pElement = XMLNode.child("scale")) { + Vector3 scale = parseVector3(pElement); + keyframe->setScale(scale); + } +} + +void SceneLoader::exportScene(SceneNode *rootNode, const String &outFileName) +{ + pugi::xml_document XMLDoc; // character type defaults to char + auto comment = XMLDoc.append_child(pugi::node_comment); + comment.set_value( + StringUtil::format(" exporter: Plugin_Scene %d.%d.%d ", + OGRE_VERSION_MAJOR, OGRE_VERSION_MINOR, + OGRE_VERSION_PATCH) + .c_str()); + auto scene = XMLDoc.append_child("scene"); + scene.append_attribute("formatVersion") = "1.1"; + scene.append_attribute("sceneManager") = + rootNode->getCreator()->getTypeName().c_str(); + + auto nodes = scene.append_child("nodes"); + + for (auto c : rootNode->getChildren()) + writeNode(nodes, static_cast(c)); + + //writeNode(nodes, rootNode); + + XMLDoc.save_file(outFileName.c_str()); +} + +static void write(pugi::xml_node &node, const Vector3 &v) +{ + node.append_attribute("x") = StringConverter::toString(v.x).c_str(); + node.append_attribute("y") = StringConverter::toString(v.y).c_str(); + node.append_attribute("z") = StringConverter::toString(v.z).c_str(); +} + +static void write(pugi::xml_node &node, const ColourValue &c) +{ + node.append_attribute("r") = StringConverter::toString(c.r).c_str(); + node.append_attribute("g") = StringConverter::toString(c.g).c_str(); + node.append_attribute("b") = StringConverter::toString(c.b).c_str(); + node.append_attribute("a") = StringConverter::toString(c.a).c_str(); +} + +void SceneLoader::writeNode(pugi::xml_node &parentXML, const SceneNode *n) +{ + auto nodeXML = parentXML.append_child("node"); + if (!n->getName().empty()) + nodeXML.append_attribute("name") = n->getName().c_str(); + + auto pos = nodeXML.append_child("position"); + write(pos, n->getPosition()); + + auto scale = nodeXML.append_child("scale"); + write(scale, n->getScale()); + + auto rot = nodeXML.append_child("rotation"); + rot.append_attribute("qw") = + StringConverter::toString(n->getOrientation().w).c_str(); + rot.append_attribute("qx") = + StringConverter::toString(n->getOrientation().x).c_str(); + rot.append_attribute("qy") = + StringConverter::toString(n->getOrientation().y).c_str(); + rot.append_attribute("qz") = + StringConverter::toString(n->getOrientation().z).c_str(); + + for (auto mo : n->getAttachedObjects()) { + if (auto c = dynamic_cast(mo)) { + auto camera = nodeXML.append_child("camera"); + camera.append_attribute("name") = c->getName().c_str(); + auto clipping = camera.append_child("clipping"); + clipping.append_attribute("near") = + StringConverter::toString( + c->getNearClipDistance()) + .c_str(); + clipping.append_attribute("far") = + StringConverter::toString( + c->getFarClipDistance()) + .c_str(); + continue; + } + + if (auto l = dynamic_cast(mo)) { + auto light = nodeXML.append_child("light"); + light.append_attribute("name") = l->getName().c_str(); + light.append_attribute("castShadows") = + StringConverter::toString(l->getCastShadows()) + .c_str(); + + if (!l->isVisible()) + light.append_attribute("visible") = "false"; + + auto diffuse = light.append_child("colourDiffuse"); + write(diffuse, l->getDiffuseColour()); + auto specular = light.append_child("colourSpecular"); + write(specular, l->getSpecularColour()); + switch (l->getType()) { + case Light::LT_POINT: + light.append_attribute("type") = "point"; + break; + case Light::LT_DIRECTIONAL: + light.append_attribute("type") = "directional"; + break; + case Light::LT_SPOTLIGHT: + light.append_attribute("type") = "spot"; + break; + case Light::LT_RECTLIGHT: + light.append_attribute("type") = "rect"; + break; + } + + if (l->getType() != Light::LT_DIRECTIONAL) { + auto range = light.append_child("lightRange"); + range.append_attribute("inner") = + StringConverter::toString( + l->getSpotlightInnerAngle()) + .c_str(); + range.append_attribute("outer") = + StringConverter::toString( + l->getSpotlightOuterAngle()) + .c_str(); + range.append_attribute("falloff") = + StringConverter::toString( + l->getSpotlightFalloff()) + .c_str(); + auto atten = + light.append_child("lightAttenuation"); + atten.append_attribute("range") = + StringConverter::toString( + l->getAttenuationRange()) + .c_str(); + atten.append_attribute("constant") = + StringConverter::toString( + l->getAttenuationConstant()) + .c_str(); + atten.append_attribute("linear") = + StringConverter::toString( + l->getAttenuationLinear()) + .c_str(); + atten.append_attribute("quadratic") = + StringConverter::toString( + l->getAttenuationQuadric()) + .c_str(); + } + + continue; + } + + if (auto e = dynamic_cast(mo)) { + auto entity = nodeXML.append_child("entity"); + entity.append_attribute("name") = e->getName().c_str(); + entity.append_attribute("meshFile") = + e->getMesh()->getName().c_str(); + + if (!e->isVisible()) + entity.append_attribute("visible") = "false"; + + // Heuristic: assume first submesh is representative + auto sub0mat = e->getSubEntity(0)->getMaterial(); + if (sub0mat != + e->getMesh()->getSubMesh(0)->getMaterial()) + entity.append_attribute("material") = + sub0mat->getName().c_str(); + continue; + } + + LogManager::getSingleton().logWarning( + "SceneLoader - unsupported MovableType " + + mo->getMovableType()); + } + + // recurse + for (auto c : n->getChildren()) + writeNode(nodeXML, static_cast(c)); +} diff --git a/src/sceneloader/loader.h b/src/sceneloader/loader.h new file mode 100644 index 0000000..b6394f8 --- /dev/null +++ b/src/sceneloader/loader.h @@ -0,0 +1,79 @@ +#ifndef LOADER_H_ +#define LOADER_H_ +#include +#include +#include +#include +#include +#include + +class SceneLoader { +public: + SceneLoader(); + virtual ~SceneLoader(); + + void load(Ogre::DataStreamPtr &stream, const Ogre::String &groupName, + Ogre::SceneNode *rootNode, int counter); + + void exportScene(Ogre::SceneNode *rootNode, + const Ogre::String &outFileName); + + const Ogre::ColourValue &getBackgroundColour() + { + return mBackgroundColour; + } + +protected: + void writeNode(pugi::xml_node &parentXML, const Ogre::SceneNode *node); + void processScene(pugi::xml_node &XMLRoot); + + void processNodes(pugi::xml_node &XMLNode); + void processExternals(pugi::xml_node &XMLNode); + void processEnvironment(pugi::xml_node &XMLNode); + void processTerrainGroup(pugi::xml_node &XMLNode); + void processBlendmaps(pugi::xml_node &XMLNode); + void processUserData(pugi::xml_node &XMLNode, + Ogre::UserObjectBindings &userData); + void processLight(pugi::xml_node &XMLNode, + Ogre::SceneNode *pParent = 0); + void processCamera(pugi::xml_node &XMLNode, + Ogre::SceneNode *pParent = 0); + + void processNode(pugi::xml_node &XMLNode, Ogre::SceneNode *pParent = 0); + void processLookTarget(pugi::xml_node &XMLNode, + Ogre::SceneNode *pParent); + void processTrackTarget(pugi::xml_node &XMLNode, + Ogre::SceneNode *pParent); + void processEntity(pugi::xml_node &XMLNode, Ogre::SceneNode *pParent); + void processParticleSystem(pugi::xml_node &XMLNode, + Ogre::SceneNode *pParent); + void processBillboardSet(pugi::xml_node &XMLNode, + Ogre::SceneNode *pParent); + void processPlane(pugi::xml_node &XMLNode, Ogre::SceneNode *pParent); + void processNodeAnimations(pugi::xml_node &XMLNode, + Ogre::SceneNode *pParent); + void processNodeAnimation(pugi::xml_node &XMLNode, + Ogre::SceneNode *pParent); + void processKeyframe(pugi::xml_node &XMLNode, + Ogre::NodeAnimationTrack *pTrack); + + void processFog(pugi::xml_node &XMLNode); + void processSkyBox(pugi::xml_node &XMLNode); + void processSkyDome(pugi::xml_node &XMLNode); + void processSkyPlane(pugi::xml_node &XMLNode); + + void processLightRange(pugi::xml_node &XMLNode, Ogre::Light *pLight); + void processLightAttenuation(pugi::xml_node &XMLNode, + Ogre::Light *pLight); + void processLightSourceSize(pugi::xml_node &XMLNode, + Ogre::Light *pLight); + + Ogre::SceneManager *mSceneMgr; + Ogre::SceneNode *mAttachNode; + Ogre::String m_sGroupName; + Ogre::ColourValue mBackgroundColour; + Ogre::String mNamePrefix; + int counter; +}; + +#endif