// Copyright (C) 2002-2012 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #include "IrrCompileConfig.h" #ifdef _IRR_COMPILE_WITH_COLLADA_LOADER_ #include "CColladaFileLoader.h" #include "CMeshTextureLoader.h" #include "os.h" #include "IXMLReader.h" #include "IDummyTransformationSceneNode.h" #include "SAnimatedMesh.h" #include "fast_atof.h" #include "quaternion.h" #include "ILightSceneNode.h" #include "ICameraSceneNode.h" #include "IMeshManipulator.h" #include "IReadFile.h" #include "IAttributes.h" #include "IMeshCache.h" #include "IMeshSceneNode.h" #include "SMeshBufferLightMap.h" #include "irrMap.h" #ifdef _DEBUG #define COLLADA_READER_DEBUG #endif namespace irr { namespace scene { namespace { // currently supported COLLADA tag names const core::stringc colladaSectionName = "COLLADA"; const core::stringc librarySectionName = "library"; const core::stringc libraryNodesSectionName = "library_nodes"; const core::stringc libraryGeometriesSectionName = "library_geometries"; const core::stringc libraryMaterialsSectionName = "library_materials"; const core::stringc libraryImagesSectionName = "library_images"; const core::stringc libraryVisualScenesSectionName = "library_visual_scenes"; const core::stringc libraryCamerasSectionName = "library_cameras"; const core::stringc libraryLightsSectionName = "library_lights"; const core::stringc libraryEffectsSectionName = "library_effects"; const core::stringc assetSectionName = "asset"; const core::stringc sceneSectionName = "scene"; const core::stringc visualSceneSectionName = "visual_scene"; const core::stringc lightPrefabName = "light"; const core::stringc cameraPrefabName = "camera"; const core::stringc materialSectionName = "material"; const core::stringc geometrySectionName = "geometry"; const core::stringc imageSectionName = "image"; const core::stringc textureSectionName = "texture"; const core::stringc effectSectionName = "effect"; const core::stringc pointSectionName = "point"; const core::stringc directionalSectionName ="directional"; const core::stringc spotSectionName = "spot"; const core::stringc ambientSectionName = "ambient"; const core::stringc meshSectionName = "mesh"; const core::stringc sourceSectionName = "source"; const core::stringc arraySectionName = "array"; const core::stringc floatArraySectionName ="float_array"; const core::stringc intArraySectionName = "int_array"; const core::stringc techniqueCommonSectionName = "technique_common"; const core::stringc accessorSectionName = "accessor"; const core::stringc verticesSectionName = "vertices"; const core::stringc inputTagName = "input"; const core::stringc polylistSectionName = "polylist"; const core::stringc trianglesSectionName = "triangles"; const core::stringc polygonsSectionName = "polygons"; const core::stringc primitivesName = "p"; const core::stringc vcountName = "vcount"; const core::stringc upAxisNodeName = "up_axis"; const core::stringc nodeSectionName = "node"; const core::stringc lookatNodeName = "lookat"; const core::stringc matrixNodeName = "matrix"; const core::stringc perspectiveNodeName = "perspective"; const core::stringc rotateNodeName = "rotate"; const core::stringc scaleNodeName = "scale"; const core::stringc translateNodeName = "translate"; const core::stringc skewNodeName = "skew"; const core::stringc minNodeName = "min"; const core::stringc maxNodeName = "max"; const core::stringc instanceName = "instance"; const core::stringc instanceGeometryName = "instance_geometry"; const core::stringc instanceSceneName = "instance_visual_scene"; const core::stringc instanceEffectName = "instance_effect"; const core::stringc instanceMaterialName = "instance_material"; const core::stringc instanceLightName = "instance_light"; const core::stringc instanceNodeName = "instance_node"; const core::stringc instanceCameraName = "instance_camera"; const core::stringc bindMaterialName = "bind_material"; const core::stringc extraNodeName = "extra"; const core::stringc techniqueNodeName = "technique"; const core::stringc colorNodeName = "color"; const core::stringc floatNodeName = "float"; const core::stringc float2NodeName = "float2"; const core::stringc float3NodeName = "float3"; const core::stringc newParamName = "newparam"; const core::stringc paramTagName = "param"; const core::stringc initFromName = "init_from"; const core::stringc dataName = "data"; const core::stringc wrapsName = "wrap_s"; const core::stringc wraptName = "wrap_t"; const core::stringc wraprName = "wrap_r"; // for downward compatibility to bug in old Irrlicht collada writer. Not standard but we wrote that accidentally up to Irrlicht 1.8, so we should still be able to load those files const core::stringc wrappName = "wrap_p"; const core::stringc minfilterName = "minfilter"; const core::stringc magfilterName = "magfilter"; const core::stringc mipfilterName = "mipfilter"; const core::stringc textureNodeName = "texture"; const core::stringc doubleSidedNodeName = "double_sided"; const core::stringc constantAttenuationNodeName = "constant_attenuation"; const core::stringc linearAttenuationNodeName = "linear_attenuation"; const core::stringc quadraticAttenuationNodeName = "quadratic_attenuation"; const core::stringc falloffAngleNodeName = "falloff_angle"; const core::stringc falloffExponentNodeName = "falloff_exponent"; const core::stringc profileCOMMONSectionName = "profile_COMMON"; const core::stringc profileCOMMONAttributeName = "COMMON"; const char* const inputSemanticNames[] = {"POSITION", "VERTEX", "NORMAL", "TEXCOORD", "UV", "TANGENT", "IMAGE", "TEXTURE", "COLOR", 0}; // We have to read ambient lights like other light types here, so we need a type for it const video::E_LIGHT_TYPE ELT_AMBIENT = video::E_LIGHT_TYPE(video::ELT_COUNT+1); } //! following class is for holding and creating instances of library //! objects, named prefabs in this loader. class CPrefab : public IColladaPrefab { public: CPrefab(const core::stringc& id) : Id(id) { } //! creates an instance of this prefab virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent, scene::ISceneManager* mgr) _IRR_OVERRIDE_ { // empty implementation return 0; } //! returns id of this prefab virtual const core::stringc& getId() _IRR_OVERRIDE_ { return Id; } protected: core::stringc Id; }; //! prefab for a light scene node class CLightPrefab : public CPrefab { public: CLightPrefab(const core::stringc& id) : CPrefab(id) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA: loaded light prefab", Id.c_str(), ELL_DEBUG); #endif } video::SLight LightData; // publically accessible //! creates an instance of this prefab virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent, scene::ISceneManager* mgr) _IRR_OVERRIDE_ { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA: Constructing light instance", Id.c_str(), ELL_DEBUG); #endif if ( LightData.Type == ELT_AMBIENT ) { mgr->setAmbientLight( LightData.DiffuseColor ); return 0; } scene::ILightSceneNode* l = mgr->addLightSceneNode(parent); if (l) { l->setLightData ( LightData ); l->setName(getId()); } return l; } }; //! prefab for a mesh scene node class CGeometryPrefab : public CPrefab { public: CGeometryPrefab(const core::stringc& id) : CPrefab(id) { } scene::IMesh* Mesh; //! creates an instance of this prefab virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent, scene::ISceneManager* mgr) _IRR_OVERRIDE_ { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA: Constructing mesh instance", Id.c_str(), ELL_DEBUG); #endif scene::ISceneNode* m = mgr->addMeshSceneNode(Mesh, parent); if (m) { m->setName(getId()); // m->setMaterialFlag(video::EMF_BACK_FACE_CULLING, false); // m->setDebugDataVisible(scene::EDS_FULL); } return m; } }; //! prefab for a camera scene node class CCameraPrefab : public CPrefab { public: CCameraPrefab(const core::stringc& id) : CPrefab(id), YFov(core::PI / 2.5f), ZNear(1.0f), ZFar(3000.0f) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA: loaded camera prefab", Id.c_str(), ELL_DEBUG); #endif } // publicly accessible data f32 YFov; f32 ZNear; f32 ZFar; //! creates an instance of this prefab virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent, scene::ISceneManager* mgr) _IRR_OVERRIDE_ { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA: Constructing camera instance", Id.c_str(), ELL_DEBUG); #endif scene::ICameraSceneNode* c = mgr->addCameraSceneNode(parent, core::vector3df(0,0,0), core::vector3df(0,0,100), -1, false); if (c) { c->setFOV(YFov); c->setNearValue(ZNear); c->setFarValue(ZFar); c->setName(getId()); } return c; } }; //! prefab for a container scene node //! Collects other prefabs and instantiates them upon instantiation //! Uses a dummy scene node to return the children as one scene node class CScenePrefab : public CPrefab { public: CScenePrefab(const core::stringc& id) : CPrefab(id) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA: loaded scene prefab", Id.c_str(), ELL_DEBUG); #endif } //! creates an instance of this prefab virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent, scene::ISceneManager* mgr) _IRR_OVERRIDE_ { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA: Constructing scene instance", Id.c_str(), ELL_DEBUG); #endif if (Children.size()==0) return 0; scene::IDummyTransformationSceneNode* s = mgr->addDummyTransformationSceneNode(parent); if (s) { s->setName(getId()); s->getRelativeTransformationMatrix() = Transformation; s->updateAbsolutePosition(); core::stringc t; for (u32 i=0; i<16; ++i) { t+=core::stringc((double)Transformation[i]); t+=" "; } #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA: Transformation", t.c_str(), ELL_DEBUG); #endif for (u32 i=0; iaddInstance(s, mgr); } return s; } core::array Children; core::matrix4 Transformation; }; //! Constructor CColladaFileLoader::CColladaFileLoader(scene::ISceneManager* smgr, io::IFileSystem* fs) : SceneManager(smgr), FileSystem(fs), DummyMesh(0), FirstLoadedMesh(0), LoadedMeshCount(0), CreateInstances(false) { #ifdef _DEBUG setDebugName("CColladaFileLoader"); #endif // Escape characters, see https://www.w3schools.com/tags/ref_urlencode.asp // TODO: There should be more, but usually people just escape the space character anyway. // And I'm not sure if our xml files are utf-8 or windows-1252, so let's avoid the ambiguous ones. EscapeCharsAnyURI.push_back(EscapeCharacterURL(' ', "%20")); EscapeCharsAnyURI.push_back(EscapeCharacterURL('"', "%22")); EscapeCharsAnyURI.push_back(EscapeCharacterURL('#', "%23")); EscapeCharsAnyURI.push_back(EscapeCharacterURL('$', "%24")); EscapeCharsAnyURI.push_back(EscapeCharacterURL('%', "%25")); EscapeCharsAnyURI.push_back(EscapeCharacterURL('&', "%26")); EscapeCharsAnyURI.push_back(EscapeCharacterURL('\'', "%27")); EscapeCharsAnyURI.push_back(EscapeCharacterURL('(', "%28")); EscapeCharsAnyURI.push_back(EscapeCharacterURL(')', "%29")); EscapeCharsAnyURI.push_back(EscapeCharacterURL('/', "%2F")); EscapeCharsAnyURI.push_back(EscapeCharacterURL('\\', "%5C")); TextureLoader = new CMeshTextureLoader( FileSystem, SceneManager->getVideoDriver() ); } //! destructor CColladaFileLoader::~CColladaFileLoader() { if (DummyMesh) DummyMesh->drop(); if (FirstLoadedMesh) FirstLoadedMesh->drop(); } //! Returns true if the file maybe is able to be loaded by this class. /** This decision should be based only on the file extension (e.g. ".cob") */ bool CColladaFileLoader::isALoadableFileExtension(const io::path& filename) const { return core::hasFileExtension ( filename, "xml", "dae" ); } //! creates/loads an animated mesh from the file. //! \return Pointer to the created mesh. Returns 0 if loading failed. //! If you no longer need the mesh, you should call IAnimatedMesh::drop(). //! See IReferenceCounted::drop() for more information. IAnimatedMesh* CColladaFileLoader::createMesh(io::IReadFile* file) { io::IXMLReaderUTF8* reader = FileSystem->createXMLReaderUTF8(file); if (!reader) return 0; if ( getMeshTextureLoader() ) getMeshTextureLoader()->setMeshFile(file); CurrentlyLoadingMesh = file->getFileName(); CreateInstances = SceneManager->getParameters()->getAttributeAsBool( scene::COLLADA_CREATE_SCENE_INSTANCES); Version = 0; FlipAxis = false; // read until COLLADA section, skip other parts while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { if (colladaSectionName == reader->getNodeName()) readColladaSection(reader); else skipSection(reader, true); // unknown section } } reader->drop(); if (!Version) return 0; // because this loader loads and creates a complete scene instead of // a single mesh, return an empty dummy mesh to make the scene manager // know that everything went well. if (!DummyMesh) DummyMesh = new SAnimatedMesh(); scene::IAnimatedMesh* returnMesh = DummyMesh; if (Version < 10400) instantiateNode(SceneManager->getRootSceneNode()); // add the first loaded mesh into the mesh cache too, if more than one // meshes have been loaded from the file if (LoadedMeshCount>1 && FirstLoadedMesh) { os::Printer::log("Added COLLADA mesh", FirstLoadedMeshName.c_str()); SceneManager->getMeshCache()->addMesh(FirstLoadedMeshName.c_str(), FirstLoadedMesh); } // clean up temporary loaded data clearData(); returnMesh->grab(); // store until this loader is destroyed DummyMesh->drop(); DummyMesh = 0; if (FirstLoadedMesh) FirstLoadedMesh->drop(); FirstLoadedMesh = 0; LoadedMeshCount = 0; return returnMesh; } //! skips an (unknown) section in the collada document void CColladaFileLoader::skipSection(io::IXMLReaderUTF8* reader, bool reportSkipping) { #ifndef COLLADA_READER_DEBUG if (reportSkipping) // always report in COLLADA_READER_DEBUG mode #endif os::Printer::log("COLLADA skipping section", core::stringc(reader->getNodeName()).c_str(), ELL_DEBUG); // skip if this element is empty anyway. if (reader->isEmptyElement()) return; // read until we've reached the last element in this section u32 tagCounter = 1; while(tagCounter && reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT && !reader->isEmptyElement()) { #ifdef COLLADA_READER_DEBUG if (reportSkipping) os::Printer::log("Skipping COLLADA unknown element", core::stringc(reader->getNodeName()).c_str(), ELL_DEBUG); #endif ++tagCounter; } else if (reader->getNodeType() == io::EXN_ELEMENT_END) --tagCounter; } } //! reads the section and its content void CColladaFileLoader::readColladaSection(io::IXMLReaderUTF8* reader) { if (reader->isEmptyElement()) return; // todo: patch level needs to be handled const f32 version = core::fast_atof(core::stringc(reader->getAttributeValue("version")).c_str()); Version = core::floor32(version)*10000+core::round32(core::fract(version)*1000.0f); // Version 1.4 can be checked for by if (Version >= 10400) while(reader->read()) if (reader->getNodeType() == io::EXN_ELEMENT) { if (assetSectionName == reader->getNodeName()) readAssetSection(reader); else if (librarySectionName == reader->getNodeName()) readLibrarySection(reader); else if (libraryNodesSectionName == reader->getNodeName()) readLibrarySection(reader); else if (libraryGeometriesSectionName == reader->getNodeName()) readLibrarySection(reader); else if (libraryMaterialsSectionName == reader->getNodeName()) readLibrarySection(reader); else if (libraryEffectsSectionName == reader->getNodeName()) readLibrarySection(reader); else if (libraryImagesSectionName == reader->getNodeName()) readLibrarySection(reader); else if (libraryCamerasSectionName == reader->getNodeName()) readLibrarySection(reader); else if (libraryLightsSectionName == reader->getNodeName()) readLibrarySection(reader); else if (libraryVisualScenesSectionName == reader->getNodeName()) readVisualScene(reader); else if (assetSectionName == reader->getNodeName()) readAssetSection(reader); else if (sceneSectionName == reader->getNodeName()) readSceneSection(reader); else { os::Printer::log("COLLADA loader warning: Wrong tag usage found", reader->getNodeName(), ELL_WARNING); skipSection(reader, true); // unknown section } } } //! reads a section and its content void CColladaFileLoader::readLibrarySection(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading library", ELL_DEBUG); #endif if (reader->isEmptyElement()) return; while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { // animation section tbd if (cameraPrefabName == reader->getNodeName()) readCameraPrefab(reader); else // code section tbd // controller section tbd if (geometrySectionName == reader->getNodeName()) readGeometry(reader); else if (imageSectionName == reader->getNodeName()) readImage(reader); else if (lightPrefabName == reader->getNodeName()) readLightPrefab(reader); else if (materialSectionName == reader->getNodeName()) readMaterial(reader); else if (nodeSectionName == reader->getNodeName()) { CScenePrefab p(""); readNodeSection(reader, SceneManager->getRootSceneNode(), &p); } else if (effectSectionName == reader->getNodeName()) readEffect(reader); else // program section tbd if (textureSectionName == reader->getNodeName()) readTexture(reader); else skipSection(reader, true); // unknown section, not all allowed supported yet } else if (reader->getNodeType() == io::EXN_ELEMENT_END) { if (librarySectionName == reader->getNodeName()) break; // end reading. if (libraryNodesSectionName == reader->getNodeName()) break; // end reading. if (libraryGeometriesSectionName == reader->getNodeName()) break; // end reading. if (libraryMaterialsSectionName == reader->getNodeName()) break; // end reading. if (libraryEffectsSectionName == reader->getNodeName()) break; // end reading. if (libraryImagesSectionName == reader->getNodeName()) break; // end reading. if (libraryLightsSectionName == reader->getNodeName()) break; // end reading. if (libraryCamerasSectionName == reader->getNodeName()) break; // end reading. } } } //! reads a element and stores it as a prefab void CColladaFileLoader::readVisualScene(io::IXMLReaderUTF8* reader) { CScenePrefab* p = 0; while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { if (visualSceneSectionName == reader->getNodeName()) p = new CScenePrefab(readId(reader)); else if (p && nodeSectionName == reader->getNodeName()) // as a child of visual_scene readNodeSection(reader, SceneManager->getRootSceneNode(), p); else if (assetSectionName == reader->getNodeName()) readAssetSection(reader); else if (extraNodeName == reader->getNodeName()) skipSection(reader, false); // ignore all other sections else { os::Printer::log("COLLADA loader warning: Wrong tag usage found", reader->getNodeName(), ELL_WARNING); skipSection(reader, true); // ignore all other sections } } else if (reader->getNodeType() == io::EXN_ELEMENT_END) { if (libraryVisualScenesSectionName == reader->getNodeName()) return; else if ((visualSceneSectionName == reader->getNodeName()) && p) { Prefabs.push_back(p); p = 0; } } } } //! reads a section and its content void CColladaFileLoader::readSceneSection(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading scene", ELL_DEBUG); #endif if (reader->isEmptyElement()) return; // read the scene core::matrix4 transform; // transformation of this node scene::IDummyTransformationSceneNode* node = 0; while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { if (lookatNodeName == reader->getNodeName()) transform *= readLookAtNode(reader); else if (matrixNodeName == reader->getNodeName()) transform *= readMatrixNode(reader); else if (perspectiveNodeName == reader->getNodeName()) transform *= readPerspectiveNode(reader); else if (rotateNodeName == reader->getNodeName()) transform *= readRotateNode(reader); else if (scaleNodeName == reader->getNodeName()) transform *= readScaleNode(reader); else if (skewNodeName == reader->getNodeName()) transform *= readSkewNode(reader); else if (translateNodeName == reader->getNodeName()) transform *= readTranslateNode(reader); else if (nodeSectionName == reader->getNodeName()) { // create dummy node if there is none yet. if (!node) node = SceneManager->addDummyTransformationSceneNode(SceneManager->getRootSceneNode()); readNodeSection(reader, node); } else if ((instanceSceneName == reader->getNodeName())) readInstanceNode(reader, SceneManager->getRootSceneNode(), 0, 0,instanceSceneName); else if (extraNodeName == reader->getNodeName()) skipSection(reader, false); else { os::Printer::log("COLLADA loader warning: Wrong tag usage found", reader->getNodeName(), ELL_WARNING); skipSection(reader, true); // ignore all other sections } } else if ((reader->getNodeType() == io::EXN_ELEMENT_END) && (sceneSectionName == reader->getNodeName())) return; } if (node) node->getRelativeTransformationMatrix() = transform; } //! reads a section and its content void CColladaFileLoader::readAssetSection(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading asset", ELL_DEBUG); #endif if (reader->isEmptyElement()) return; while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { if (upAxisNodeName == reader->getNodeName()) { reader->read(); FlipAxis = (core::stringc("Z_UP") == reader->getNodeData()); } } else if ((reader->getNodeType() == io::EXN_ELEMENT_END) && (assetSectionName == reader->getNodeName())) return; } } //! reads a section and its content void CColladaFileLoader::readNodeSection(io::IXMLReaderUTF8* reader, scene::ISceneNode* parent, CScenePrefab* p) { if (reader->isEmptyElement()) { return; #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading empty node", ELL_DEBUG); #endif } core::stringc name = readId(reader); #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading node", name, ELL_DEBUG); #endif core::matrix4 transform; // transformation of this node scene::ISceneNode* node = 0; // instance CScenePrefab* nodeprefab = 0; // prefab for library_nodes usage if (p) { nodeprefab = new CScenePrefab(readId(reader)); p->Children.push_back(nodeprefab); Prefabs.push_back(nodeprefab); // in order to delete them later on } // read the node while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { if (assetSectionName == reader->getNodeName()) readAssetSection(reader); else if (lookatNodeName == reader->getNodeName()) transform *= readLookAtNode(reader); else if (matrixNodeName == reader->getNodeName()) transform *= readMatrixNode(reader); else if (perspectiveNodeName == reader->getNodeName()) transform *= readPerspectiveNode(reader); else if (rotateNodeName == reader->getNodeName()) transform *= readRotateNode(reader); else if (scaleNodeName == reader->getNodeName()) transform *= readScaleNode(reader); else if (skewNodeName == reader->getNodeName()) transform *= readSkewNode(reader); else if (translateNodeName == reader->getNodeName()) transform *= readTranslateNode(reader); else if ((instanceName == reader->getNodeName()) || (instanceNodeName == reader->getNodeName()) || (instanceGeometryName == reader->getNodeName()) || (instanceLightName == reader->getNodeName()) || (instanceCameraName == reader->getNodeName()) ) { scene::ISceneNode* newnode = 0; readInstanceNode(reader, parent, &newnode, nodeprefab, reader->getNodeName()); if (node && newnode) { // move children from dummy to new node ISceneNodeList::ConstIterator it = node->getChildren().begin(); for (; it != node->getChildren().end(); it = node->getChildren().begin()) (*it)->setParent(newnode); // remove previous dummy node node->remove(); node = newnode; } } else if (nodeSectionName == reader->getNodeName()) { // create dummy node if there is none yet. if (CreateInstances && !node) { scene::IDummyTransformationSceneNode* dummy = SceneManager->addDummyTransformationSceneNode(parent); dummy->getRelativeTransformationMatrix() = transform; node = dummy; } else node = parent; // read and add child readNodeSection(reader, node, nodeprefab); } else if (extraNodeName == reader->getNodeName()) skipSection(reader, false); else skipSection(reader, true); // ignore all other sections } // end if node else if (reader->getNodeType() == io::EXN_ELEMENT_END) { if (nodeSectionName == reader->getNodeName()) break; } } if (nodeprefab) nodeprefab->Transformation = transform; else if (node) { // set transformation correctly into node. node->setPosition(transform.getTranslation()); node->setRotation(transform.getRotationDegrees()); node->setScale(transform.getScale()); node->updateAbsolutePosition(); node->setName(name); } } //! reads a element and its content and creates a matrix from it core::matrix4 CColladaFileLoader::readLookAtNode(io::IXMLReaderUTF8* reader) { core::matrix4 mat; if (reader->isEmptyElement()) return mat; #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading look at node", ELL_DEBUG); #endif f32 floats[9]; readFloatsInsideElement(reader, floats, 9); if (FlipAxis) { mat.buildCameraLookAtMatrixLH( core::vector3df(floats[0], floats[2], floats[1]), core::vector3df(floats[3], floats[5], floats[4]), core::vector3df(floats[6], floats[8], floats[7])); } else { mat.buildCameraLookAtMatrixLH( core::vector3df(floats[0], floats[1], floats[2]*-1.f), core::vector3df(floats[3], floats[4], floats[5]*-1.f), core::vector3df(floats[6], floats[7], floats[8]*-1.f)); } return mat; } //! reads a element and its content and creates a matrix from it core::matrix4 CColladaFileLoader::readSkewNode(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading skew node", ELL_DEBUG); #endif core::matrix4 mat; if (reader->isEmptyElement()) return mat; f32 floats[7]; // angle rotation-axis translation-axis readFloatsInsideElement(reader, floats, 7); // build skew matrix from these 7 floats // TODO: missing example, not sure if rotation is in correct direction. // TODO: shouldn't FlipAxis also be regarded here? core::quaternion q; q.fromAngleAxis(floats[0]*core::DEGTORAD, core::vector3df(floats[1], floats[2], floats[3])); mat = q.getMatrix(); if (floats[4]==1.f) // along x-axis { mat[4]=0.f; mat[6]=0.f; mat[8]=0.f; mat[9]=0.f; } else if (floats[5]==1.f) // along y-axis { mat[1]=0.f; mat[2]=0.f; mat[8]=0.f; mat[9]=0.f; } else if (floats[6]==1.f) // along z-axis { mat[1]=0.f; mat[2]=0.f; mat[4]=0.f; mat[6]=0.f; } if ( FlipAxis ) return mat; else return flipZAxis(mat); } //! reads a element and its content and creates a matrix from it core::matrix4 CColladaFileLoader::readMatrixNode(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading matrix node", ELL_DEBUG); #endif core::matrix4 mat; if (reader->isEmptyElement()) return mat; readFloatsInsideElement(reader, mat.pointer(), 16); // put translation into the correct place if (FlipAxis) { core::matrix4 mat2(mat, core::matrix4::EM4CONST_TRANSPOSED); mat2[1]=mat[8]; mat2[2]=mat[4]; mat2[4]=mat[2]; mat2[5]=mat[10]; mat2[6]=mat[6]; mat2[8]=mat[1]; mat2[9]=mat[9]; mat2[10]=mat[5]; mat2[12]=mat[3]; mat2[13]=mat[11]; mat2[14]=mat[7]; return mat2; } else return flipZAxis(core::matrix4(mat, core::matrix4::EM4CONST_TRANSPOSED)); } //! reads a element and its content and creates a matrix from it core::matrix4 CColladaFileLoader::readPerspectiveNode(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading perspective node", ELL_DEBUG); #endif core::matrix4 mat; if (reader->isEmptyElement()) return mat; f32 floats[1]; readFloatsInsideElement(reader, floats, 1); // TODO: build perspective matrix from this float os::Printer::log("COLLADA loader warning: not implemented yet.", ELL_WARNING); return mat; } //! reads a element and its content and creates a matrix from it core::matrix4 CColladaFileLoader::readRotateNode(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading rotate node", ELL_DEBUG); #endif core::matrix4 mat; if (reader->isEmptyElement()) return mat; f32 floats[4]; readFloatsInsideElement(reader, floats, 4); floats[3] *= -1.f; // to left handed rotation if (!core::iszero(floats[3])) { core::quaternion q; if (FlipAxis) q.fromAngleAxis(floats[3]*core::DEGTORAD, core::vector3df(floats[0], floats[2], floats[1])); else q.fromAngleAxis(floats[3]*core::DEGTORAD, core::vector3df(floats[0], floats[1], floats[2]*-1.f)); return q.getMatrix(); } else return core::IdentityMatrix; } //! reads a element and its content and creates a matrix from it core::matrix4 CColladaFileLoader::readScaleNode(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading scale node", ELL_DEBUG); #endif core::matrix4 mat; if (reader->isEmptyElement()) return mat; f32 floats[3]; readFloatsInsideElement(reader, floats, 3); if (FlipAxis) mat.setScale(core::vector3df(floats[0], floats[2], floats[1])); else mat.setScale(core::vector3df(floats[0], floats[1], floats[2])); return mat; } //! reads a element and its content and creates a matrix from it core::matrix4 CColladaFileLoader::readTranslateNode(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading translate node", ELL_DEBUG); #endif core::matrix4 mat; if (reader->isEmptyElement()) return mat; f32 floats[3]; readFloatsInsideElement(reader, floats, 3); if (FlipAxis) mat.setTranslation(core::vector3df(floats[0], floats[2], floats[1])); else mat.setTranslation(core::vector3df(floats[0], floats[1], floats[2]*-1.f)); return mat; } //! reads any kind of node void CColladaFileLoader::readInstanceNode(io::IXMLReaderUTF8* reader, scene::ISceneNode* parent, scene::ISceneNode** outNode, CScenePrefab* p, const core::stringc& type) { // find prefab of the specified id core::stringc url = reader->getAttributeValue("url"); uriToId(url); #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading instance", url, ELL_DEBUG); #endif if (!reader->isEmptyElement()) { while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { if (bindMaterialName == reader->getNodeName()) readBindMaterialSection(reader,url); else if (extraNodeName == reader->getNodeName()) skipSection(reader, false); } else if (reader->getNodeType() == io::EXN_ELEMENT_END) break; } } instantiateNode(parent, outNode, p, url, type); } void CColladaFileLoader::instantiateNode(scene::ISceneNode* parent, scene::ISceneNode** outNode, CScenePrefab* p, const core::stringc& url, const core::stringc& type) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA instantiate node", ELL_DEBUG); #endif for (u32 i=0; igetId()) { if (p) p->Children.push_back(Prefabs[i]); else if (CreateInstances) { scene::ISceneNode * newNode = Prefabs[i]->addInstance(parent, SceneManager); if (outNode) { *outNode = newNode; if (*outNode) (*outNode)->setName(url); } } return; } } if (p) { if (instanceGeometryName==type) { Prefabs.push_back(new CGeometryPrefab(url)); p->Children.push_back(Prefabs.getLast()); } } } //! reads a element and stores it as prefab void CColladaFileLoader::readCameraPrefab(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading camera prefab", ELL_DEBUG); #endif CCameraPrefab* prefab = new CCameraPrefab(readId(reader)); if (!reader->isEmptyElement()) { // read techniques optics and imager (the latter is completely ignored, though) readColladaParameters(reader, cameraPrefabName); SColladaParam* p; // XFOV not yet supported p = getColladaParameter(ECPN_YFOV); if (p && p->Type == ECPT_FLOAT) prefab->YFov = p->Floats[0]; p = getColladaParameter(ECPN_ZNEAR); if (p && p->Type == ECPT_FLOAT) prefab->ZNear = p->Floats[0]; p = getColladaParameter(ECPN_ZFAR); if (p && p->Type == ECPT_FLOAT) prefab->ZFar = p->Floats[0]; // orthographic camera uses LEFT, RIGHT, TOP, and BOTTOM } Prefabs.push_back(prefab); } //! reads a element and stores it in the image section void CColladaFileLoader::readImage(io::IXMLReaderUTF8* reader) { // add image to list of loaded images. Images.push_back(SColladaImage()); SColladaImage& image=Images.getLast(); image.Id = readId(reader); #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading image", core::stringc(image.Id), ELL_DEBUG); #endif image.Dimension.Height = (u32)reader->getAttributeValueAsInt("height"); image.Dimension.Width = (u32)reader->getAttributeValueAsInt("width"); if (Version >= 10400) // start with 1.4 { while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { if (assetSectionName == reader->getNodeName()) skipSection(reader, false); else if (initFromName == reader->getNodeName()) { reader->read(); image.Source = reader->getNodeData(); image.Source.trim(); unescape(image.Source); image.SourceIsFilename=true; } else if (dataName == reader->getNodeName()) { reader->read(); image.Source = reader->getNodeData(); image.Source.trim(); image.SourceIsFilename=false; } else if (extraNodeName == reader->getNodeName()) skipSection(reader, false); } else if (reader->getNodeType() == io::EXN_ELEMENT_END) { if (initFromName == reader->getNodeName()) return; } } } else { image.Source = reader->getAttributeValue("source"); image.Source.trim(); image.SourceIsFilename=false; } } //! reads a element and stores it in the texture section void CColladaFileLoader::readTexture(io::IXMLReaderUTF8* reader) { // add texture to list of loaded textures. Textures.push_back(SColladaTexture()); SColladaTexture& texture=Textures.getLast(); texture.Id = readId(reader); #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading texture", core::stringc(texture.Id), ELL_DEBUG); #endif if (!reader->isEmptyElement()) { readColladaInputs(reader, textureSectionName); SColladaInput* input = getColladaInput(ECIS_IMAGE); if (input) { const core::stringc imageName = input->Source; texture.Texture = getTextureFromImage(imageName, NULL); } } } //! reads a element and stores it in the material section void CColladaFileLoader::readMaterial(io::IXMLReaderUTF8* reader) { // add material to list of loaded materials. Materials.push_back(SColladaMaterial()); SColladaMaterial& material = Materials.getLast(); material.Id = readId(reader); #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading material", core::stringc(material.Id), ELL_DEBUG); #endif if (Version >= 10400) { while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT && instanceEffectName == reader->getNodeName()) { material.InstanceEffectId = reader->getAttributeValue("url"); uriToId(material.InstanceEffectId); } else if (reader->getNodeType() == io::EXN_ELEMENT_END && materialSectionName == reader->getNodeName()) { break; } } // end while reader->read(); } else { if (!reader->isEmptyElement()) { readColladaInputs(reader, materialSectionName); SColladaInput* input = getColladaInput(ECIS_TEXTURE); if (input) { core::stringc textureName = input->Source; uriToId(textureName); for (u32 i=0; iType == ECPT_FLOAT3) material.Mat.AmbientColor = video::SColorf(p->Floats[0],p->Floats[1],p->Floats[2]).toSColor(); p = getColladaParameter(ECPN_DIFFUSE); if (p && p->Type == ECPT_FLOAT3) material.Mat.DiffuseColor = video::SColorf(p->Floats[0],p->Floats[1],p->Floats[2]).toSColor(); p = getColladaParameter(ECPN_SPECULAR); if (p && p->Type == ECPT_FLOAT3) material.Mat.DiffuseColor = video::SColorf(p->Floats[0],p->Floats[1],p->Floats[2]).toSColor(); p = getColladaParameter(ECPN_SHININESS); if (p && p->Type == ECPT_FLOAT) material.Mat.Shininess = p->Floats[0]; #endif } } } void CColladaFileLoader::readEffect(io::IXMLReaderUTF8* reader, SColladaEffect * effect) { static const core::stringc constantNode("constant"); static const core::stringc lambertNode("lambert"); static const core::stringc phongNode("phong"); static const core::stringc blinnNode("blinn"); static const core::stringc emissionNode("emission"); static const core::stringc ambientNode("ambient"); static const core::stringc diffuseNode("diffuse"); static const core::stringc specularNode("specular"); static const core::stringc shininessNode("shininess"); static const core::stringc reflectiveNode("reflective"); static const core::stringc reflectivityNode("reflectivity"); static const core::stringc transparentNode("transparent"); static const core::stringc transparencyNode("transparency"); static const core::stringc indexOfRefractionNode("index_of_refraction"); if (!effect) { Effects.push_back(SColladaEffect()); effect = &Effects.getLast(); effect->Parameters = new io::CAttributes(); effect->Id = readId(reader); effect->Transparency = 1.f; effect->Mat.Lighting=true; effect->Mat.NormalizeNormals=true; #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading effect", core::stringc(effect->Id), ELL_DEBUG); #endif } while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { // first come the tags we descend, but ignore the top-levels if (!reader->isEmptyElement() && ((profileCOMMONSectionName == reader->getNodeName()) || (techniqueNodeName == reader->getNodeName()))) readEffect(reader,effect); else if (newParamName == reader->getNodeName()) readParameter(reader, effect->Parameters); else // these are the actual materials inside technique if (constantNode == reader->getNodeName() || lambertNode == reader->getNodeName() || phongNode == reader->getNodeName() || blinnNode == reader->getNodeName()) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading effect part", reader->getNodeName(), ELL_DEBUG); #endif effect->Mat.setFlag(irr::video::EMF_GOURAUD_SHADING, phongNode == reader->getNodeName() || blinnNode == reader->getNodeName()); while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { const core::stringc node = reader->getNodeName(); if (emissionNode == node || ambientNode == node || diffuseNode == node || specularNode == node || reflectiveNode == node || transparentNode == node ) { // color or texture types while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT && colorNodeName == reader->getNodeName()) { const video::SColorf colorf = readColorNode(reader); const video::SColor color = colorf.toSColor(); if (emissionNode == node) effect->Mat.EmissiveColor = color; else if (ambientNode == node) effect->Mat.AmbientColor = color; else if (diffuseNode == node) effect->Mat.DiffuseColor = color; else if (specularNode == node) effect->Mat.SpecularColor = color; else if (transparentNode == node) effect->Transparency = colorf.getAlpha(); } else if (reader->getNodeType() == io::EXN_ELEMENT && textureNodeName == reader->getNodeName()) { effect->Textures.push_back(reader->getAttributeValue("texture")); break; } else if (reader->getNodeType() == io::EXN_ELEMENT) skipSection(reader, false); else if (reader->getNodeType() == io::EXN_ELEMENT_END && node == reader->getNodeName()) break; } } else if (shininessNode == node || reflectivityNode == node || transparencyNode == node || indexOfRefractionNode == node ) { // float or param types while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT && floatNodeName == reader->getNodeName()) { f32 f = readFloatNode(reader); if (shininessNode == node) effect->Mat.Shininess = f; else if (transparencyNode == node) effect->Transparency *= f; } else if (reader->getNodeType() == io::EXN_ELEMENT) skipSection(reader, false); else if (reader->getNodeType() == io::EXN_ELEMENT_END && node == reader->getNodeName()) break; } } else skipSection(reader, true); // ignore all other nodes } else if (reader->getNodeType() == io::EXN_ELEMENT_END && ( constantNode == reader->getNodeName() || lambertNode == reader->getNodeName() || phongNode == reader->getNodeName() || blinnNode == reader->getNodeName() )) break; } } else if (!reader->isEmptyElement() && (extraNodeName == reader->getNodeName())) readEffect(reader,effect); else if (doubleSidedNodeName == reader->getNodeName()) { // read the GoogleEarth extra flag for double sided polys s32 doubleSided = 0; readIntsInsideElement(reader,&doubleSided,1); if (doubleSided) { #ifdef COLLADA_READER_DEBUG os::Printer::log("Setting double sided flag for effect.", ELL_DEBUG); #endif effect->Mat.setFlag(irr::video::EMF_BACK_FACE_CULLING,false); } } else skipSection(reader, true); // ignore all other sections } else if (reader->getNodeType() == io::EXN_ELEMENT_END) { if (effectSectionName == reader->getNodeName()) break; else if (profileCOMMONSectionName == reader->getNodeName()) break; else if (techniqueNodeName == reader->getNodeName()) break; else if (extraNodeName == reader->getNodeName()) break; } } if (effect->Mat.AmbientColor == video::SColor(0) && effect->Mat.DiffuseColor != video::SColor(0)) effect->Mat.AmbientColor = effect->Mat.DiffuseColor; if (effect->Mat.DiffuseColor == video::SColor(0) && effect->Mat.AmbientColor != video::SColor(0)) effect->Mat.DiffuseColor = effect->Mat.AmbientColor; if ((effect->Transparency != 0.0f) && (effect->Transparency != 1.0f)) { effect->Mat.MaterialType = irr::video::EMT_TRANSPARENT_VERTEX_ALPHA; effect->Mat.ZWriteEnable = false; } video::E_TEXTURE_CLAMP twu = video::ETC_REPEAT; s32 idx = effect->Parameters->findAttribute(wrapsName.c_str()); if ( idx >= 0 ) twu = (video::E_TEXTURE_CLAMP)(effect->Parameters->getAttributeAsInt(idx)); video::E_TEXTURE_CLAMP twv = video::ETC_REPEAT; idx = effect->Parameters->findAttribute(wraptName.c_str()); if ( idx >= 0 ) twv = (video::E_TEXTURE_CLAMP)(effect->Parameters->getAttributeAsInt(idx)); video::E_TEXTURE_CLAMP twr = video::ETC_REPEAT; idx = effect->Parameters->findAttribute(wrappName.c_str()); if ( idx >= 0 ) twr = (video::E_TEXTURE_CLAMP)(effect->Parameters->getAttributeAsInt(idx)); else { // for downward compatibility with older Irrlicht collada writer idx = effect->Parameters->findAttribute(wraprName.c_str()); if ( idx >= 0 ) twr = (video::E_TEXTURE_CLAMP)(effect->Parameters->getAttributeAsInt(idx)); } for (u32 i=0; iMat.TextureLayer[i].TextureWrapU = twu; effect->Mat.TextureLayer[i].TextureWrapV = twv; effect->Mat.TextureLayer[i].TextureWrapW = twr; } effect->Mat.setFlag(video::EMF_BILINEAR_FILTER, effect->Parameters->getAttributeAsBool("bilinear")); effect->Mat.setFlag(video::EMF_TRILINEAR_FILTER, effect->Parameters->getAttributeAsBool("trilinear")); effect->Mat.setFlag(video::EMF_ANISOTROPIC_FILTER, effect->Parameters->getAttributeAsBool("anisotropic")); } const SColladaMaterial* CColladaFileLoader::findMaterial(const core::stringc& materialName) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA find material", materialName, ELL_DEBUG); #endif // do a quick lookup in the materials SColladaMaterial matToFind; matToFind.Id = materialName; s32 mat = Materials.binary_search(matToFind); if (mat == -1) return 0; // instantiate the material effect if needed if (Materials[mat].InstanceEffectId.size() != 0) { // do a quick lookup in the effects SColladaEffect effectToFind; effectToFind.Id = Materials[mat].InstanceEffectId; s32 effect = Effects.binary_search(effectToFind); if (effect != -1) { // found the effect, instantiate by copying into the material Materials[mat].Mat = Effects[effect].Mat; if (Effects[effect].Textures.size()) Materials[mat].Mat.setTexture(0, getTextureFromImage(Effects[effect].Textures[0], &(Effects[effect]))); Materials[mat].Transparency = Effects[effect].Transparency; // and indicate the material is instantiated by removing the effect ref Materials[mat].InstanceEffectId = ""; } else return 0; } return &Materials[mat]; } void CColladaFileLoader::readBindMaterialSection(io::IXMLReaderUTF8* reader, const core::stringc & id) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading bind material", ELL_DEBUG); #endif while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { if (instanceMaterialName == reader->getNodeName()) { // the symbol to retarget, and the target material core::stringc meshbufferReference = reader->getAttributeValue("symbol"); if (meshbufferReference.size()==0) continue; core::stringc target = reader->getAttributeValue("target"); uriToId(target); if (target.size()==0) continue; const SColladaMaterial * material = findMaterial(target); if (!material) continue; // bind any pending materials for this node meshbufferReference = id+"/"+meshbufferReference; #ifdef COLLADA_READER_DEBUG os::Printer::log((core::stringc("Material binding: ")+meshbufferReference+" "+target).c_str(), ELL_DEBUG); #endif if (MaterialsToBind.find(meshbufferReference)) { core::array & toBind = MeshesToBind[MaterialsToBind[meshbufferReference]]; #ifdef COLLADA_READER_DEBUG os::Printer::log("Material binding now ",material->Id.c_str(), ELL_DEBUG); os::Printer::log("#meshbuffers",core::stringc(toBind.size()).c_str(), ELL_DEBUG); #endif SMesh tmpmesh; for (u32 i = 0; i < toBind.size(); ++i) { toBind[i]->getMaterial() = material->Mat; tmpmesh.addMeshBuffer(toBind[i]); if ((material->Transparency!=0.0f) && (material->Transparency!=1.0f)) { toBind[i]->getMaterial().MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; toBind[i]->getMaterial().ZWriteEnable = false; } } SceneManager->getMeshManipulator()->setVertexColors(&tmpmesh,material->Mat.DiffuseColor); if ((material->Transparency!=0.0f) && (material->Transparency!=1.0f)) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA found transparency material", core::stringc(material->Transparency).c_str(), ELL_DEBUG); #endif SceneManager->getMeshManipulator()->setVertexColorAlpha(&tmpmesh, core::floor32(material->Transparency*255.0f)); } } } } else if (reader->getNodeType() == io::EXN_ELEMENT_END && bindMaterialName == reader->getNodeName()) break; } } //! reads a element and stores it as mesh if possible void CColladaFileLoader::readGeometry(io::IXMLReaderUTF8* reader) { core::stringc id = readId(reader); #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading geometry", id, ELL_DEBUG); #endif SAnimatedMesh* amesh = new SAnimatedMesh(); scene::SMesh* mesh = new SMesh(); amesh->addMesh(mesh); core::array sources; bool okToReadArray = false; // handles geometry node and the mesh children in this loop // read sources with arrays and accessor for each mesh if (!reader->isEmptyElement()) while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { const char* nodeName = reader->getNodeName(); if (meshSectionName == nodeName) { // inside a mesh section. Don't have to do anything here. } else if (sourceSectionName == nodeName) { // create a new source sources.push_back(SSource()); sources.getLast().Id = readId(reader); #ifdef COLLADA_READER_DEBUG os::Printer::log("Reading source", sources.getLast().Id.c_str(), ELL_DEBUG); #endif } else if (arraySectionName == nodeName || floatArraySectionName == nodeName || intArraySectionName == nodeName) { // create a new array and read it. if (!sources.empty()) { sources.getLast().Array.Name = readId(reader); int count = reader->getAttributeValueAsInt("count"); sources.getLast().Array.Data.set_used(count); // pre allocate // check if type of array is ok const char* type = reader->getAttributeValue("type"); okToReadArray = (type && (!strcmp("float", type) || !strcmp("int", type))) || floatArraySectionName == nodeName || intArraySectionName == nodeName; #ifdef COLLADA_READER_DEBUG os::Printer::log("Read array", sources.getLast().Array.Name.c_str(), ELL_DEBUG); #endif } #ifdef COLLADA_READER_DEBUG else os::Printer::log("Warning, array outside source found", readId(reader).c_str(), ELL_DEBUG); #endif } else if (accessorSectionName == nodeName) // child of source (below a technique tag) { #ifdef COLLADA_READER_DEBUG os::Printer::log("Reading accessor", ELL_DEBUG); #endif SAccessor accessor; accessor.Count = reader->getAttributeValueAsInt("count"); accessor.Offset = reader->getAttributeValueAsInt("offset"); accessor.Stride = reader->getAttributeValueAsInt("stride"); if (accessor.Stride == 0) accessor.Stride = 1; // the accessor contains some information on how to access (boi!) the array, // the info is stored in collada style parameters, so just read them. readColladaParameters(reader, accessorSectionName); if (!sources.empty()) { sources.getLast().Accessors.push_back(accessor); sources.getLast().Accessors.getLast().Parameters = ColladaParameters; } } else if (verticesSectionName == nodeName) { #ifdef COLLADA_READER_DEBUG os::Printer::log("Reading vertices", ELL_DEBUG); #endif // read vertex input position source readColladaInputs(reader, verticesSectionName); } else // lines and linestrips missing if (polygonsSectionName == nodeName || polylistSectionName == nodeName || trianglesSectionName == nodeName) { // read polygons section readPolygonSection(reader, sources, mesh, id); } else // trifans, and tristrips missing if (doubleSidedNodeName == reader->getNodeName()) { // read the extra flag for double sided polys s32 doubleSided = 0; readIntsInsideElement(reader,&doubleSided,1); if (doubleSided) { #ifdef COLLADA_READER_DEBUG os::Printer::log("Setting double sided flag for mesh.", ELL_DEBUG); #endif amesh->setMaterialFlag(irr::video::EMF_BACK_FACE_CULLING,false); } } else // techniqueCommon or 'technique profile=common' must not be skipped if ((techniqueCommonSectionName != nodeName) // Collada 1.2/1.3 && (techniqueNodeName != nodeName) // Collada 1.4+ && (extraNodeName != nodeName)) { os::Printer::log("COLLADA loader warning: Wrong tag usage found in geometry", reader->getNodeName(), ELL_WARNING); skipSection(reader, true); // ignore all other sections } } // end if node type is element else if (reader->getNodeType() == io::EXN_TEXT) { // read array data if (okToReadArray && !sources.empty()) { core::array& a = sources.getLast().Array.Data; core::stringc data = reader->getNodeData(); data.trim(); const c8* p = &data[0]; for (u32 i=0; igetNodeType() == io::EXN_ELEMENT_END) { if (geometrySectionName == reader->getNodeName()) { // end of geometry section reached, cancel out break; } } } // end while reader->read(); // add mesh as geometry mesh->recalculateBoundingBox(); amesh->recalculateBoundingBox(); // create virtual file name io::path filename = CurrentlyLoadingMesh; filename += '#'; filename += id; // add to scene manager if (LoadedMeshCount) { SceneManager->getMeshCache()->addMesh(filename.c_str(), amesh); os::Printer::log("Added COLLADA mesh", filename.c_str(), ELL_DEBUG); } else { FirstLoadedMeshName = filename; FirstLoadedMesh = amesh; FirstLoadedMesh->grab(); } ++LoadedMeshCount; mesh->drop(); amesh->drop(); // create geometry prefab u32 i; for (i=0; igetId()==id) { ((CGeometryPrefab*)Prefabs[i])->Mesh=mesh; break; } } if (i==Prefabs.size()) { CGeometryPrefab* prefab = new CGeometryPrefab(id); prefab->Mesh = mesh; Prefabs.push_back(prefab); } // store as dummy mesh if no instances will be created if (!CreateInstances && !DummyMesh) { DummyMesh = amesh; DummyMesh->grab(); } } struct SPolygon { core::array Indices; }; //! reads a polygons section and creates a mesh from it void CColladaFileLoader::readPolygonSection(io::IXMLReaderUTF8* reader, core::array& sources, scene::SMesh* mesh, const core::stringc& geometryId) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading polygon section", ELL_DEBUG); #endif core::stringc materialName = reader->getAttributeValue("material"); core::stringc polygonType = reader->getNodeName(); const int polygonCount = reader->getAttributeValueAsInt("count"); // Not useful because it only determines the number of primitives, which have arbitrary vertices in case of polygon core::array polygons; if (polygonType == polygonsSectionName) polygons.reallocate(polygonCount); core::array vCounts; bool parsePolygonOK = false; bool parseVcountOK = false; u32 inputSemanticCount = 0; u32 maxOffset = 0; core::array localInputs; // read all and primitives if (!reader->isEmptyElement()) while(reader->read()) { const char* nodeName = reader->getNodeName(); if (reader->getNodeType() == io::EXN_ELEMENT) { // polygon node may contain params if (inputTagName == nodeName) { // read input tag readColladaInput(reader, localInputs); // resolve input source SColladaInput& inp = localInputs.getLast(); // get input source array id, if it is a vertex input, take // the -source attribute. if (inp.Semantic == ECIS_VERTEX) { inp.Source = Inputs[0].Source; for (u32 i=1; igetNodeType() == io::EXN_ELEMENT_END) { if (primitivesName == nodeName) parsePolygonOK = false; // end parsing a polygon else if (vcountName == nodeName) parseVcountOK = false; // end parsing vcounts else if (polygonType == nodeName) break; // cancel out and create mesh } // end is element end else if (reader->getNodeType() == io::EXN_TEXT) { if (parseVcountOK) { core::stringc data = reader->getNodeData(); data.trim(); const c8* p = &data[0]; while(*p) { findNextNoneWhiteSpace(&p); if (*p) vCounts.push_back(readInt(&p)); } parseVcountOK = false; } else if (parsePolygonOK && polygons.size()) { core::stringc data = reader->getNodeData(); data.trim(); const c8* p = &data[0]; SPolygon& poly = polygons.getLast(); if (polygonType == polygonsSectionName) poly.Indices.reallocate((maxOffset+1)*3); else poly.Indices.reallocate(polygonCount*(maxOffset+1)*3); if (vCounts.empty()) { while(*p) { findNextNoneWhiteSpace(&p); poly.Indices.push_back(readInt(&p)); } } else { for (u32 i = 0; i < vCounts.size(); i++) { const int polyVCount = vCounts[i]; core::array polyCorners; for (u32 j = 0; j < polyVCount * inputSemanticCount; j++) { if (!*p) break; findNextNoneWhiteSpace(&p); polyCorners.push_back(readInt(&p)); } while (polyCorners.size() >= 3 * inputSemanticCount) { // add one triangle's worth of indices for (u32 k = 0; k < inputSemanticCount * 3; ++k) { poly.Indices.push_back(polyCorners[k]); } // remove one corner from our poly polyCorners.erase(inputSemanticCount,inputSemanticCount); } polyCorners.clear(); } vCounts.clear(); } parsePolygonOK = false; } } } // end while reader->read() // find source array (we'll ignore accessors for this implementation) for (u32 i=0; i vertMap; for (u32 i=0; i indices; const u32 vertexCount = polygons[i].Indices.size() / maxOffset; mbuffer->Vertices.reallocate(mbuffer->Vertices.size()+vertexCount); // for all index/semantic groups for (u32 v=0; v::Node* n = vertMap.find(vtx); if (n) { indices.push_back(n->getValue()); } else { indices.push_back(mbuffer->getVertexCount()); mbuffer->Vertices.push_back(vtx); vertMap.insert(vtx, mbuffer->getVertexCount()-1); } } // end for all vertices if (polygonsSectionName == polygonType && indices.size() > 3) { // need to tessellate for polygons of 4 or more vertices // for now we naively turn interpret it as a triangle fan // as full tessellation is problematic for (u32 ind = 0; ind+2 < indices.size(); ++ind) { mbuffer->Indices.push_back(indices[0]); mbuffer->Indices.push_back(indices[ind+2]); mbuffer->Indices.push_back(indices[ind+1]); } } else { // it's just triangles for (u32 ind = 0; ind < indices.size(); ind+=3) { mbuffer->Indices.push_back(indices[ind+2]); mbuffer->Indices.push_back(indices[ind+1]); mbuffer->Indices.push_back(indices[ind+0]); } } } // end for all polygons } else { // lightmap mesh buffer scene::SMeshBufferLightMap* mbuffer = new SMeshBufferLightMap(); buffer = mbuffer; for (u32 i=0; iVertices.reallocate(mbuffer->Vertices.size()+vertexCount); // for all vertices in array for (u32 v=0; vVertices.push_back(vtx); } // end for all vertices // add vertex indices const u32 oldVertexCount = mbuffer->Vertices.size() - vertexCount; for (u32 face=0; faceIndices.push_back(oldVertexCount + 0); mbuffer->Indices.push_back(oldVertexCount + 1 + face); mbuffer->Indices.push_back(oldVertexCount + 2 + face); } } // end for all polygons } const SColladaMaterial* m = findMaterial(materialName); if (m) { buffer->getMaterial() = m->Mat; SMesh tmpmesh; tmpmesh.addMeshBuffer(buffer); SceneManager->getMeshManipulator()->setVertexColors(&tmpmesh,m->Mat.DiffuseColor); if (m->Transparency != 1.0f) SceneManager->getMeshManipulator()->setVertexColorAlpha(&tmpmesh,core::floor32(m->Transparency*255.0f)); } // add future bind reference for the material core::stringc meshbufferReference = geometryId+"/"+materialName; if (!MaterialsToBind.find(meshbufferReference)) { MaterialsToBind[meshbufferReference] = MeshesToBind.size(); MeshesToBind.push_back(core::array()); } MeshesToBind[MaterialsToBind[meshbufferReference]].push_back(buffer); // calculate normals if there is no slot for it if (!normalSlotCount) SceneManager->getMeshManipulator()->recalculateNormals(buffer, true); // recalculate bounding box buffer->recalculateBoundingBox(); // add mesh buffer mesh->addMeshBuffer(buffer); #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA added meshbuffer", core::stringc(buffer->getVertexCount())+" vertices, "+core::stringc(buffer->getIndexCount())+" indices.", ELL_DEBUG); #endif buffer->drop(); } //! reads a element and stores it as prefab void CColladaFileLoader::readLightPrefab(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading light prefab", ELL_DEBUG); #endif CLightPrefab* prefab = new CLightPrefab(readId(reader)); if (!reader->isEmptyElement()) { if (Version >= 10400) // start with 1.4 { while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { if (pointSectionName == reader->getNodeName()) prefab->LightData.Type=video::ELT_POINT; else if (directionalSectionName == reader->getNodeName()) prefab->LightData.Type=video::ELT_DIRECTIONAL; else if (spotSectionName == reader->getNodeName()) prefab->LightData.Type=video::ELT_SPOT; else if (ambientSectionName == reader->getNodeName()) prefab->LightData.Type=ELT_AMBIENT; else if (colorNodeName == reader->getNodeName()) prefab->LightData.DiffuseColor=readColorNode(reader); else if (constantAttenuationNodeName == reader->getNodeName()) readFloatsInsideElement(reader,&prefab->LightData.Attenuation.X,1); else if (linearAttenuationNodeName == reader->getNodeName()) readFloatsInsideElement(reader,&prefab->LightData.Attenuation.Y,1); else if (quadraticAttenuationNodeName == reader->getNodeName()) readFloatsInsideElement(reader,&prefab->LightData.Attenuation.Z,1); else if (falloffAngleNodeName == reader->getNodeName()) { readFloatsInsideElement(reader,&prefab->LightData.OuterCone,1); prefab->LightData.OuterCone *= core::DEGTORAD; } else if (falloffExponentNodeName == reader->getNodeName()) readFloatsInsideElement(reader,&prefab->LightData.Falloff,1); } else if (reader->getNodeType() == io::EXN_ELEMENT_END) { if ((pointSectionName == reader->getNodeName()) || (directionalSectionName == reader->getNodeName()) || (spotSectionName == reader->getNodeName()) || (ambientSectionName == reader->getNodeName())) break; } } } else { readColladaParameters(reader, lightPrefabName); SColladaParam* p = getColladaParameter(ECPN_COLOR); if (p && p->Type == ECPT_FLOAT3) prefab->LightData.DiffuseColor.set(p->Floats[0], p->Floats[1], p->Floats[2]); } } Prefabs.push_back(prefab); } //! returns a collada parameter or none if not found SColladaParam* CColladaFileLoader::getColladaParameter(ECOLLADA_PARAM_NAME name) { for (u32 i=0; i& inputs) { // parse param SColladaInput p; // get type core::stringc semanticName = reader->getAttributeValue("semantic"); for (u32 i=0; inputSemanticNames[i]; ++i) { if (semanticName == inputSemanticNames[i]) { p.Semantic = (ECOLLADA_INPUT_SEMANTIC)i; break; } } // get source p.Source = reader->getAttributeValue("source"); if (reader->getAttributeValue("offset")) // Collada 1.4+ p.Offset = (u32)reader->getAttributeValueAsInt("offset"); else // Collada 1.2/1.3 p.Offset = (u32)reader->getAttributeValueAsInt("idx"); p.Set = (u32)reader->getAttributeValueAsInt("set"); // add input inputs.push_back(p); } //! parses all collada inputs inside an element and stores them in Inputs void CColladaFileLoader::readColladaInputs(io::IXMLReaderUTF8* reader, const core::stringc& parentName) { Inputs.clear(); while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT && inputTagName == reader->getNodeName()) { readColladaInput(reader, Inputs); } else if (reader->getNodeType() == io::EXN_ELEMENT_END) { if (parentName == reader->getNodeName()) return; // end of parent reached } } // end while reader->read(); } //! parses all collada parameters inside an element and stores them in ColladaParameters void CColladaFileLoader::readColladaParameters(io::IXMLReaderUTF8* reader, const core::stringc& parentName) { ColladaParameters.clear(); const char* const paramNames[] = {"COLOR", "AMBIENT", "DIFFUSE", "SPECULAR", "SHININESS", "YFOV", "ZNEAR", "ZFAR", 0}; const char* const typeNames[] = {"float", "float2", "float3", 0}; while(reader->read()) { const char* nodeName = reader->getNodeName(); if (reader->getNodeType() == io::EXN_ELEMENT && paramTagName == nodeName) { // parse param SColladaParam p; // get type u32 i; core::stringc typeName = reader->getAttributeValue("type"); for (i=0; typeNames[i]; ++i) if (typeName == typeNames[i]) { p.Type = (ECOLLADA_PARAM_TYPE)i; break; } // get name core::stringc nameName = reader->getAttributeValue("name"); for (i=0; typeNames[i]; ++i) if (nameName == paramNames[i]) { p.Name = (ECOLLADA_PARAM_NAME)i; break; } // read parameter data inside parameter tags switch(p.Type) { case ECPT_FLOAT: case ECPT_FLOAT2: case ECPT_FLOAT3: case ECPT_FLOAT4: readFloatsInsideElement(reader, p.Floats, p.Type - ECPT_FLOAT + 1); break; // TODO: other types of data (ints, bools or whatever) default: break; } // add param ColladaParameters.push_back(p); } else if (reader->getNodeType() == io::EXN_ELEMENT_END) { if (parentName == reader->getNodeName()) return; // end of parent reached } } // end while reader->read(); } //! parses a float from a char pointer and moves the pointer //! to the end of the parsed float inline f32 CColladaFileLoader::readFloat(const c8** p) { f32 ftmp; *p = core::fast_atof_move(*p, ftmp); return ftmp; } //! parses an int from a char pointer and moves the pointer to //! the end of the parsed float inline s32 CColladaFileLoader::readInt(const c8** p) { return (s32)readFloat(p); } //! places pointer to next begin of a token void CColladaFileLoader::findNextNoneWhiteSpace(const c8** start) { const c8* p = *start; while(*p && (*p==' ' || *p=='\n' || *p=='\r' || *p=='\t')) ++p; // TODO: skip comments *start = p; } //! reads floats from inside of xml element until end of xml element void CColladaFileLoader::readFloatsInsideElement(io::IXMLReaderUTF8* reader, f32* floats, u32 count) { if (reader->isEmptyElement()) return; while(reader->read()) { // TODO: check for comments inside the element // and ignore them. if (reader->getNodeType() == io::EXN_TEXT) { // parse float data core::stringc data = reader->getNodeData(); data.trim(); const c8* p = &data[0]; for (u32 i=0; igetNodeType() == io::EXN_ELEMENT_END) break; // end parsing text } } //! reads ints from inside of xml element until end of xml element void CColladaFileLoader::readIntsInsideElement(io::IXMLReaderUTF8* reader, s32* ints, u32 count) { if (reader->isEmptyElement()) return; while(reader->read()) { // TODO: check for comments inside the element // and ignore them. if (reader->getNodeType() == io::EXN_TEXT) { // parse float data core::stringc data = reader->getNodeData(); data.trim(); const c8* p = &data[0]; for (u32 i=0; igetNodeType() == io::EXN_ELEMENT_END) break; // end parsing text } } video::SColorf CColladaFileLoader::readColorNode(io::IXMLReaderUTF8* reader) { if (reader->getNodeType() == io::EXN_ELEMENT && colorNodeName == reader->getNodeName()) { f32 color[4]; readFloatsInsideElement(reader,color,4); return video::SColorf(color[0], color[1], color[2], color[3]); } return video::SColorf(); } f32 CColladaFileLoader::readFloatNode(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading ", ELL_DEBUG); #endif f32 result = 0.0f; if (reader->getNodeType() == io::EXN_ELEMENT && floatNodeName == reader->getNodeName()) { readFloatsInsideElement(reader,&result,1); } return result; } //! clears all loaded data void CColladaFileLoader::clearData() { // delete all prefabs for (u32 i=0; idrop(); Prefabs.clear(); // clear all parameters ColladaParameters.clear(); // clear all materials Images.clear(); // clear all materials Textures.clear(); // clear all materials Materials.clear(); // clear all inputs Inputs.clear(); // clear all effects for ( u32 i=0; idrop(); Effects.clear(); // clear all the materials to bind MaterialsToBind.clear(); MeshesToBind.clear(); } //! changes the XML URI into an internal id void CColladaFileLoader::uriToId(core::stringc& str) { // Currently, we only remove the # from the beginning // as we don't support referencing other files. if (!str.size()) return; if (str[0] == '#') str.erase(0); } //! read Collada Id, uses id or name if id is missing core::stringc CColladaFileLoader::readId(io::IXMLReaderUTF8* reader) { core::stringc id = reader->getAttributeValue("id"); if (id.size()==0) id = reader->getAttributeValue("name"); return id; } //! create an Irrlicht texture from the reference video::ITexture* CColladaFileLoader::getTextureFromImage(core::stringc uri, SColladaEffect * effect) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA searching texture", uri, ELL_DEBUG); #endif video::IVideoDriver* driver = SceneManager->getVideoDriver(); for (;;) { uriToId(uri); for (u32 i=0; igetTexture( Images[i].Source ) : NULL; } else if (Images[i].Source.size()) { //const u32 size = Images[i].Dimension.getArea(); const u32 size = Images[i].Dimension.Width * Images[i].Dimension.Height;; u32* data = new u32[size]; // we assume RGBA u32* ptrdest = data; const c8* ptrsrc = Images[i].Source.c_str(); for (u32 j=0; jcreateImageFromData(video::ECF_A8R8G8B8, Images[i].Dimension, data, true, true); video::ITexture* tex = driver->addTexture((CurrentlyLoadingMesh+"#"+Images[i].Id).c_str(), img); img->drop(); return tex; } break; } } if (effect && effect->Parameters->getAttributeType(uri.c_str())==io::EAT_STRING) { uri = effect->Parameters->getAttributeAsString(uri.c_str()); #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA now searching texture", uri.c_str(), ELL_DEBUG); #endif } else break; } return 0; } //! read a parameter and value void CColladaFileLoader::readParameter(io::IXMLReaderUTF8* reader, io::IAttributes* parameters) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading parameter", ELL_DEBUG); #endif if ( !parameters ) return; const core::stringc name = reader->getAttributeValue("sid"); if (!reader->isEmptyElement()) { while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { if (floatNodeName == reader->getNodeName()) { const f32 f = readFloatNode(reader); parameters->addFloat(name.c_str(), f); } else if (float2NodeName == reader->getNodeName()) { f32 f[2]; readFloatsInsideElement(reader, f, 2); // Parameters.addVector2d(name.c_str(), core::vector2df(f[0],f[1])); } else if (float3NodeName == reader->getNodeName()) { f32 f[3]; readFloatsInsideElement(reader, f, 3); parameters->addVector3d(name.c_str(), core::vector3df(f[0],f[1],f[2])); } else if ((initFromName == reader->getNodeName()) || (sourceSectionName == reader->getNodeName())) { reader->read(); parameters->addString(name.c_str(), reader->getNodeData()); } else if (wrapsName == reader->getNodeName()) { reader->read(); const core::stringc val = reader->getNodeData(); if (val == "WRAP") parameters->addInt(wrapsName.c_str(), (int)video::ETC_REPEAT); else if ( val== "MIRROR") parameters->addInt(wrapsName.c_str(), (int)video::ETC_MIRROR); else if ( val== "CLAMP") parameters->addInt(wrapsName.c_str(), (int)video::ETC_CLAMP_TO_EDGE); else if ( val== "BORDER") parameters->addInt(wrapsName.c_str(), (int)video::ETC_CLAMP_TO_BORDER); else if ( val== "NONE") parameters->addInt(wrapsName.c_str(), (int)video::ETC_CLAMP_TO_BORDER); } else if (wraptName == reader->getNodeName()) { reader->read(); const core::stringc val = reader->getNodeData(); if (val == "WRAP") parameters->addInt(wraptName.c_str(), (int)video::ETC_REPEAT); else if ( val== "MIRROR") parameters->addInt(wraptName.c_str(), (int)video::ETC_MIRROR); else if ( val== "CLAMP") parameters->addInt(wraptName.c_str(), (int)video::ETC_CLAMP_TO_EDGE); else if ( val== "BORDER") parameters->addInt(wraptName.c_str(), (int)video::ETC_CLAMP_TO_BORDER); else if ( val== "NONE") parameters->addInt(wraptName.c_str(), (int)video::ETC_CLAMP_TO_BORDER); } else if (minfilterName == reader->getNodeName()) { reader->read(); const core::stringc val = reader->getNodeData(); if (val == "LINEAR_MIPMAP_LINEAR") parameters->addBool("trilinear", true); else if (val == "LINEAR_MIPMAP_NEAREST") parameters->addBool("bilinear", true); } else if (magfilterName == reader->getNodeName()) { reader->read(); const core::stringc val = reader->getNodeData(); if (val != "LINEAR") { parameters->addBool("bilinear", false); parameters->addBool("trilinear", false); } } else if (mipfilterName == reader->getNodeName()) { parameters->addBool("anisotropic", true); } } else if(reader->getNodeType() == io::EXN_ELEMENT_END) { if (newParamName == reader->getNodeName()) break; } } } } core::matrix4 CColladaFileLoader::flipZAxis(const core::matrix4& m) { core::matrix4 matrix(m); matrix[2] *= -1.f; matrix[6] *= -1.f; matrix[8] *= -1.f; matrix[9] *= -1.f; matrix[11] *= -1.f; matrix[14] *= -1.f; return matrix; } void CColladaFileLoader::unescape(irr::core::stringc& uri) { u32 len = uri.size(); for (u32 i=0; i