// 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 "CXMeshFileLoader.h" #include "os.h" #include "fast_atof.h" #include "coreutil.h" #include "ISceneManager.h" #include "IVideoDriver.h" #include "IReadFile.h" #ifdef _DEBUG #define _XREADER_DEBUG #endif //#define BETTER_MESHBUFFER_SPLITTING_FOR_X #define SET_ERR_AND_RETURN() do { ErrorState = true; return false; } while (0) namespace irr { namespace scene { //! Constructor CXMeshFileLoader::CXMeshFileLoader(scene::ISceneManager* smgr) : AnimatedMesh(0), Buffer(0), P(0), End(0), BinaryNumCount(0), Line(0), ErrorState(false), CurFrame(0), MajorVersion(0), MinorVersion(0), BinaryFormat(false), FloatSize(0) { #ifdef _DEBUG setDebugName("CXMeshFileLoader"); #endif } //! returns true if the file maybe is able to be loaded by this class //! based on the file extension (e.g. ".b3d") bool CXMeshFileLoader::isALoadableFileExtension(const io::path& filename) const { return core::hasFileExtension ( filename, "x" ); } //! 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* CXMeshFileLoader::createMesh(io::IReadFile* file) { if (!file) return 0; #ifdef _XREADER_DEBUG u32 time = os::Timer::getRealTime(); #endif AnimatedMesh = new CSkinnedMesh(); if (load(file)) { AnimatedMesh->finalize(); } else { AnimatedMesh->drop(); AnimatedMesh = 0; } #ifdef _XREADER_DEBUG time = os::Timer::getRealTime() - time; core::stringc tmpString = "Time to load "; tmpString += BinaryFormat ? "binary" : "ascii"; tmpString += " X file: "; tmpString += time; tmpString += "ms"; os::Printer::log(tmpString.c_str()); #endif //Clear up MajorVersion=0; MinorVersion=0; BinaryFormat=0; BinaryNumCount=0; FloatSize=0; P=0; End=0; CurFrame=0; delete [] Buffer; Buffer = 0; for (u32 i=0; iMaterials.size()) { mesh->Materials.push_back(video::SMaterial()); mesh->Materials[0].DiffuseColor.set(0xff777777); mesh->Materials[0].Shininess=0.f; mesh->Materials[0].SpecularColor.set(0xff777777); mesh->Materials[0].EmissiveColor.set(0xff000000); } u32 i; mesh->Buffers.reallocate(mesh->Materials.size()); #ifndef BETTER_MESHBUFFER_SPLITTING_FOR_X const u32 bufferOffset = AnimatedMesh->getMeshBufferCount(); #endif for (i=0; iMaterials.size(); ++i) { mesh->Buffers.push_back( AnimatedMesh->addMeshBuffer() ); mesh->Buffers.getLast()->Material = mesh->Materials[i]; if (!mesh->HasSkinning) { //Set up rigid animation if (mesh->AttachedJointID!=-1) { AnimatedMesh->getAllJoints()[mesh->AttachedJointID]->AttachedMeshes.push_back( AnimatedMesh->getMeshBuffers().size()-1 ); } } } if (!mesh->FaceMaterialIndices.size()) { mesh->FaceMaterialIndices.set_used(mesh->Indices.size() / 3); for (i=0; iFaceMaterialIndices.size(); ++i) mesh->FaceMaterialIndices[i]=0; } if (!mesh->HasVertexColors) { for (u32 j=0;jFaceMaterialIndices.size();++j) { for (u32 id=j*3+0;id<=j*3+2;++id) { mesh->Vertices[ mesh->Indices[id] ].Color = mesh->Buffers[mesh->FaceMaterialIndices[j]]->Material.DiffuseColor; } } } #ifdef BETTER_MESHBUFFER_SPLITTING_FOR_X { //the same vertex can be used in many different meshbuffers, but it's slow to work out core::array< core::array< u32 > > verticesLinkIndex; verticesLinkIndex.reallocate(mesh->Vertices.size()); core::array< core::array< u16 > > verticesLinkBuffer; verticesLinkBuffer.reallocate(mesh->Vertices.size()); for (i=0;iVertices.size();++i) { verticesLinkIndex.push_back( core::array< u32 >() ); verticesLinkBuffer.push_back( core::array< u16 >() ); } for (i=0;iFaceMaterialIndices.size();++i) { for (u32 id=i*3+0;id<=i*3+2;++id) { core::array< u16 > &Array=verticesLinkBuffer[ mesh->Indices[id] ]; bool found=false; for (u32 j=0; j < Array.size(); ++j) { if (Array[j]==mesh->FaceMaterialIndices[i]) { found=true; break; } } if (!found) Array.push_back( mesh->FaceMaterialIndices[i] ); } } for (i=0;iVertices.size();++i) { core::array< u16 > &Array = verticesLinkBuffer[i]; verticesLinkIndex[i].reallocate(Array.size()); for (u32 j=0; j < Array.size(); ++j) { scene::SSkinMeshBuffer *buffer = mesh->Buffers[ Array[j] ]; verticesLinkIndex[i].push_back( buffer->Vertices_Standard.size() ); buffer->Vertices_Standard.push_back( mesh->Vertices[i] ); } } for (i=0;iFaceMaterialIndices.size();++i) { scene::SSkinMeshBuffer *buffer=mesh->Buffers[ mesh->FaceMaterialIndices[i] ]; for (u32 id=i*3+0;id<=i*3+2;++id) { core::array< u16 > &Array=verticesLinkBuffer[ mesh->Indices[id] ]; for (u32 j=0;j< Array.size() ;++j) { if ( Array[j]== mesh->FaceMaterialIndices[i] ) buffer->Indices.push_back( verticesLinkIndex[ mesh->Indices[id] ][j] ); } } } for (u32 j=0;jWeightJoint.size();++j) { ISkinnedMesh::SJoint* joint = AnimatedMesh->getAllJoints()[mesh->WeightJoint[j]]; ISkinnedMesh::SWeight& weight = joint->Weights[mesh->WeightNum[j]]; u32 id = weight.vertex_id; if (id>=verticesLinkIndex.size()) { os::Printer::log("X loader: Weight id out of range", ELL_WARNING); id=0; weight.strength=0.f; } if (verticesLinkBuffer[id].size()==1) { weight.vertex_id=verticesLinkIndex[id][0]; weight.buffer_id=verticesLinkBuffer[id][0]; } else if (verticesLinkBuffer[id].size() != 0) { for (u32 k=1; k < verticesLinkBuffer[id].size(); ++k) { ISkinnedMesh::SWeight* WeightClone = AnimatedMesh->addWeight(joint); WeightClone->strength = weight.strength; WeightClone->vertex_id = verticesLinkIndex[id][k]; WeightClone->buffer_id = verticesLinkBuffer[id][k]; } } } } #else { core::array< u32 > verticesLinkIndex; core::array< s16 > verticesLinkBuffer; verticesLinkBuffer.set_used(mesh->Vertices.size()); // init with 0 for (i=0;iVertices.size();++i) { // watch out for vertices which are not part of the mesh // they will keep the -1 and can lead to out-of-bounds access verticesLinkBuffer[i]=-1; } bool warned = false; // store meshbuffer number per vertex for (i=0;iFaceMaterialIndices.size();++i) { for (u32 id=i*3+0;id<=i*3+2;++id) { if ((verticesLinkBuffer[mesh->Indices[id]] != -1) && (verticesLinkBuffer[mesh->Indices[id]] != (s16)mesh->FaceMaterialIndices[i])) { if (!warned) { os::Printer::log("X loader", "Duplicated vertex, animation might be corrupted.", ELL_WARNING); warned=true; } const u32 tmp = mesh->Vertices.size(); mesh->Vertices.push_back(mesh->Vertices[ mesh->Indices[id] ]); mesh->Indices[id] = tmp; verticesLinkBuffer.set_used(mesh->Vertices.size()); } verticesLinkBuffer[ mesh->Indices[id] ] = mesh->FaceMaterialIndices[i]; } } if (mesh->FaceMaterialIndices.size() != 0) { // store vertices in buffers and remember relation in verticesLinkIndex u32* vCountArray = new u32[mesh->Buffers.size()]; memset(vCountArray, 0, mesh->Buffers.size()*sizeof(u32)); // count vertices in each buffer and reallocate for (i=0; iVertices.size(); ++i) { if (verticesLinkBuffer[i] != -1) ++vCountArray[verticesLinkBuffer[i]]; } if (mesh->TCoords2.size()) { for (i=0; i!=mesh->Buffers.size(); ++i) { mesh->Buffers[i]->Vertices_2TCoords.reallocate(vCountArray[i]); mesh->Buffers[i]->VertexType=video::EVT_2TCOORDS; } } else { for (i=0; i!=mesh->Buffers.size(); ++i) mesh->Buffers[i]->Vertices_Standard.reallocate(vCountArray[i]); } verticesLinkIndex.set_used(mesh->Vertices.size()); // actually store vertices for (i=0; iVertices.size(); ++i) { // if a vertex is missing for some reason, just skip it if (verticesLinkBuffer[i]==-1) continue; scene::SSkinMeshBuffer *buffer = mesh->Buffers[ verticesLinkBuffer[i] ]; if (mesh->TCoords2.size()) { verticesLinkIndex[i] = buffer->Vertices_2TCoords.size(); buffer->Vertices_2TCoords.push_back( mesh->Vertices[i] ); // We have a problem with correct tcoord2 handling here // crash fixed for now by checking the values buffer->Vertices_2TCoords.getLast().TCoords2=(iTCoords2.size())?mesh->TCoords2[i]:mesh->Vertices[i].TCoords; } else { verticesLinkIndex[i] = buffer->Vertices_Standard.size(); buffer->Vertices_Standard.push_back( mesh->Vertices[i] ); } } // count indices per buffer and reallocate memset(vCountArray, 0, mesh->Buffers.size()*sizeof(u32)); for (i=0; iFaceMaterialIndices.size(); ++i) ++vCountArray[ mesh->FaceMaterialIndices[i] ]; for (i=0; i!=mesh->Buffers.size(); ++i) mesh->Buffers[i]->Indices.reallocate(vCountArray[i]); delete [] vCountArray; // create indices per buffer for (i=0; iFaceMaterialIndices.size(); ++i) { scene::SSkinMeshBuffer *buffer = mesh->Buffers[ mesh->FaceMaterialIndices[i] ]; for (u32 id=i*3+0; id!=i*3+3; ++id) { buffer->Indices.push_back( verticesLinkIndex[ mesh->Indices[id] ] ); } } } for (u32 j=0; jWeightJoint.size(); ++j) { ISkinnedMesh::SWeight& weight = (AnimatedMesh->getAllJoints()[mesh->WeightJoint[j]]->Weights[mesh->WeightNum[j]]); u32 id = weight.vertex_id; if (id>=verticesLinkIndex.size()) { os::Printer::log("X loader: Weight id out of range", ELL_WARNING); id=0; weight.strength=0.f; } weight.vertex_id=verticesLinkIndex[id]; weight.buffer_id=verticesLinkBuffer[id] + bufferOffset; } } #endif } return true; } //! Reads file into memory bool CXMeshFileLoader::readFileIntoMemory(io::IReadFile* file) { const long size = file->getSize(); if (size < 12) { os::Printer::log("X File is too small.", ELL_WARNING); return false; } Buffer = new c8[size+1]; Buffer[size] = 0x0; // null-terminate //! read all into memory if (file->read(Buffer, size) != static_cast(size)) { os::Printer::log("Could not read from x file.", ELL_WARNING); return false; } Line = 1; End = Buffer + size; //! check header "xof " if (strncmp(Buffer, "xof ", 4)!=0) { os::Printer::log("Not an x file, wrong header.", ELL_WARNING); return false; } //! read minor and major version, e.g. 0302 or 0303 c8 tmp[3]; tmp[0] = Buffer[4]; tmp[1] = Buffer[5]; tmp[2] = 0x0; MajorVersion = core::strtoul10(tmp); tmp[0] = Buffer[6]; tmp[1] = Buffer[7]; MinorVersion = core::strtoul10(tmp); //! read format if (strncmp(&Buffer[8], "txt ", 4) ==0) BinaryFormat = false; else if (strncmp(&Buffer[8], "bin ", 4) ==0) BinaryFormat = true; else { os::Printer::log("Only uncompressed x files currently supported.", ELL_WARNING); return false; } BinaryNumCount=0; //! read float size if (strncmp(&Buffer[12], "0032", 4) ==0) FloatSize = 4; else if (strncmp(&Buffer[12], "0064", 4) ==0) FloatSize = 8; else { os::Printer::log("Float size not supported.", ELL_WARNING); return false; } P = &Buffer[16]; readUntilEndOfLine(); return true; } //! Parses the file bool CXMeshFileLoader::parseFile() { while(parseDataObject()) { // loop } return !ErrorState; } //! Parses the next Data object in the file bool CXMeshFileLoader::parseDataObject() { core::stringc objectName = getNextToken(); if (objectName.size() == 0) return false; // parse specific object #ifdef _XREADER_DEBUG os::Printer::log("debug DataObject", objectName.c_str(), ELL_DEBUG); #endif if (objectName == "template") return parseDataObjectTemplate(); else if (objectName == "Frame") { return parseDataObjectFrame( 0 ); } else if (objectName == "Mesh") { // some meshes have no frames at all //CurFrame = AnimatedMesh->addJoint(0); SXMesh *mesh=new SXMesh; //mesh->Buffer=AnimatedMesh->addMeshBuffer(); Meshes.push_back(mesh); return parseDataObjectMesh(*mesh); } else if (objectName == "AnimationSet") { return parseDataObjectAnimationSet(); } else if (objectName == "AnimTicksPerSecond") { return parseDataObjectAnimationTicksPerSecond(); } else if (objectName == "Material") { return parseUnknownDataObject(); } else if (objectName == "}") { os::Printer::log("} found in dataObject", ELL_WARNING); return true; } os::Printer::log("Unknown data object in animation of .x file", objectName.c_str(), ELL_WARNING); return parseUnknownDataObject(); } bool CXMeshFileLoader::parseDataObjectTemplate() { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading template", ELL_DEBUG); #endif // parse a template data object. Currently not stored. core::stringc name; if (!readHeadOfDataObject(&name)) { os::Printer::log("Left delimiter in template data object missing.", name.c_str(), ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } // read GUID getNextToken(); // read and ignore data members while(true) { core::stringc s = getNextToken(); if (s == "}") break; if (s.size() == 0) return false; } return true; } bool CXMeshFileLoader::parseDataObjectFrame(CSkinnedMesh::SJoint *Parent) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading frame", ELL_DEBUG); #endif // A coordinate frame, or "frame of reference." The Frame template // is open and can contain any object. The Direct3D extensions (D3DX) // mesh-loading functions recognize Mesh, FrameTransformMatrix, and // Frame template instances as child objects when loading a Frame // instance. u32 JointID=0; core::stringc name; if (!readHeadOfDataObject(&name)) { os::Printer::log("No opening brace in Frame found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } CSkinnedMesh::SJoint *joint=0; if (name.size()) { for (u32 n=0; n < AnimatedMesh->getAllJoints().size(); ++n) { if (AnimatedMesh->getAllJoints()[n]->Name==name) { joint=AnimatedMesh->getAllJoints()[n]; JointID=n; break; } } } if (!joint) { #ifdef _XREADER_DEBUG os::Printer::log("creating joint ", name.c_str(), ELL_DEBUG); #endif joint=AnimatedMesh->addJoint(Parent); joint->Name=name; JointID=AnimatedMesh->getAllJoints().size()-1; } else { #ifdef _XREADER_DEBUG os::Printer::log("using joint ", name.c_str(), ELL_DEBUG); #endif if (Parent) Parent->Children.push_back(joint); } // Now inside a frame. // read tokens until closing brace is reached. while(true) { core::stringc objectName = getNextToken(); #ifdef _XREADER_DEBUG os::Printer::log("debug DataObject in frame:", objectName.c_str(), ELL_DEBUG); #endif if (objectName.size() == 0) { os::Printer::log("Unexpected ending found in Frame in x file.", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } else if (objectName == "}") { break; // frame finished } else if (objectName == "Frame") { if (!parseDataObjectFrame(joint)) return false; } else if (objectName == "FrameTransformMatrix") { if (!parseDataObjectTransformationMatrix(joint->LocalMatrix)) return false; //joint->LocalAnimatedMatrix //joint->LocalAnimatedMatrix.makeInverse(); //joint->LocalMatrix=tmp*joint->LocalAnimatedMatrix; } else if (objectName == "Mesh") { /* frame.Meshes.push_back(SXMesh()); if (!parseDataObjectMesh(frame.Meshes.getLast())) return false; */ SXMesh *mesh=new SXMesh; mesh->AttachedJointID=JointID; Meshes.push_back(mesh); if (!parseDataObjectMesh(*mesh)) return false; } else { os::Printer::log("Unknown data object in frame in x file", objectName.c_str(), ELL_WARNING); if (!parseUnknownDataObject()) return false; } } return true; } bool CXMeshFileLoader::parseDataObjectTransformationMatrix(core::matrix4 &mat) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading Transformation Matrix", ELL_DEBUG); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Transformation Matrix found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } readMatrix(mat); if (!checkForOneFollowingSemicolons()) { os::Printer::log("No finishing semicolon in Transformation Matrix found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); } if (!checkForClosingBrace()) { os::Printer::log("No closing brace in Transformation Matrix found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } return true; } bool CXMeshFileLoader::parseDataObjectMesh(SXMesh &mesh) { core::stringc name; if (!readHeadOfDataObject(&name)) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading mesh", ELL_DEBUG); #endif os::Printer::log("No opening brace in Mesh found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading mesh", name.c_str(), ELL_DEBUG); #endif // read vertex count const u32 nVertices = readInt(); // read vertices mesh.Vertices.set_used(nVertices); for (u32 n=0; n polygonfaces; u32 currentIndex = 0; for (u32 k=0; k= mesh.Vertices.size()) { os::Printer::log("Out of range index found in Mesh x file reader.", ELL_WARNING); SET_ERR_AND_RETURN(); } } if (!checkForTwoFollowingSemicolons()) { os::Printer::log("No finishing semicolon in Mesh Face Array found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); } // here, other data objects may follow while(true) { core::stringc objectName = getNextToken(); if (objectName.size() == 0) { os::Printer::log("Unexpected ending found in Mesh in x file.", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } else if (objectName == "}") { break; // mesh finished } #ifdef _XREADER_DEBUG os::Printer::log("debug DataObject in mesh", objectName.c_str(), ELL_DEBUG); #endif if (objectName == "MeshNormals") { if (!parseDataObjectMeshNormals(mesh)) return false; } else if (objectName == "MeshTextureCoords") { if (!parseDataObjectMeshTextureCoords(mesh)) return false; } else if (objectName == "MeshVertexColors") { if (!parseDataObjectMeshVertexColors(mesh)) return false; } else if (objectName == "MeshMaterialList") { if (!parseDataObjectMeshMaterialList(mesh)) return false; } else if (objectName == "VertexDuplicationIndices") { // we'll ignore vertex duplication indices // TODO: read them if (!parseUnknownDataObject()) return false; } else if (objectName == "DeclData") { if (!readHeadOfDataObject()) { os::Printer::log("No starting brace in DeclData found.", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } // arbitrary vertex attributes // first comes the number of element definitions // then the vertex element type definitions // with format type;tesselator;semantics;usageindex // we want to support 2;0;6;0 == tangent // 2;0;7;0 == binormal // 2;0;3;0 == normal // 1/2;0;5;0 == 1st uv coord // and 1/2;0;5;1 == 2nd uv coord // type==2 is 3xf32, type==1 is 2xf32 u32 j; const u32 dcnt = readInt(); u16 size = 0; s16 normalpos = -1; s16 uvpos = -1; s16 uv2pos = -1; s16 tangentpos = -1; s16 binormalpos = -1; s16 normaltype = -1; s16 uvtype = -1; s16 uv2type = -1; s16 tangenttype = -1; s16 binormaltype = -1; (void)tangentpos; // disable unused variable warnings (void)binormalpos; // disable unused variable warnings (void)tangenttype; // disable unused variable warnings (void)binormaltype; // disable unused variable warnings for (j=0; j>8)&0xf)*sizeof(core::vector2df); for (u32 j=0; jgetAllJoints().size(); ++n) { if (AnimatedMesh->getAllJoints()[n]->Name==TransformNodeName) { joint=AnimatedMesh->getAllJoints()[n]; break; } } if (!joint) { #ifdef _XREADER_DEBUG os::Printer::log("creating joint for skinning ", TransformNodeName.c_str(), ELL_DEBUG); #endif n = AnimatedMesh->getAllJoints().size(); joint=AnimatedMesh->addJoint(0); joint->Name=TransformNodeName; } // read vertex weights const u32 nWeights = readInt(); // read vertex indices u32 i; const u32 jointStart = joint->Weights.size(); joint->Weights.reallocate(jointStart+nWeights); mesh.WeightJoint.reallocate( mesh.WeightJoint.size() + nWeights ); mesh.WeightNum.reallocate( mesh.WeightNum.size() + nWeights ); for (i=0; iWeights.size()); CSkinnedMesh::SWeight *weight=AnimatedMesh->addWeight(joint); weight->buffer_id=0; weight->vertex_id=readInt(); } // read vertex weights for (i=jointStart; iWeights[i].strength = readFloat(); // read matrix offset // transforms the mesh vertices to the space of the bone // When concatenated to the bone's transform, this provides the // world space coordinates of the mesh as affected by the bone core::matrix4& MatrixOffset = joint->GlobalInversedMatrix; readMatrix(MatrixOffset); if (!checkForOneFollowingSemicolons()) { os::Printer::log("No finishing semicolon in Skin Weights found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); } if (!checkForClosingBrace()) { os::Printer::log("No closing brace in Skin Weights found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } return true; } bool CXMeshFileLoader::parseDataObjectSkinMeshHeader(SXMesh& mesh) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading skin mesh header", ELL_DEBUG); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Skin Mesh header found in .x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } mesh.MaxSkinWeightsPerVertex = readInt(); mesh.MaxSkinWeightsPerFace = readInt(); mesh.BoneCount = readInt(); if (!BinaryFormat) getNextToken(); // skip semicolon if (!checkForClosingBrace()) { os::Printer::log("No closing brace in skin mesh header in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } return true; } bool CXMeshFileLoader::parseDataObjectMeshNormals(SXMesh &mesh) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: reading mesh normals", ELL_DEBUG); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Mesh Normals found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } // read count const u32 nNormals = readInt(); core::array normals; normals.set_used(nNormals); // read normals for (u32 i=0; i normalIndices; normalIndices.set_used(mesh.Indices.size()); // read face normal indices const u32 nFNormals = readInt(); //if (nFNormals >= mesh.IndexCountPerFace.size()) if (0) // this condition doesn't work for some reason { os::Printer::log("Too many face normals found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } u32 normalidx = 0; core::array polygonfaces; for (u32 k=0; k= mesh.Vertices.size()) if (0) // this condition doesn't work for some reason { os::Printer::log("Too many texture coords found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } for (u32 i=0; i=mesh.Vertices.size()) { os::Printer::log("index value in parseDataObjectMeshVertexColors out of bounds", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } readRGBA(mesh.Vertices[Index].Color); checkForOneFollowingSemicolons(); } if (!checkForOneFollowingSemicolons()) { os::Printer::log("No finishing semicolon in Mesh Vertex Colors Array found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); } if (!checkForClosingBrace()) { os::Printer::log("No closing brace in Mesh Texture Coordinates Array found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); return false; } return true; } bool CXMeshFileLoader::parseDataObjectMeshMaterialList(SXMesh &mesh) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading mesh material list", ELL_DEBUG); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Mesh Material List found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } // read material count const u32 nMaterials = readInt(); mesh.Materials.reallocate(nMaterials); // read non triangulated face material index count const u32 nFaceIndices = readInt(); // There seems to be a compact representation of "all faces the same material" // being represented as 1;1;0;; which means 1 material, 1 face with first material // all the other faces have to obey then, so check is disabled //if (nFaceIndices != mesh.IndexCountPerFace.size()) // os::Printer::log("Index count per face not equal to face material index count in x file.", ELL_WARNING); // read non triangulated face indices and create triangulated ones mesh.FaceMaterialIndices.set_used( mesh.Indices.size() / 3); u32 triangulatedindex = 0; u32 ind = 0; for (u32 tfi=0; tfi= core::max_(nMaterials, 1U)) { os::Printer::log("Out of range index found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } const u32 fc = mesh.IndexCountPerFace[tfi]/3; for (u32 k=0; ksetAnimationSpeed(static_cast(ticks)); return true; } bool CXMeshFileLoader::parseDataObjectAnimation() { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: reading animation", ELL_DEBUG); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Animation found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } //anim.closed = true; //anim.linearPositionQuality = true; CSkinnedMesh::SJoint animationDump; core::stringc FrameName; while(true) { core::stringc objectName = getNextToken(); if (objectName.size() == 0) { os::Printer::log("Unexpected ending found in Animation in x file.", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } else if (objectName == "}") { break; // animation finished } else if (objectName == "AnimationKey") { if (!parseDataObjectAnimationKey(&animationDump)) return false; } else if (objectName == "AnimationOptions") { //TODO: parse options. if (!parseUnknownDataObject()) return false; } else if (objectName == "{") { // read frame name FrameName = getNextToken(); if (!checkForClosingBrace()) { os::Printer::log("Unexpected ending found in Animation in x file.", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } } else { os::Printer::log("Unknown data object in animation in x file", objectName.c_str(), ELL_WARNING); if (!parseUnknownDataObject()) SET_ERR_AND_RETURN(); } } if (FrameName.size() != 0) { #ifdef _XREADER_DEBUG os::Printer::log("frame name", FrameName.c_str(), ELL_DEBUG); #endif CSkinnedMesh::SJoint *joint=0; u32 n; for (n=0; n < AnimatedMesh->getAllJoints().size(); ++n) { if (AnimatedMesh->getAllJoints()[n]->Name==FrameName) { joint=AnimatedMesh->getAllJoints()[n]; break; } } if (!joint) { #ifdef _XREADER_DEBUG os::Printer::log("creating joint for animation ", FrameName.c_str(), ELL_DEBUG); #endif joint=AnimatedMesh->addJoint(0); joint->Name=FrameName; } joint->PositionKeys.reallocate(joint->PositionKeys.size()+animationDump.PositionKeys.size()); for (n=0; nPositionKeys.push_back(animationDump.PositionKeys[n]); } joint->ScaleKeys.reallocate(joint->ScaleKeys.size()+animationDump.ScaleKeys.size()); for (n=0; nScaleKeys.push_back(animationDump.ScaleKeys[n]); } joint->RotationKeys.reallocate(joint->RotationKeys.size()+animationDump.RotationKeys.size()); for (n=0; nRotationKeys.push_back(animationDump.RotationKeys[n]); } } else os::Printer::log("joint name was never given", ELL_WARNING); return true; } bool CXMeshFileLoader::parseDataObjectAnimationKey(ISkinnedMesh::SJoint *joint) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: reading animation key", ELL_DEBUG); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Animation Key found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } // read key type const u32 keyType = readInt(); if (keyType > 4) { os::Printer::log("Unknown key type found in Animation Key in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } // read number of keys const u32 numberOfKeys = readInt(); // eat the semicolon after the "0". if there are keys present, readInt() // does this for us. If there aren't, we need to do it explicitly if (numberOfKeys == 0) checkForOneFollowingSemicolons(); for (u32 i=0; iaddRotationKey(joint); key->frame=time; key->rotation.set(X,Y,Z,W); key->rotation.normalize(); } break; case 1: //scale case 2: //position { // read vectors // read count if (readInt() != 3) { os::Printer::log("Expected 3 numbers in animation key in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } core::vector3df vector; readVector3(vector); if (!checkForTwoFollowingSemicolons()) { os::Printer::log("No finishing semicolon after vector animation key in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); } if (keyType==2) { ISkinnedMesh::SPositionKey *key=AnimatedMesh->addPositionKey(joint); key->frame=time; key->position=vector; } else { ISkinnedMesh::SScaleKey *key=AnimatedMesh->addScaleKey(joint); key->frame=time; key->scale=vector; } } break; case 3: case 4: { // read matrix // read count if (readInt() != 16) { os::Printer::log("Expected 16 numbers in animation key in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } // read matrix core::matrix4 mat(core::matrix4::EM4CONST_NOTHING); readMatrix(mat); //mat=joint->LocalMatrix*mat; if (!checkForOneFollowingSemicolons()) { os::Printer::log("No finishing semicolon after matrix animation key in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); } //core::vector3df rotation = mat.getRotationDegrees(); ISkinnedMesh::SRotationKey *keyR=AnimatedMesh->addRotationKey(joint); keyR->frame=time; // IRR_TEST_BROKEN_QUATERNION_USE: TODO - switched from mat to mat.getTransposed() for downward compatibility. // Not tested so far if this was correct or wrong before quaternion fix! keyR->rotation= core::quaternion(mat.getTransposed()); ISkinnedMesh::SPositionKey *keyP=AnimatedMesh->addPositionKey(joint); keyP->frame=time; keyP->position=mat.getTranslation(); /* core::vector3df scale=mat.getScale(); if (scale.X==0) scale.X=1; if (scale.Y==0) scale.Y=1; if (scale.Z==0) scale.Z=1; ISkinnedMesh::SScaleKey *keyS=AnimatedMesh->addScaleKey(joint); keyS->frame=time; keyS->scale=scale; */ } break; } // end switch } if (!checkForOneFollowingSemicolons()) --P; if (!checkForClosingBrace()) { os::Printer::log("No closing brace in animation key in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } return true; } bool CXMeshFileLoader::parseDataObjectTextureFilename(core::stringc& texturename) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: reading texture filename", ELL_DEBUG); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Texture filename found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } if (!getNextTokenAsString(texturename)) { os::Printer::log("Unknown syntax while reading texture filename string in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } if (!checkForClosingBrace()) { os::Printer::log("No closing brace in Texture filename found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } return true; } bool CXMeshFileLoader::parseUnknownDataObject() { // find opening delimiter while(true) { core::stringc t = getNextToken(); if (t.size() == 0) return false; if (t == "{") break; } u32 counter = 1; // parse until closing delimiter while(counter) { core::stringc t = getNextToken(); if (t.size() == 0) return false; if (t == "{") ++counter; else if (t == "}") --counter; } return true; } //! checks for closing curly brace, returns false if not there bool CXMeshFileLoader::checkForClosingBrace() { return (getNextToken() == "}"); } //! checks for one following semicolon, returns false if not there bool CXMeshFileLoader::checkForOneFollowingSemicolons() { if (BinaryFormat) return true; if (getNextToken() == ";") return true; else { --P; return false; } } //! checks for two following semicolons, returns false if they are not there bool CXMeshFileLoader::checkForTwoFollowingSemicolons() { if (BinaryFormat) return true; for (u32 k=0; k<2; ++k) { if (getNextToken() != ";") { --P; return false; } } return true; } //! reads header of dataobject including the opening brace. //! returns false if error happened, and writes name of object //! if there is one bool CXMeshFileLoader::readHeadOfDataObject(core::stringc* outname) { core::stringc nameOrBrace = getNextToken(); if (nameOrBrace != "{") { if (outname) (*outname) = nameOrBrace; if (getNextToken() != "{") return false; } return true; } //! returns next parseable token. Returns empty string if no token there core::stringc CXMeshFileLoader::getNextToken() { core::stringc s; // process binary-formatted file if (BinaryFormat) { // in binary mode it will only return NAME and STRING token // and (correctly) skip over other tokens. s16 tok = readBinWord(); u32 len; // standalone tokens switch (tok) { case 1: // name token len = readBinDWord(); s = core::stringc(P, len); P += len; return s; case 2: // string token len = readBinDWord(); s = core::stringc(P, len); P += (len + 2); return s; case 3: // integer token P += 4; return ""; case 5: // GUID token P += 16; return ""; case 6: len = readBinDWord(); P += (len * 4); return ""; case 7: len = readBinDWord(); P += (len * FloatSize); return ""; case 0x0a: return "{"; case 0x0b: return "}"; case 0x0c: return "("; case 0x0d: return ")"; case 0x0e: return "["; case 0x0f: return "]"; case 0x10: return "<"; case 0x11: return ">"; case 0x12: return "."; case 0x13: return ","; case 0x14: return ";"; case 0x1f: return "template"; case 0x28: return "WORD"; case 0x29: return "DWORD"; case 0x2a: return "FLOAT"; case 0x2b: return "DOUBLE"; case 0x2c: return "CHAR"; case 0x2d: return "UCHAR"; case 0x2e: return "SWORD"; case 0x2f: return "SDWORD"; case 0x30: return "void"; case 0x31: return "string"; case 0x32: return "unicode"; case 0x33: return "cstring"; case 0x34: return "array"; } } // process text-formatted file else { findNextNoneWhiteSpace(); if (P >= End) return s; while((P < End) && !core::isspace(P[0])) { // either keep token delimiters when already holding a token, or return if first valid char if (P[0]==';' || P[0]=='}' || P[0]=='{' || P[0]==',') { if (!s.size()) { s.append(P[0]); ++P; } break; // stop for delimiter } s.append(P[0]); ++P; } } return s; } //! places pointer to next begin of a token, which must be a number, // and ignores comments void CXMeshFileLoader::findNextNoneWhiteSpaceNumber() { if (BinaryFormat) return; while((P < End) && (P[0] != '-') && (P[0] != '.') && !( core::isdigit(P[0]))) { // check if this is a comment if ((P[0] == '/' && P[1] == '/') || P[0] == '#') readUntilEndOfLine(); else ++P; } } // places pointer to next begin of a token, and ignores comments void CXMeshFileLoader::findNextNoneWhiteSpace() { if (BinaryFormat) return; while(true) { while((P < End) && core::isspace(P[0])) { if (*P=='\n') ++Line; ++P; } if (P >= End) return; // check if this is a comment if ((P[0] == '/' && P[1] == '/') || P[0] == '#') readUntilEndOfLine(); else break; } } //! reads a x file style string bool CXMeshFileLoader::getNextTokenAsString(core::stringc& out) { if (BinaryFormat) { out=getNextToken(); return true; } findNextNoneWhiteSpace(); if (P >= End) return false; if (P[0] != '"') return false; ++P; while(P < End && P[0]!='"') { out.append(P[0]); ++P; } if ( P[1] != ';' || P[0] != '"') return false; P+=2; return true; } void CXMeshFileLoader::readUntilEndOfLine() { if (BinaryFormat) return; while(P < End) { if (P[0] == '\n' || P[0] == '\r') { ++P; ++Line; return; } ++P; } } u16 CXMeshFileLoader::readBinWord() { if (P>=End) return 0; #ifdef __BIG_ENDIAN__ const u16 tmp = os::Byteswap::byteswap(*(u16 *)P); #else const u16 tmp = *(u16 *)P; #endif P += 2; return tmp; } u32 CXMeshFileLoader::readBinDWord() { if (P>=End) return 0; #ifdef __BIG_ENDIAN__ const u32 tmp = os::Byteswap::byteswap(*(u32 *)P); #else const u32 tmp = *(u32 *)P; #endif P += 4; return tmp; } u32 CXMeshFileLoader::readInt() { if (BinaryFormat) { if (!BinaryNumCount) { const u16 tmp = readBinWord(); // 0x06 or 0x03 if (tmp == 0x06) BinaryNumCount = readBinDWord(); else BinaryNumCount = 1; // single int } --BinaryNumCount; return readBinDWord(); } else { findNextNoneWhiteSpaceNumber(); return core::strtoul10(P, &P); } } f32 CXMeshFileLoader::readFloat() { if (BinaryFormat) { if (!BinaryNumCount) { const u16 tmp = readBinWord(); // 0x07 or 0x42 if (tmp == 0x07) BinaryNumCount = readBinDWord(); else BinaryNumCount = 1; // single int } --BinaryNumCount; if (FloatSize == 8) { #ifdef __BIG_ENDIAN__ //TODO: Check if data is properly converted here f32 ctmp[2]; ctmp[1] = os::Byteswap::byteswap(*(f32*)P); ctmp[0] = os::Byteswap::byteswap(*(f32*)P+4); const f32 tmp = (f32)(*(f64*)(void*)ctmp); #else const f32 tmp = (f32)(*(f64 *)P); #endif P += 8; return tmp; } else { #ifdef __BIG_ENDIAN__ const f32 tmp = os::Byteswap::byteswap(*(f32 *)P); #else const f32 tmp = *(f32 *)P; #endif P += 4; return tmp; } } findNextNoneWhiteSpaceNumber(); f32 ftmp; P = core::fast_atof_move(P, ftmp); return ftmp; } // read 2-dimensional vector. Stops at semicolon after second value for text file format bool CXMeshFileLoader::readVector2(core::vector2df& vec) { vec.X = readFloat(); vec.Y = readFloat(); return true; } // read 3-dimensional vector. Stops at semicolon after third value for text file format bool CXMeshFileLoader::readVector3(core::vector3df& vec) { vec.X = readFloat(); vec.Y = readFloat(); vec.Z = readFloat(); return true; } // read color without alpha value. Stops after second semicolon after blue value bool CXMeshFileLoader::readRGB(video::SColor& color) { video::SColorf tmpColor; tmpColor.r = readFloat(); tmpColor.g = readFloat(); tmpColor.b = readFloat(); color = tmpColor.toSColor(); return checkForOneFollowingSemicolons(); } // read color with alpha value. Stops after second semicolon after blue value bool CXMeshFileLoader::readRGBA(video::SColor& color) { video::SColorf tmpColor; tmpColor.r = readFloat(); tmpColor.g = readFloat(); tmpColor.b = readFloat(); tmpColor.a = readFloat(); color = tmpColor.toSColor(); return checkForOneFollowingSemicolons(); } // read matrix from list of floats bool CXMeshFileLoader::readMatrix(core::matrix4& mat) { for (u32 i=0; i<16; ++i) mat[i] = readFloat(); return checkForOneFollowingSemicolons(); } } // end namespace scene } // end namespace irr