// 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 "CXFileReader.h" #include "os.h" #include "fast_atof.h" #include "coreutil.h" namespace irr { namespace scene { CXFileReader::CXFileReader(io::IReadFile* file) : MajorVersion(0), MinorVersion(0), binary(false), binaryNumCount(0), Buffer(0), Size(0), FloatSize(0), P(0), End(0), ErrorHappened(false), m_bFrameRemoved(false), m_pgCurFrame(0) { if (!file) { ErrorHappened = true; return; } if (!readFileIntoMemory(file)) { ErrorHappened = true; return; } if (!parseFile()) { ErrorHappened = true; return; } #ifdef _XREADER_DEBUG for( unsigned int i = 0; i < RootFrames.size(); i++ ) validateMesh(&RootFrames[i]); #endif for( unsigned int i = 0; i < RootFrames.size(); i++ ) { computeGlobalFrameMatrices(RootFrames[i], 0 ); } } CXFileReader::~CXFileReader() { if (Buffer) delete [] Buffer; for (u32 i=0; iMeshes.size(); ++m) { s32 vcnt = frame->Meshes[m].Vertices.size(); s32 icnt = frame->Meshes[m].Indices.size(); s32 ncnt = frame->Meshes[m].Normals.size(); s32 nicnt = frame->Meshes[m].NormalIndices.size(); // validate indices in mesh s32 i; for (i=0; iMeshes[m].Indices[i] < 0) { sprintf(tmp, "XLoader error: index %d smaller than 0 in mesh %u: %d, frame '%s'", i, m, frame->Meshes[m].Indices[i], frame->Name.c_str()); os::Printer::log(tmp, ELL_ERROR); error = true; } if (frame->Meshes[m].Indices[i] > vcnt-1) { sprintf(tmp, "XLoader error: invalid index %d in mesh %u: %d, frame '%s'", i, m, frame->Meshes[m].Indices[i], frame->Name.c_str()); os::Printer::log(tmp, ELL_ERROR); error = true; } } // validate normal indices for (i=0; iMeshes[m].NormalIndices[i] < 0) { sprintf(tmp, "XLoader error: normal index %d smaller than 0 in mesh %u: %d, frame '%s'", i, m, frame->Meshes[m].NormalIndices[i], frame->Name.c_str()); os::Printer::log(tmp, ELL_ERROR); error = true; } if (frame->Meshes[m].NormalIndices[i] > ncnt-1) { sprintf(tmp, "XLoader error: invalid normal index %d in mesh %u: %d, frame '%s'", i, m, frame->Meshes[m].NormalIndices[i], frame->Name.c_str()); os::Printer::log(tmp, ELL_ERROR); error = true; } } } // validate child frames for (u32 i=0; iChildFrames.size(); ++i) if (!validateMesh(&frame->ChildFrames[i])) error = true; return error; } //! Reads file into memory bool CXFileReader::readFileIntoMemory(io::IReadFile* file) { s32 Size = file->getSize(); if (Size < 12) { os::Printer::log("X File is too small.", ELL_WARNING); return false; } Buffer = new c8[Size]; //! read all into memory file->seek(0); // apparently sometimes files have been read already, so reset it if (file->read(Buffer, Size) != Size) { os::Printer::log("Could not read from x file.", ELL_WARNING); return false; } 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[2] = 0x0; tmp[0] = Buffer[4]; tmp[1] = Buffer[5]; MajorVersion = strtol(tmp, (char**) &P, 10); tmp[0] = Buffer[6]; tmp[1] = Buffer[7]; MinorVersion = strtol(tmp, (char**) &P, 10); //! read format if (strncmp(&Buffer[8], "txt ", 4) ==0) binary = false; else if (strncmp(&Buffer[8], "bin ", 4) ==0) binary = 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 CXFileReader::parseFile() { u32 u32Idx; while(parseDataObject()) { // loop } // loop through hiearchy and combine frames that have no mesh // and no name into its parent m_bFrameRemoved = false; for( u32Idx = 0; u32Idx < RootFrames.size(); u32Idx++ ) { optimizeFrames( &RootFrames[ u32Idx ], 0 ); } while( m_bFrameRemoved ) { m_bFrameRemoved = false; for( u32Idx = 0; u32Idx < RootFrames.size(); u32Idx++ ) { optimizeFrames( &RootFrames[ u32Idx ], 0 ); } } return true; } //! loop through hiearchy and combine frames that have no mesh or name into parent frame void CXFileReader::optimizeFrames( SXFrame * pgFrame, SXFrame * pgParent ) { if( pgParent ) { if( (0 == pgParent->Meshes.size()) && (0 == strlen( pgFrame->Name.c_str() )) && strlen( pgParent->Name.c_str() ) ) { // combine this frame with parent // add child frames to parent pgParent->LocalMatrix *= pgFrame->LocalMatrix; u32 c; for( c=0; cChildFrames.size(); ++c ) { // add child frames to parent pgParent->ChildFrames.push_back(pgFrame->ChildFrames[c]); } // add meshes to parent for( c=0; cMeshes.size(); ++c ) { // add meshes frames to parent pgParent->Meshes.push_back( pgFrame->Meshes[c] ); } // remove child frames in our list pgFrame->ChildFrames.clear(); // remove meshes pgFrame->Meshes.clear(); // find ourselve and remove from parent frame for( c=0; c< pgParent->ChildFrames.size(); ++c ) { if( &pgParent->ChildFrames[c] == pgFrame ) { //found ourself pgParent->ChildFrames.erase( c, 1 ); m_bFrameRemoved = true; return; } } } } for (u32 c=0; cChildFrames.size(); ++c) optimizeFrames( &pgFrame->ChildFrames[c], pgFrame ); } //! Parses the next Data object in the file bool CXFileReader::parseDataObject() { core::stringc objectName = getNextToken(); if (objectName.size() == 0) return false; // parse specific object if (objectName == "template") return parseDataObjectTemplate(); else if (objectName == "Frame") { if (!m_pgCurFrame) { RootFrames.push_back(SXFrame()); m_pgCurFrame = &RootFrames.getLast(); } else { m_pgCurFrame->ChildFrames.push_back(SXFrame()); m_pgCurFrame = &(m_pgCurFrame->ChildFrames.getLast()); } return parseDataObjectFrame( * m_pgCurFrame ); } else if (objectName == "Mesh") { // some meshes have no frames at all if (!m_pgCurFrame) { RootFrames.push_back(SXFrame()); m_pgCurFrame = &RootFrames.getLast(); } m_pgCurFrame->Meshes.push_back(SXMesh()); return parseDataObjectMesh(m_pgCurFrame->Meshes.getLast()); } else if (objectName == "AnimationSet") { AnimationSets.push_back(SXAnimationSet()); return parseDataObjectAnimationSet(AnimationSets.getLast()); } else if (objectName == "Material") { // template materials now available thanks to joeWright TemplateMaterials.push_back(SXTemplateMaterial()); TemplateMaterials.getLast().Name = getNextToken(); return parseDataObjectMaterial(TemplateMaterials.getLast().Material); } os::Printer::log("Unknown data object in x file", objectName.c_str()); return parseUnknownDataObject(); } bool CXFileReader::parseDataObjectFrame(SXFrame& frame) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading frame"); #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. if (!readHeadOfDataObject(&frame.Name)) { os::Printer::log("No opening brace in Frame found in x file", ELL_WARNING); return false; } // Now inside a frame. // read tokens until closing brace is reached. while(true) { core::stringc objectName = getNextToken(); if (objectName.size() == 0) { os::Printer::log("Unexpected ending found in Frame in x file.", ELL_WARNING); return false; } else if (objectName == "}") { break; // frame finished } else if (objectName == "Frame") { frame.ChildFrames.push_back(SXFrame()); if (!parseDataObjectFrame(frame.ChildFrames.getLast())) return false; } else if (objectName == "FrameTransformMatrix") { if (!parseDataObjectTransformationMatrix(frame.LocalMatrix)) return false; } else if (objectName == "Mesh") { frame.Meshes.push_back(SXMesh()); if (!parseDataObjectMesh(frame.Meshes.getLast())) return false; } else { os::Printer::log("Unknown data object in frame in x file", objectName.c_str()); if (!parseUnknownDataObject()) return false; } } return true; } bool CXFileReader::parseDataObjectTransformationMatrix(core::matrix4 &mat) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading Transformation Matrix"); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Transformation Matrix found in x file", ELL_WARNING); return false; } if (binary) { // read matrix in binary format if (readBinWord() != 7) { os::Printer::log("Binary X: Mesh: Expecting float list (for matrix)", ELL_WARNING); return false; } if (readBinDWord() != 0x10) { os::Printer::log("Binary X: Mesh: Should be 16 floats in matrix", ELL_WARNING); return false; } } for (s32 i=0; i<4; ++i) for (s32 j=0; j<4; ++j) mat(i,j)=readFloat(); if (!checkForTwoFollowingSemicolons()) { os::Printer::log("No finishing semicolon in Transformation Matrix found in x file", ELL_WARNING); return false; } if (getNextToken() != "}") { os::Printer::log("No closing brace in Transformation Matrix found in x file", ELL_WARNING); return false; } return true; } bool CXFileReader::parseDataObjectTemplate() { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading template"); #endif // parse a template data object. Currently not stored. core::stringc name = getNextToken(); // ignore left delimiter if (getNextToken() != "{") { os::Printer::log("Left delimiter in template data object missing.", name.c_str(), ELL_ERROR); return false; } // read GUID core::stringc 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 CXFileReader::parseDataObjectMesh(SXMesh &mesh) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading mesh"); #endif if (!readHeadOfDataObject(&mesh.Name)) { os::Printer::log("No opening brace in Mesh found in x file", ELL_WARNING); return false; } // read vertex count s32 nVertices = readInt(); // read vertices mesh.Vertices.set_used(nVertices); s32 count=0; if (binary) { // read vertices in binary format if (readBinWord() != 7) { os::Printer::log("Binary X: Mesh: Expecting float list (for vertices)", ELL_WARNING); return false; } count = readBinDWord(); if (count != (nVertices * 3)) { os::Printer::log("Binary X: Mesh: Value count not matching vertices count", ELL_WARNING); return false; } } for (s32 n=0; n polygonfaces; s32 currentIndex = 0; for (s32 k=0; k& indexCountPerFace) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading mesh material list"); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Mesh Material List found in x file", ELL_WARNING); return false; } // read material count readInt(); // read non triangulated face material index count s32 nFaceIndices = readInt(); // read non triangulated face indices core::array nonTriFaceIndices; nonTriFaceIndices.set_used(nFaceIndices); for (s32 i=0; i= 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; } bool CXFileReader::parseDataObjectAnimationSet(SXAnimationSet& set) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: Reading animation set"); #endif if (!readHeadOfDataObject(&set.AnimationName)) { os::Printer::log("No opening brace in Animation Set found in x file", ELL_WARNING); return false; } while(true) { core::stringc objectName = getNextToken(); if (objectName.size() == 0) { os::Printer::log("Unexpected ending found in Animation set in x file.", ELL_WARNING); return false; } else if (objectName == "}") { break; // animation set finished } else if (objectName == "Animation") { set.Animations.push_back(SXAnimation()); if (!parseDataObjectAnimation(set.Animations.getLast())) return false; } else { os::Printer::log("Unknown data object in animation set in x file", objectName.c_str()); if (!parseUnknownDataObject()) return false; } } return true; } bool CXFileReader::parseDataObjectAnimation(SXAnimation& anim) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: reading animation"); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Animation found in x file", ELL_WARNING); return false; } anim.closed = true; anim.linearPositionQuality = true; while(true) { core::stringc objectName = getNextToken(); if (objectName.size() == 0) { os::Printer::log("Unexpected ending found in Animation in x file.", ELL_WARNING); return false; } else if (objectName == "}") { break; // animation finished } else if (objectName == "AnimationKey") { anim.Keys.push_back(SXAnimationKey()); if (!parseDataObjectAnimationKey(anim.Keys.getLast())) return false; } else if (objectName == "AnimationOptions") { //TODO: parse options. if (!parseUnknownDataObject()) return false; } else if (objectName == "{") { // read frame name anim.FrameName = getNextToken(); core::stringc end = getNextToken(); if (end.size() == 0 || end != "}") { os::Printer::log("Unexpected ending found in Animation in x file.", ELL_WARNING); return false; } } else { if (objectName.size()>2 && objectName[0] == '{' && objectName[objectName.size()-1] == '}') { anim.FrameName = objectName.subString(1,objectName.size()-2); } else { os::Printer::log("Unknown data object in animation in x file", objectName.c_str()); if (!parseUnknownDataObject()) return false; } } } return true; } bool CXFileReader::parseDataObjectAnimationKey(SXAnimationKey& animkey) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: reading animation key"); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Animation Key found in x file", ELL_WARNING); return false; } // read key type animkey.keyType = readInt(); if ((animkey.keyType < 0) || (animkey.keyType > 4)) { os::Printer::log("Unknown key type found in Animation Key in x file", ELL_WARNING); return false; } // read number of keys animkey.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 (!binary && animkey.numberOfKeys == 0) getNextToken(); // skip semicolon animkey.init(); // read keys switch(animkey.keyType) { case 0: { //read quaternions for (s32 i=0; i& normals, core::array< s32 >& normalIndices, s32 triangulatedIndexCount, core::array< s32 >& indexCountPerFace) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: reading mesh normals"); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Mesh Normals found in x file", ELL_WARNING); return false; } // read count s32 nNormals; s32 count; nNormals = readInt(); normals.set_used(nNormals); // read normals if (binary) { if (readBinWord() != 7) { os::Printer::log("Binary X: MeshNormals: Expecting float list", ELL_WARNING); return false; } count = readBinDWord(); if (count != nNormals * 3) { os::Printer::log("Binary X: MeshNormals: Value count not equal to normal count", ELL_WARNING); return false; } } for (s32 i=0; i polygonfaces; for (s32 k=0; k& textureCoords) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: reading mesh texture coordinates"); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace in Mesh Texture Coordinates found in x file", ELL_WARNING); return false; } s32 nCoords; u32 count; nCoords = readInt(); if (binary) { if (readBinWord() != 7) { os::Printer::log("Binary X: MeshTextureCoords: Expecting float list", ELL_WARNING); return false; } count = readBinDWord(); } textureCoords.set_used(nCoords); for (s32 i=0; i& vertexColors) { #ifdef _XREADER_DEBUG os::Printer::log("CXFileReader: reading mesh vertex colors"); #endif if (!readHeadOfDataObject()) { os::Printer::log("No opening brace for Mesh Vertex Colors found in x file", ELL_WARNING); return false; } s32 nColors; u32 count; nColors = readInt(); if (binary) { if (readBinWord() != 7) { os::Printer::log("Binary X: MeshVertexColors: Expecting float list", ELL_WARNING); return false; } count = readBinDWord(); } vertexColors.set_used(nColors); for (s32 i=0; i"; 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 0x34: return "array"; } } // process text-formatted file else { findNextNoneWhiteSpace(); if (P >= End) return s; while(P < End && !core::isspace(P[0])) { s.append(P[0]); ++P; } } return s; } //! places pointer to next begin of a token, which must be a number, // and ignores comments void CXFileReader::findNextNoneWhiteSpaceNumber() { if (binary) return; while(true) { while((P < End) && (P[0] != '-') && (P[0] != '.') && !( core::isdigit(P[0]))) ++P; if (P >= End) return; // check if this is a comment if ((P[0] == '/' && P[1] == '/') || P[0] == '#') readUntilEndOfLine(); else break; } } // places pointer to next begin of a token, and ignores comments void CXFileReader::findNextNoneWhiteSpace() { if (binary) return; while(true) { while(P < End && (P[0]==' ' || P[0]=='\n' || P[0]=='\r' || P[0]=='\t')) ++P; if (P >= End) return; // check if this is a comment if ((P[0] == '/' && P[1] == '/') || P[0] == '#') readUntilEndOfLine(); else break; } } void CXFileReader::readUntilEndOfLine() { if (binary) return; while(P < End) { if (P[0] == '\n') { ++P; return; } ++P; } } //! Returns if the loaded mesh is static bool CXFileReader::isStaticMesh() const { return AnimationSets.empty(); } //! returns count of animations s32 CXFileReader::getAnimationSetCount() const { return AnimationSets.size(); } //! returns a specific animation set CXFileReader::SXAnimationSet& CXFileReader::getAnimationSet(s32 i) { return AnimationSets[i]; } //! returns the root frame of the mesh core::array & CXFileReader::getRootFrames() { return RootFrames; } void CXFileReader::computeGlobalFrameMatrices(SXFrame& frame, const SXFrame* const parent) { if (!parent) frame.GlobalMatrix = frame.LocalMatrix; else frame.GlobalMatrix = parent->GlobalMatrix * frame.LocalMatrix; #ifdef _XREADER_DEBUG char tmp[255]; sprintf(tmp, "CXFileReader: Frame %s ", frame.Name.c_str()); os::Printer::log(tmp); for (int i=0; i<4; ++i) { sprintf(tmp, " %f, %f, %f, %f", frame.LocalMatrix(i,0), frame.LocalMatrix(i,1), frame.LocalMatrix(i,2), frame.LocalMatrix(i,3)); os::Printer::log(tmp); } #endif for (u32 c=0; c