// 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. ".bsp") 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; i < Meshes.size(); ++i) delete Meshes[i]; Meshes.clear(); return AnimatedMesh; } bool CXMeshFileLoader::load(io::IReadFile *file) { if (!readFileIntoMemory(file)) return false; if (!parseFile()) return false; for (u32 n = 0; n < Meshes.size(); ++n) { SXMesh *mesh = Meshes[n]; // default material if nothing loaded if (!mesh->Materials.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; i < mesh->Materials.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; i < mesh->FaceMaterialIndices.size(); ++i) mesh->FaceMaterialIndices[i] = 0; } if (!mesh->HasVertexColors) { for (u32 j = 0; j < mesh->FaceMaterialIndices.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> verticesLinkIndex; verticesLinkIndex.reallocate(mesh->Vertices.size()); core::array> verticesLinkBuffer; verticesLinkBuffer.reallocate(mesh->Vertices.size()); for (i = 0; i < mesh->Vertices.size(); ++i) { verticesLinkIndex.push_back(core::array()); verticesLinkBuffer.push_back(core::array()); } for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) { for (u32 id = i * 3 + 0; id <= i * 3 + 2; ++id) { core::array &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; i < verticesLinkBuffer.size(); ++i) { if (!verticesLinkBuffer[i].size()) verticesLinkBuffer[i].push_back(0); } for (i = 0; i < mesh->Vertices.size(); ++i) { core::array &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; i < mesh->FaceMaterialIndices.size(); ++i) { scene::SSkinMeshBuffer *buffer = mesh->Buffers[mesh->FaceMaterialIndices[i]]; for (u32 id = i * 3 + 0; id <= i * 3 + 2; ++id) { core::array &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; j < mesh->WeightJoint.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 verticesLinkIndex; core::array verticesLinkBuffer; verticesLinkBuffer.set_used(mesh->Vertices.size()); // init with 0 for (i = 0; i < mesh->Vertices.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; i < mesh->FaceMaterialIndices.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; i < mesh->Vertices.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; i < mesh->Vertices.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 = (i < mesh->TCoords2.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; i < mesh->FaceMaterialIndices.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; i < mesh->FaceMaterialIndices.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; j < mesh->WeightJoint.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()) { auto n = AnimatedMesh->getJointNumber(name.c_str()); if (n.has_value()) { JointID = *n; joint = AnimatedMesh->getAllJoints()[JointID]; } } if (!joint) { #ifdef _XREADER_DEBUG os::Printer::log("creating joint ", name.c_str(), ELL_DEBUG); #endif joint = AnimatedMesh->addJoint(Parent); joint->Name = name.c_str(); 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 < nVertices; ++n) { readVector3(mesh.Vertices[n].Pos); mesh.Vertices[n].Color = 0xFFFFFFFF; mesh.Vertices[n].Normal = core::vector3df(0.0f); } if (!checkForTwoFollowingSemicolons()) { os::Printer::log("No finishing semicolon in Mesh Vertex Array found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); } // read faces const u32 nFaces = readInt(); mesh.Indices.set_used(nFaces * 3); mesh.IndexCountPerFace.set_used(nFaces); core::array polygonfaces; u32 currentIndex = 0; for (u32 k = 0; k < nFaces; ++k) { const u32 fcnt = readInt(); if (fcnt != 3) { if (fcnt < 3) { os::Printer::log("Invalid face count (<3) found in Mesh x file reader.", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } // read face indices polygonfaces.set_used(fcnt); u32 triangles = (fcnt - 2); mesh.Indices.set_used(mesh.Indices.size() + ((triangles - 1) * 3)); mesh.IndexCountPerFace[k] = (u16)(triangles * 3); for (u32 f = 0; f < fcnt; ++f) polygonfaces[f] = readInt(); for (u32 jk = 0; jk < triangles; ++jk) { mesh.Indices[currentIndex++] = polygonfaces[0]; mesh.Indices[currentIndex++] = polygonfaces[jk + 1]; mesh.Indices[currentIndex++] = polygonfaces[jk + 2]; } // TODO: change face indices in material list } else { mesh.Indices[currentIndex++] = readInt(); mesh.Indices[currentIndex++] = readInt(); mesh.Indices[currentIndex++] = readInt(); mesh.IndexCountPerFace[k] = 3; } } for (u32 j = 0; j < mesh.Indices.size(); j++) { if (mesh.Indices[j] >= 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 < dcnt; ++j) { const u32 type = readInt(); // const u32 tesselator = readInt(); readInt(); const u32 semantics = readInt(); const u32 index = readInt(); switch (semantics) { case 3: normalpos = size; normaltype = type; break; case 5: if (index == 0) { uvpos = size; uvtype = type; } else if (index == 1) { uv2pos = size; uv2type = type; } break; case 6: tangentpos = size; tangenttype = type; break; case 7: binormalpos = size; binormaltype = type; break; default: break; } switch (type) { case 0: size += 4; break; case 1: size += 8; break; case 2: size += 12; break; case 3: size += 16; break; case 4: case 5: case 6: size += 4; break; case 7: size += 8; break; case 8: case 9: size += 4; break; case 10: size += 8; break; case 11: size += 4; break; case 12: size += 8; break; case 13: size += 4; break; case 14: size += 4; break; case 15: size += 4; break; case 16: size += 8; break; } } const u32 datasize = readInt(); u32 *data = new u32[datasize]; for (j = 0; j < datasize; ++j) data[j] = readInt(); if (!checkForOneFollowingSemicolons()) { os::Printer::log("No finishing semicolon in DeclData found.", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); } if (!checkForClosingBrace()) { os::Printer::log("No closing brace in DeclData.", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); delete[] data; SET_ERR_AND_RETURN(); } u8 *dataptr = (u8 *)data; if ((uv2pos != -1) && (uv2type == 1)) mesh.TCoords2.reallocate(mesh.Vertices.size()); for (j = 0; j < mesh.Vertices.size(); ++j) { if ((normalpos != -1) && (normaltype == 2)) mesh.Vertices[j].Normal.set(*((core::vector3df *)(dataptr + normalpos))); if ((uvpos != -1) && (uvtype == 1)) mesh.Vertices[j].TCoords.set(*((core::vector2df *)(dataptr + uvpos))); if ((uv2pos != -1) && (uv2type == 1)) mesh.TCoords2.push_back(*((core::vector2df *)(dataptr + uv2pos))); dataptr += size; } delete[] data; } else if (objectName == "FVFData") { if (!readHeadOfDataObject()) { os::Printer::log("No starting brace in FVFData found.", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } const u32 dataformat = readInt(); const u32 datasize = readInt(); u32 *data = new u32[datasize]; for (u32 j = 0; j < datasize; ++j) data[j] = readInt(); if (dataformat & 0x102) { // 2nd uv set mesh.TCoords2.reallocate(mesh.Vertices.size()); u8 *dataptr = (u8 *)data; const u32 size = ((dataformat >> 8) & 0xf) * sizeof(core::vector2df); for (u32 j = 0; j < mesh.Vertices.size(); ++j) { mesh.TCoords2.push_back(*((core::vector2df *)(dataptr))); dataptr += size; } } delete[] data; if (!checkForOneFollowingSemicolons()) { os::Printer::log("No finishing semicolon in FVFData found.", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); } if (!checkForClosingBrace()) { os::Printer::log("No closing brace in FVFData found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } } else if (objectName == "XSkinMeshHeader") { if (!parseDataObjectSkinMeshHeader(mesh)) return false; } else if (objectName == "SkinWeights") { // mesh.SkinWeights.push_back(SXSkinWeight()); // if (!parseDataObjectSkinWeights(mesh.SkinWeights.getLast())) if (!parseDataObjectSkinWeights(mesh)) return false; } else { os::Printer::log("Unknown data object in mesh in x file", objectName.c_str(), ELL_WARNING); if (!parseUnknownDataObject()) return false; } } return true; } bool CXMeshFileLoader::parseDataObjectSkinWeights(SXMesh &mesh) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading mesh skin weights", ELL_DEBUG); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening 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(); } core::stringc TransformNodeName; if (!getNextTokenAsString(TransformNodeName)) { os::Printer::log("Unknown syntax while reading transform node name string in .x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } mesh.HasSkinning = true; auto n = AnimatedMesh->getJointNumber(TransformNodeName.c_str()); CSkinnedMesh::SJoint *joint = n.has_value() ? AnimatedMesh->getAllJoints()[*n] : nullptr; 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.c_str(); } // 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; i < nWeights; ++i) { mesh.WeightJoint.push_back(*n); mesh.WeightNum.push_back(joint->Weights.size()); CSkinnedMesh::SWeight *weight = AnimatedMesh->addWeight(joint); weight->buffer_id = 0; weight->vertex_id = readInt(); } // read vertex weights for (i = jointStart; i < jointStart + nWeights; ++i) joint->Weights[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 < nNormals; ++i) readVector3(normals[i]); if (!checkForTwoFollowingSemicolons()) { os::Printer::log("No finishing semicolon in Mesh Normals Array found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); } core::array 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 < nFNormals; ++k) { const u32 fcnt = readInt(); u32 triangles = fcnt - 2; u32 indexcount = triangles * 3; if (indexcount != mesh.IndexCountPerFace[k]) { os::Printer::log("Not matching normal and face index count found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } if (indexcount == 3) { // default, only one triangle in this face for (u32 h = 0; h < 3; ++h) { const u32 normalnum = readInt(); mesh.Vertices[mesh.Indices[normalidx++]].Normal.set(normals[normalnum]); } } else { polygonfaces.set_used(fcnt); // multiple triangles in this face for (u32 h = 0; h < fcnt; ++h) polygonfaces[h] = readInt(); for (u32 jk = 0; jk < triangles; ++jk) { mesh.Vertices[mesh.Indices[normalidx++]].Normal.set(normals[polygonfaces[0]]); mesh.Vertices[mesh.Indices[normalidx++]].Normal.set(normals[polygonfaces[jk + 1]]); mesh.Vertices[mesh.Indices[normalidx++]].Normal.set(normals[polygonfaces[jk + 2]]); } } } if (!checkForTwoFollowingSemicolons()) { os::Printer::log("No finishing semicolon in Mesh Face Normals 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 Normals 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::parseDataObjectMeshTextureCoords(SXMesh &mesh) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: reading mesh texture coordinates", ELL_DEBUG); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Mesh Texture Coordinates found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } const u32 nCoords = readInt(); // if (nCoords >= 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 < nCoords; ++i) readVector2(mesh.Vertices[i].TCoords); if (!checkForTwoFollowingSemicolons()) { os::Printer::log("No finishing semicolon in Mesh Texture Coordinates 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); SET_ERR_AND_RETURN(); } return true; } bool CXMeshFileLoader::parseDataObjectMeshVertexColors(SXMesh &mesh) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: reading mesh vertex colors", ELL_DEBUG); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace for Mesh Vertex Colors found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } mesh.HasVertexColors = true; const u32 nColors = readInt(); for (u32 i = 0; i < nColors; ++i) { const u32 Index = readInt(); if (Index >= 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 < mesh.IndexCountPerFace.size(); ++tfi) { if (tfi < nFaceIndices) ind = readInt(); if (ind >= 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; k < fc; ++k) mesh.FaceMaterialIndices[triangulatedindex++] = ind; } // in version 03.02, the face indices end with two semicolons. // commented out version check, as version 03.03 exported from blender also has 2 semicolons if (!BinaryFormat) { // && MajorVersion == 3 && MinorVersion <= 2) if (P[0] == ';') ++P; } // read following data objects while (true) { core::stringc objectName = getNextToken(); if (objectName.size() == 0) { os::Printer::log("Unexpected ending found in Mesh Material list in .x file.", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } else if (objectName == "}") { break; // material list finished } else if (objectName == "{") { // template materials now available thanks to joeWright objectName = getNextToken(); mesh.Materials.push_back(video::SMaterial()); getNextToken(); // skip } } else if (objectName == "Material") { mesh.Materials.push_back(video::SMaterial()); if (!parseUnknownDataObject()) return false; } else if (objectName == ";") { // ignore } else { os::Printer::log("Unknown data object in material list in x file", objectName.c_str(), ELL_WARNING); if (!parseUnknownDataObject()) return false; } } return true; } bool CXMeshFileLoader::parseDataObjectAnimationSet() { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading animation set", ELL_DEBUG); #endif core::stringc AnimationName; if (!readHeadOfDataObject(&AnimationName)) { os::Printer::log("No opening brace in Animation Set found in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } os::Printer::log("Reading animationset ", AnimationName, ELL_DEBUG); while (true) { core::stringc objectName = getNextToken(); if (objectName.size() == 0) { os::Printer::log("Unexpected ending found in Animation set 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 set finished } else if (objectName == "Animation") { if (!parseDataObjectAnimation()) return false; } else { os::Printer::log("Unknown data object in animation set in x file", objectName.c_str(), ELL_WARNING); if (!parseUnknownDataObject()) return false; } } return true; } bool CXMeshFileLoader::parseDataObjectAnimationTicksPerSecond() { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: reading AnimationTicksPerSecond", 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(); } const u32 ticks = readInt(); if (!checkForOneFollowingSemicolons()) { os::Printer::log("No closing semicolon in AnimationTicksPerSecond 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 AnimationTicksPerSecond in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } AnimatedMesh->setAnimationSpeed(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 auto n = AnimatedMesh->getJointNumber(FrameName.c_str()); CSkinnedMesh::SJoint *joint; if (n.has_value()) { joint = AnimatedMesh->getAllJoints()[*n]; } else { #ifdef _XREADER_DEBUG os::Printer::log("creating joint for animation ", FrameName.c_str(), ELL_DEBUG); #endif joint = AnimatedMesh->addJoint(0); joint->Name = FrameName.c_str(); } joint->PositionKeys.reallocate(joint->PositionKeys.size() + animationDump.PositionKeys.size()); for (u32 n = 0; n < animationDump.PositionKeys.size(); ++n) { joint->PositionKeys.push_back(animationDump.PositionKeys[n]); } joint->ScaleKeys.reallocate(joint->ScaleKeys.size() + animationDump.ScaleKeys.size()); for (u32 n = 0; n < animationDump.ScaleKeys.size(); ++n) { joint->ScaleKeys.push_back(animationDump.ScaleKeys[n]); } joint->RotationKeys.reallocate(joint->RotationKeys.size() + animationDump.RotationKeys.size()); for (u32 n = 0; n < animationDump.RotationKeys.size(); ++n) { joint->RotationKeys.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; i < numberOfKeys; ++i) { // read time const f32 time = (f32)readInt(); // read keys switch (keyType) { case 0: // rotation { // read quaternions // read count if (readInt() != 4) { os::Printer::log("Expected 4 numbers in animation key in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); SET_ERR_AND_RETURN(); } f32 W = -readFloat(); f32 X = -readFloat(); f32 Y = -readFloat(); f32 Z = -readFloat(); if (!checkForTwoFollowingSemicolons()) { os::Printer::log("No finishing semicolon after quaternion animation key in x file", ELL_WARNING); os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING); } ISkinnedMesh::SRotationKey *key = AnimatedMesh->addRotationKey(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