// Copyright (C) 2002-2007 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #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" //#define COLLADA_READER_DEBUG namespace irr { namespace scene { // currently supported COLLADA tag names const core::stringc colladaSectionName = "COLLADA"; const core::stringc librarySectionName = "library"; const core::stringc assetSectionName = "asset"; const core::stringc sceneSectionName = "scene"; const core::stringc lightPrefabName = "light"; const core::stringc cameraPrefabName = "camera"; const core::stringc materialSectionName = "material"; const core::stringc geometrySectionName = "geometry"; const core::stringc meshSectionName = "mesh"; const core::stringc sourceSectionName = "source"; const core::stringc arraySectionName = "array"; const core::stringc accessorSectionName = "accessor"; const core::stringc verticesSectionName = "vertices"; const core::stringc inputTagName = "input"; const core::stringc polygonsSectionName = "polygons"; const core::stringc polygonName = "p"; 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 instanceNodeName = "instance"; const core::stringc paramTagName = "param"; const char* const inputSemanticNames[] = {"POSITION", "VERTEX", "NORMAL", "TEXCOORD", "UV", "TANGENT", 0}; //! following class is for holding and creating instances of library objects, //! named prefabs in this loader. class CPrefab : public IColladaPrefab { public: CPrefab(const char* 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 c8* getId() { return Id.c_str(); } protected: core::stringc Id; }; //! prefab for a light scene node class CLightPrefab : public CPrefab { public: CLightPrefab(const char* id) : CPrefab(id) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA: loaded light prefab:", Id.c_str()); #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()); #endif scene::ILightSceneNode* l = mgr->addLightSceneNode(parent); l->setLightData ( LightData ); return l; } }; //! prefab for a mesh scene node class CGeometryPrefab : public CPrefab { public: CGeometryPrefab(const char* id) : CPrefab(id) { } scene::IMesh* Mesh; // public 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 mesh instance:", Id.c_str()); #endif scene::ISceneNode* m = mgr->addMeshSceneNode(Mesh, parent); return m; } }; //! prefab for a camera scene node class CCameraPrefab : public CPrefab { public: CCameraPrefab(const char* 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()); #endif } // public 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()); #endif scene::ICameraSceneNode* c = mgr->addCameraSceneNode(parent); c->setFOV(YFov); c->setNearValue(ZNear); c->setFarValue(ZFar); return c; } }; //! Constructor CColladaFileLoader::CColladaFileLoader(video::IVideoDriver* driver, scene::ISceneManager* smgr, io::IFileSystem* fs) : Driver(driver), SceneManager(smgr), FileSystem(fs), DummyMesh(0), FirstLoadedMesh(0), LoadedMeshCount(0), CreateInstances(false) { } //! 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 c8* fileName) { return strstr(fileName, ".xml") || strstr(fileName, ".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 IUnknown::drop() for more information. IAnimatedMesh* CColladaFileLoader::createMesh(irr::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); // 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(); scene::IAnimatedMesh* returnMesh = DummyMesh; // 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(); returnMesh = DummyMesh; } // 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()); // skip if this element is empty anyway. if (reader->isEmptyElement()) return; // read until we've reached the last element in this section int tagCounter = 1; while(tagCounter && reader->read()) { if (reader->getNodeType() == io::EXN_ELEMENT && !reader->isEmptyElement()) { #ifdef COLLADA_READER_DEBUG if (reportSkipping) os::Printer::log("COLLADA unknown element:", core::stringc(reader->getNodeName()).c_str()); #endif // COLLADA_READER_DEBUG ++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; // I ignore version information here. Keep on reading content: while(reader->read()) if (reader->getNodeType() == io::EXN_ELEMENT) { if (librarySectionName == reader->getNodeName()) readLibrarySection(reader); else if (assetSectionName == reader->getNodeName()) readAssetSection(reader); else if (sceneSectionName == reader->getNodeName()) readSceneSection(reader); else skipSection(reader, true); // unknown section } } //! reads a section and its content void CColladaFileLoader::readLibrarySection(io::IXMLReaderUTF8* reader) { while(reader->read()) if (reader->getNodeType() == io::EXN_ELEMENT) { if (lightPrefabName == reader->getNodeName()) readLightPrefab(reader); else if (materialSectionName == reader->getNodeName()) readMaterial(reader); else if (cameraPrefabName == reader->getNodeName()) readCameraPrefab(reader); else if (geometrySectionName == reader->getNodeName()) readGeometry(reader); else skipSection(reader, true); // unknown section } else if (reader->getNodeType() == io::EXN_ELEMENT_END) { if (librarySectionName == reader->getNodeName()) break; // end reading. } } //! reads a section and its content void CColladaFileLoader::readSceneSection(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading scene"); #endif if (reader->isEmptyElement()) return; // read the scene while(reader->read()) if (reader->getNodeType() == io::EXN_ELEMENT) { if (nodeSectionName == reader->getNodeName()) readNodeSection(reader, SceneManager->getRootSceneNode()); else skipSection(reader, true); // ignore all other sections } } //! reads a section and its content void CColladaFileLoader::readAssetSection(io::IXMLReaderUTF8* reader) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading asset"); #endif // don't need asset data so far, so skip it skipSection(reader, false); } //! reads a section and its content void CColladaFileLoader::readNodeSection(io::IXMLReaderUTF8* reader, scene::ISceneNode* parent) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading node"); #endif if (reader->isEmptyElement()) return; core::stringc name = reader->getAttributeValue("name"); // name of the node core::matrix4 transform; // transformation of this node scene::ISceneNode* node = 0; // instance // read the node 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 (translateNodeName == reader->getNodeName()) transform *= readTranslateNode(reader); else if (skewNodeName == reader->getNodeName()) transform *= readSkewNode(reader); else if (instanceNodeName == reader->getNodeName()) { scene::ISceneNode* newnode = 0; readInstanceNode(reader, parent, &newnode); if (node && newnode) { // move children from dummy to new node core::list::Iterator 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 (!node) { scene::IDummyTransformationSceneNode* dummy = SceneManager->addDummyTransformationSceneNode(parent); dummy->getRelativeTransformationMatrix() = transform; node = dummy; } // read and add child readNodeSection(reader, node); } 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 (node) { // TODO: set transformation correctly into node. // currently this isn't done correctly. Need to get transformation, // rotation and scale from the matrix. core::vector3df trans = transform.getTranslation(); core::vector3df rot = transform.getRotationDegrees(); node->setPosition(trans); node->setRotation(rot); node->updateAbsolutePosition(); node->setName(name.c_str()); } } //! 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"); #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) { core::matrix4 mat; if (reader->isEmptyElement()) return mat; f32 floats[7]; readFloatsInsideElement(reader, floats, 7); // TODO: build skew matrix from these 7 floats 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::readMatrixNode(io::IXMLReaderUTF8* reader) { core::matrix4 mat; if (reader->isEmptyElement()) return mat; readFloatsInsideElement(reader, mat.pointer(), 16); return mat; } //! reads a element and its content and creates a matrix from it core::matrix4 CColladaFileLoader::readPerspectiveNode(io::IXMLReaderUTF8* reader) { 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) { core::matrix4 mat; if (reader->isEmptyElement()) return mat; f32 floats[4]; readFloatsInsideElement(reader, floats, 4); core::quaternion q(floats[0], floats[1], floats[2], floats[3]); mat = q.getMatrix(); return mat; } //! reads a element and its content and creates a matrix from it core::matrix4 CColladaFileLoader::readScaleNode(io::IXMLReaderUTF8* reader) { core::matrix4 mat; if (reader->isEmptyElement()) return mat; f32 floats[3]; readFloatsInsideElement(reader, floats, 3); 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) { core::matrix4 mat; if (reader->isEmptyElement()) return mat; f32 floats[3]; readFloatsInsideElement(reader, floats, 3); mat.setTranslation(core::vector3df(floats[0], floats[1], floats[2])); return mat; } //! reads a node and creates a scene node from it void CColladaFileLoader::readInstanceNode(io::IXMLReaderUTF8* reader, scene::ISceneNode* parent, scene::ISceneNode** outNode) { #ifdef COLLADA_READER_DEBUG os::Printer::log("COLLADA reading instance"); #endif // find prefab of the specified id core::stringc url = reader->getAttributeValue("url"); uriToId(url); if (CreateInstances) for (int i=0; i<(int)Prefabs.size(); ++i) if (url == Prefabs[i]->getId()) { *outNode = Prefabs[i]->addInstance(parent, SceneManager); if (*outNode) (*outNode)->setName(reader->getAttributeValue("id")); return; } } //! reads a element and stores it as prefab void CColladaFileLoader::readCameraPrefab(io::IXMLReaderUTF8* reader) { CCameraPrefab* prefab = new CCameraPrefab(reader->getAttributeValue("id")); if (!reader->isEmptyElement()) { readColladaParameters(reader, cameraPrefabName); SColladaParam* p; 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]; } Prefabs.push_back(prefab); } //! reads a element and stores it in the material section void CColladaFileLoader::readMaterial(io::IXMLReaderUTF8* reader) { SColladaMaterial material; material.Id = reader->getAttributeValue("id"); if (!reader->isEmptyElement()) { // TODO: implement material parsing later. // for now we only read and discard then all tags. readColladaParameters(reader, materialSectionName); } // add material to list of loaded materials. Materials.push_back(material); } //! reads a element and stores it as mesh if possible void CColladaFileLoader::readGeometry(io::IXMLReaderUTF8* reader) { core::stringc id = reader->getAttributeValue("id"); core::stringc VertexPositionSource; // each mesh has exactly one member, containing // a POSITION input. This string stores the source of this input. core::array sources; bool okToReadArray = false; SAnimatedMesh* amesh = new SAnimatedMesh(); scene::SMesh* mesh = new SMesh(); amesh->addMesh(mesh); // 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 = reader->getAttributeValue("id"); #ifdef COLLADA_READER_DEBUG os::Printer::log("Loaded source", sources.getLast().Id.c_str()); #endif } else if (arraySectionName == nodeName) { // create a new array and read it. if (!sources.empty()) { sources.getLast().Array.Name = reader->getAttributeValue("id"); 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 = (!strcmp("float", type) || !strcmp("int", type)); #ifdef COLLADA_READER_DEBUG os::Printer::log("Read array", sources.getLast().Array.Name.c_str()); #endif } #ifdef COLLADA_READER_DEBUG else os::Printer::log("Warning, array found, but no source", reader->getAttributeValue("id")); #endif } else if (accessorSectionName == nodeName) { 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 = Parameters; } } else if (verticesSectionName == nodeName) { // read vertex input position source readColladaInputs(reader, verticesSectionName); SColladaInput* input = getColladaInput(ECIS_POSITION); if (input) VertexPositionSource = input->Source; } else if (polygonsSectionName == nodeName) { // read polygons section readPolygonSection(reader, VertexPositionSource, sources, mesh); } } // 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(); const c8* p = &data[0]; for (int i=0; i<(int)a.size(); ++i) { findNextNoneWhiteSpace(&p); a[i] = readFloat(&p); } } // end reading array okToReadArray = false; } // end if node type is text else if (reader->getNodeType() == 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(); // create virtual file name core::stringc 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()); } else { FirstLoadedMeshName = filename; FirstLoadedMesh = amesh; FirstLoadedMesh->grab(); } ++LoadedMeshCount; mesh->drop(); amesh->drop(); // create geometry prefab CGeometryPrefab* prefab = new CGeometryPrefab(id.c_str()); 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 SInputSlot { f32* Data; // Pointer to source data ECOLLADA_INPUT_SEMANTIC Semantic; }; struct SPolygon { core::array Indices; }; //! reads a polygons section and creates a mesh from it void CColladaFileLoader::readPolygonSection(io::IXMLReaderUTF8* reader, core::stringc vertexPositionSource, core::array& sources, scene::SMesh* mesh) { core::stringc material = reader->getAttributeValue("material"); // int polygonCount = reader->getAttributeValueAsInt("count"); core::array slots; core::array polygons; bool parsePolygonOK = false; int inputSemanticCount = 0; // read all and if (!reader->isEmptyElement()) while(reader->read()) { const char* nodeName = reader->getNodeName(); if (reader->getNodeType() == io::EXN_ELEMENT) { if (inputTagName == nodeName) { // read input tag readColladaInput(reader); // create new input slot if (!Inputs.empty()) { SInputSlot slot; slot.Data=0; slot.Semantic = Inputs.getLast().Semantic; core::stringc sourceArrayURI; // get input source array id, if it is a vertex input, take // the -source attribute. if (slot.Semantic == ECIS_VERTEX) sourceArrayURI = vertexPositionSource; else sourceArrayURI = Inputs.getLast().Source; uriToId(sourceArrayURI); // find source array (we'll ignore acessors for this implementation) int s; for (s=0; s<(int)sources.size(); ++s) if (sources[s].Id == sourceArrayURI) { // slot found slot.Data = sources[s].Array.Data.pointer(); break; } if (s == (int)sources.size()) os::Printer::log("COLLADA Warning, polygon input source not found", sourceArrayURI.c_str()); else slots.push_back(slot); #ifdef COLLADA_READER_DEBUG // print slot // core::stringc tmp = "Added slot "; // tmp += inputSemanticNames[Inputs.getLast().Semantic]; // tmp += " sourceArray:"; // tmp += sourceArrayURI; // os::Printer::log(tmp.c_str()); #endif ++inputSemanticCount; } } // end is input node else if (polygonName == nodeName) { parsePolygonOK = true; polygons.push_back(SPolygon()); } // end is polygon node } // end is element node else if (reader->getNodeType() == io::EXN_ELEMENT_END) { if (polygonName == nodeName) parsePolygonOK = false; // end parsing a polygon else if (polygonsSectionName == nodeName) break; // cancel out and create mesh } // end is element end else if (reader->getNodeType() == io::EXN_TEXT) { if (parsePolygonOK && polygons.size()) { // parse this text and add it to the last polygon data core::stringc data = reader->getNodeData(); const c8* p = &data[0]; SPolygon& poly = polygons.getLast(); while(*p) { findNextNoneWhiteSpace(&p); poly.Indices.push_back(readInt(&p)); } parsePolygonOK = false; } } } // end while reader->read() if (inputSemanticCount == 0 || inputSemanticCount != (int)slots.size()) return; // we cannot create the mesh if one of the input semantics wasn't found. if (!polygons.size()) return; // cancel if there are no polygons anyway. // analyze content of slots to create a fitting mesh buffer int i; int textureCoordSetCount = 0; int normalSlotCount = 0; int secondTexCoordSetIndex = -1; for (i=0; i<(int)slots.size(); ++i) { if (slots[i].Semantic == ECIS_TEXCOORD || slots[i].Semantic == ECIS_UV ) { ++textureCoordSetCount; if (textureCoordSetCount==2) secondTexCoordSetIndex = i; } else if (slots[i].Semantic == ECIS_NORMAL) ++normalSlotCount; } // if there is more than one texture coordinate set, create a lightmap mesh buffer, // otherwise use a standard mesh buffer scene::IMeshBuffer* buffer = 0; if ( textureCoordSetCount <= 1 ) { // standard mesh buffer scene::SMeshBuffer* mbuffer = new SMeshBuffer(); buffer = mbuffer; for (i=0; i<(int)polygons.size(); ++i) { int vertexCount = polygons[i].Indices.size() / inputSemanticCount; // for all vertices in array for (int v=0; v<(int)polygons[i].Indices.size(); v+=inputSemanticCount) { video::S3DVertex vtx; vtx.Color.set(100,255,255,255); // for all input semantics for (int k=0; k<(int)slots.size(); ++k) { // build vertex from input semantics. int idx = polygons[i].Indices[v+k]; switch(slots[k].Semantic) { case ECIS_POSITION: case ECIS_VERTEX: vtx.Pos.X = slots[k].Data[(idx*3)+0]; vtx.Pos.Y = slots[k].Data[(idx*3)+1]; vtx.Pos.Z = slots[k].Data[(idx*3)+2]; break; case ECIS_NORMAL: vtx.Normal.X = slots[k].Data[(idx*3)+0]; vtx.Normal.Y = slots[k].Data[(idx*3)+1]; vtx.Normal.Z = slots[k].Data[(idx*3)+2]; break; case ECIS_TEXCOORD: case ECIS_UV: vtx.TCoords.X = slots[k].Data[(idx*2)+0]; vtx.TCoords.Y = slots[k].Data[(idx*2)+1]; break; case ECIS_TANGENT: break; default: break; } } mbuffer->Vertices.push_back(vtx); } // end for all vertices // add vertex indices int currentVertexCount = mbuffer->Vertices.size(); int oldVertexCount = currentVertexCount - vertexCount; for (int 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 } else { // lightmap mesh buffer scene::SMeshBufferLightMap* mbuffer = new SMeshBufferLightMap(); buffer = mbuffer; for (i=0; i<(int)polygons.size(); ++i) { int vertexCount = polygons[i].Indices.size() / inputSemanticCount; // for all vertices in array for (int v=0; v<(int)polygons[i].Indices.size(); v+=inputSemanticCount) { video::S3DVertex2TCoords vtx; vtx.Color.set(100,255,255,255); // for all input semantics for (int k=0; k<(int)slots.size(); ++k) { // build vertex from input semantics. int idx = polygons[i].Indices[v+k]; switch(slots[k].Semantic) { case ECIS_POSITION: case ECIS_VERTEX: vtx.Pos.X = slots[k].Data[(idx*3)+0]; vtx.Pos.Y = slots[k].Data[(idx*3)+1]; vtx.Pos.Z = slots[k].Data[(idx*3)+2]; break; case ECIS_NORMAL: vtx.Normal.X = slots[k].Data[(idx*3)+0]; vtx.Normal.Y = slots[k].Data[(idx*3)+1]; vtx.Normal.Z = slots[k].Data[(idx*3)+2]; break; case ECIS_TEXCOORD: case ECIS_UV: if (k==secondTexCoordSetIndex) { vtx.TCoords2.X = slots[k].Data[(idx*2)+0]; vtx.TCoords2.Y = slots[k].Data[(idx*2)+1]; } else { vtx.TCoords.X = slots[k].Data[(idx*2)+0]; vtx.TCoords.Y = slots[k].Data[(idx*2)+1]; } break; case ECIS_TANGENT: break; default: break; } } mbuffer->Vertices.push_back(vtx); } // end for all vertices // add vertex indices int currentVertexCount = mbuffer->Vertices.size(); int oldVertexCount = currentVertexCount - vertexCount; for (int 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 } // calculate normals if there is no slot for it if (!normalSlotCount) SceneManager->getMeshManipulator()->recalculateNormals(buffer); // recalculate bounding box SceneManager->getMeshManipulator()->recalculateBoundingBox(buffer); // add mesh buffer mesh->addMeshBuffer(buffer); buffer->drop(); } //! reads a element and stores it as prefab void CColladaFileLoader::readLightPrefab(io::IXMLReaderUTF8* reader) { CLightPrefab* prefab = new CLightPrefab(reader->getAttributeValue("id")); if (!reader->isEmptyElement()) { 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 (int i=0; i<(int)Parameters.size(); ++i) if (Parameters[i].Name == name) return &Parameters[i]; return 0; } //! returns a collada input or none if not found SColladaInput* CColladaFileLoader::getColladaInput(ECOLLADA_INPUT_SEMANTIC input) { for (int i=0; i<(int)Inputs.size(); ++i) if (Inputs[i].Semantic == input) return &Inputs[i]; return 0; } //! reads a collada input tag and adds it to the input parameter void CColladaFileLoader::readColladaInput(io::IXMLReaderUTF8* reader) { // parse param SColladaInput p; // get type int i; core::stringc semanticName = reader->getAttributeValue("semantic"); for (i=0; inputSemanticNames[i]; ++i) if (semanticName == inputSemanticNames[i]) { p.Semantic = (ECOLLADA_INPUT_SEMANTIC)i; break; } // get source p.Source = reader->getAttributeValue("source"); // add input Inputs.push_back(p); } //! parses all collada inuts inside an element and stores them in Parameters 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); } 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 Parameters void CColladaFileLoader::readColladaParameters(io::IXMLReaderUTF8* reader, const core::stringc& parentName) { Parameters.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 int 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 Parameters.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, s32 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(); const c8* p = &data[0]; for (int i=0; igetNodeType() == io::EXN_ELEMENT_END) break; // end parsing text } } //! clears all loaded data void CColladaFileLoader::clearData() { // delete all prefabs for (int i=0; i<(int)Prefabs.size(); ++i) Prefabs[i]->drop(); Prefabs.clear(); // clear all parameters Parameters.clear(); // clear all materials Materials.clear(); // clear all inputs Inputs.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); } } // end namespace scene } // end namespace irr