// 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 "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 bboxNodeName = "boundingbox"; 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 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 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", 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) { // empty implementation return 0; } //! returns id of this prefab virtual const core::stringc& getId() { 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) { #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) { #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) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA: Constructing camera instance", Id.c_str(), ELL_DEBUG); #endif scene::ICameraSceneNode* c = mgr->addCameraSceneNode(parent); 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) { #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 } //! 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; 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 core::aabbox3df bbox; 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 (bboxNodeName == reader->getNodeName()) readBboxNode(reader, bbox); 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 core::aabbox3df bbox; 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 (bboxNodeName == reader->getNodeName()) readBboxNode(reader, bbox); else if ((instanceName == reader->getNodeName()) || (instanceNodeName == reader->getNodeName()) || (instanceGeometryName == reader->getNodeName()) || (instanceLightName == 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); mat.buildCameraLookAtMatrixLH( core::vector3df(floats[0], floats[1], floats[2]), core::vector3df(floats[3], floats[4], floats[5]), core::vector3df(floats[6], floats[7], floats[8])); 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 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; } return mat; } //! reads a element and its content and stores it in bbox void CColladaFileLoader::readBboxNode(io::IXMLReaderUTF8* reader, core::aabbox3df& bbox) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading boundingbox node", ELL_DEBUG); #endif bbox.reset(core::aabbox3df()); if (reader->isEmptyElement()) return; f32 floats[3]; while(reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT) { if (minNodeName == reader->getNodeName()) { readFloatsInsideElement(reader, floats, 3); bbox.MinEdge.set(floats[0], floats[1], floats[2]); } else if (maxNodeName == reader->getNodeName()) { readFloatsInsideElement(reader, floats, 3); bbox.MaxEdge.set(floats[0], floats[1], floats[2]); } else skipSection(reader, true); // ignore all other sections } else if (reader->getNodeType() == io::EXN_ELEMENT_END) { if (bboxNodeName == reader->getNodeName()) break; } } } //! 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 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 perspecitve 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); 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])); 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])); 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(); 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)); for (u32 i=0; iMat.TextureLayer[i].TextureWrapU = twu; effect->Mat.TextureLayer[i].TextureWrapV = twv; } 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; bool unresolvedInput=false; 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 tesselate for polygons of 4 or more vertices // for now we naively turn interpret it as a triangle fan // as full tesselation is problematic if (FlipAxis) { for (u32 ind = indices.size()-3; ind>0 ; --ind) { mbuffer->Indices.push_back(indices[0]); mbuffer->Indices.push_back(indices[ind+2]); mbuffer->Indices.push_back(indices[ind+1]); } } else { for (u32 ind = 0; ind+2 < indices.size(); ++ind) { mbuffer->Indices.push_back(indices[0]); mbuffer->Indices.push_back(indices[ind+1]); mbuffer->Indices.push_back(indices[ind+2]); } } } else { // it's just triangles for (u32 ind = 0; ind < indices.size(); ind+=3) { if (FlipAxis) { mbuffer->Indices.push_back(indices[ind+2]); mbuffer->Indices.push_back(indices[ind+1]); mbuffer->Indices.push_back(indices[ind+0]); } else { mbuffer->Indices.push_back(indices[ind+0]); mbuffer->Indices.push_back(indices[ind+1]); mbuffer->Indices.push_back(indices[ind+2]); } } } } // 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 begin if there // because we simply 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; iexistFile(Images[i].Source)) return driver->getTexture(Images[i].Source); return driver->getTexture((FileSystem->getFileDir(CurrentlyLoadingMesh)+"/"+Images[i].Source)); } 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; } } } } } // end namespace scene } // end namespace irr #endif // _IRR_COMPILE_WITH_COLLADA_LOADER_