diff options
Diffstat (limited to 'libs/assimp/code/AssetLib/X')
-rw-r--r-- | libs/assimp/code/AssetLib/X/XFileExporter.cpp | 541 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/X/XFileExporter.h | 140 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/X/XFileHelper.h | 234 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/X/XFileImporter.cpp | 691 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/X/XFileImporter.h | 148 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/X/XFileParser.cpp | 1360 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/X/XFileParser.h | 158 |
7 files changed, 3272 insertions, 0 deletions
diff --git a/libs/assimp/code/AssetLib/X/XFileExporter.cpp b/libs/assimp/code/AssetLib/X/XFileExporter.cpp new file mode 100644 index 0000000..f0b1608 --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileExporter.cpp @@ -0,0 +1,541 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +@author: Richard Steffen, 2014 +---------------------------------------------------------------------- +*/ + + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_X_EXPORTER + +#include "AssetLib/X/XFileExporter.h" +#include "PostProcessing/ConvertToLHProcess.h" + +#include <assimp/Bitmap.h> +#include <assimp/BaseImporter.h> +#include <assimp/fast_atof.h> +#include <assimp/SceneCombiner.h> +#include <assimp/DefaultIOSystem.h> +#include <assimp/Exceptional.h> +#include <assimp/IOSystem.hpp> +#include <assimp/scene.h> +#include <assimp/light.h> + +#include <ctime> +#include <set> +#include <memory> + +using namespace Assimp; + +namespace Assimp +{ + +// ------------------------------------------------------------------------------------------------ +// Worker function for exporting a scene to Collada. Prototyped and registered in Exporter.cpp +void ExportSceneXFile(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties) +{ + std::string path = DefaultIOSystem::absolutePath(std::string(pFile)); + std::string file = DefaultIOSystem::completeBaseName(std::string(pFile)); + + // create/copy Properties + ExportProperties props(*pProperties); + + // set standard properties if not set + if (!props.HasPropertyBool(AI_CONFIG_EXPORT_XFILE_64BIT)) props.SetPropertyBool(AI_CONFIG_EXPORT_XFILE_64BIT, false); + + // invoke the exporter + XFileExporter iDoTheExportThing( pScene, pIOSystem, path, file, &props); + + if (iDoTheExportThing.mOutput.fail()) { + throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); + } + + // we're still here - export successfully completed. Write result to the given IOSYstem + std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt")); + if (outfile == nullptr) { + throw DeadlyExportError("could not open output .x file: " + std::string(pFile)); + } + + // XXX maybe use a small wrapper around IOStream that behaves like std::stringstream in order to avoid the extra copy. + outfile->Write( iDoTheExportThing.mOutput.str().c_str(), static_cast<size_t>(iDoTheExportThing.mOutput.tellp()),1); +} + +} // end of namespace Assimp + + +// ------------------------------------------------------------------------------------------------ +// Constructor for a specific scene to export +XFileExporter::XFileExporter(const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file, const ExportProperties* pProperties) + : mProperties(pProperties), + mIOSystem(pIOSystem), + mPath(path), + mFile(file), + mScene(pScene), + mSceneOwned(false), + endstr("\n") +{ + // make sure that all formatting happens using the standard, C locale and not the user's current locale + mOutput.imbue( std::locale("C") ); + mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); + + // start writing + WriteFile(); +} + +// ------------------------------------------------------------------------------------------------ +// Destructor +XFileExporter::~XFileExporter() +{ + if(mSceneOwned) { + delete mScene; + } +} + +// ------------------------------------------------------------------------------------------------ +// Starts writing the contents +void XFileExporter::WriteFile() +{ + // note, that all realnumber values must be comma separated in x files + mOutput.setf(std::ios::fixed); + mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); // precision for ai_real + + // entry of writing the file + WriteHeader(); + + mOutput << startstr << "Frame DXCC_ROOT {" << endstr; + PushTag(); + + aiMatrix4x4 I; // identity + WriteFrameTransform(I); + + WriteNode(mScene->mRootNode); + PopTag(); + + mOutput << startstr << "}" << endstr; + +} + +// ------------------------------------------------------------------------------------------------ +// Writes the asset header +void XFileExporter::WriteHeader() +{ + if (mProperties->GetPropertyBool(AI_CONFIG_EXPORT_XFILE_64BIT) == true) + mOutput << startstr << "xof 0303txt 0064" << endstr; + else + mOutput << startstr << "xof 0303txt 0032" << endstr; + mOutput << endstr; + mOutput << startstr << "template Frame {" << endstr; + PushTag(); + mOutput << startstr << "<3d82ab46-62da-11cf-ab39-0020af71e433>" << endstr; + mOutput << startstr << "[...]" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template Matrix4x4 {" << endstr; + PushTag(); + mOutput << startstr << "<f6f23f45-7686-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "array FLOAT matrix[16];" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template FrameTransformMatrix {" << endstr; + PushTag(); + mOutput << startstr << "<f6f23f41-7686-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "Matrix4x4 frameMatrix;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template Vector {" << endstr; + PushTag(); + mOutput << startstr << "<3d82ab5e-62da-11cf-ab39-0020af71e433>" << endstr; + mOutput << startstr << "FLOAT x;" << endstr; + mOutput << startstr << "FLOAT y;" << endstr; + mOutput << startstr << "FLOAT z;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template MeshFace {" << endstr; + PushTag(); + mOutput << startstr << "<3d82ab5f-62da-11cf-ab39-0020af71e433>" << endstr; + mOutput << startstr << "DWORD nFaceVertexIndices;" << endstr; + mOutput << startstr << "array DWORD faceVertexIndices[nFaceVertexIndices];" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template Mesh {" << endstr; + PushTag(); + mOutput << startstr << "<3d82ab44-62da-11cf-ab39-0020af71e433>" << endstr; + mOutput << startstr << "DWORD nVertices;" << endstr; + mOutput << startstr << "array Vector vertices[nVertices];" << endstr; + mOutput << startstr << "DWORD nFaces;" << endstr; + mOutput << startstr << "array MeshFace faces[nFaces];" << endstr; + mOutput << startstr << "[...]" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template MeshNormals {" << endstr; + PushTag(); + mOutput << startstr << "<f6f23f43-7686-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "DWORD nNormals;" << endstr; + mOutput << startstr << "array Vector normals[nNormals];" << endstr; + mOutput << startstr << "DWORD nFaceNormals;" << endstr; + mOutput << startstr << "array MeshFace faceNormals[nFaceNormals];" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template Coords2d {" << endstr; + PushTag(); + mOutput << startstr << "<f6f23f44-7686-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "FLOAT u;" << endstr; + mOutput << startstr << "FLOAT v;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template MeshTextureCoords {" << endstr; + PushTag(); + mOutput << startstr << "<f6f23f40-7686-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "DWORD nTextureCoords;" << endstr; + mOutput << startstr << "array Coords2d textureCoords[nTextureCoords];" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template ColorRGBA {" << endstr; + PushTag(); + mOutput << startstr << "<35ff44e0-6c7c-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "FLOAT red;" << endstr; + mOutput << startstr << "FLOAT green;" << endstr; + mOutput << startstr << "FLOAT blue;" << endstr; + mOutput << startstr << "FLOAT alpha;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template IndexedColor {" << endstr; + PushTag(); + mOutput << startstr << "<1630b820-7842-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "DWORD index;" << endstr; + mOutput << startstr << "ColorRGBA indexColor;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template MeshVertexColors {" << endstr; + PushTag(); + mOutput << startstr << "<1630b821-7842-11cf-8f52-0040333594a3>" << endstr; + mOutput << startstr << "DWORD nVertexColors;" << endstr; + mOutput << startstr << "array IndexedColor vertexColors[nVertexColors];" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template VertexElement {" << endstr; + PushTag(); + mOutput << startstr << "<f752461c-1e23-48f6-b9f8-8350850f336f>" << endstr; + mOutput << startstr << "DWORD Type;" << endstr; + mOutput << startstr << "DWORD Method;" << endstr; + mOutput << startstr << "DWORD Usage;" << endstr; + mOutput << startstr << "DWORD UsageIndex;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; + mOutput << startstr << "template DeclData {" << endstr; + PushTag(); + mOutput << startstr << "<bf22e553-292c-4781-9fea-62bd554bdd93>" << endstr; + mOutput << startstr << "DWORD nElements;" << endstr; + mOutput << startstr << "array VertexElement Elements[nElements];" << endstr; + mOutput << startstr << "DWORD nDWords;" << endstr; + mOutput << startstr << "array DWORD data[nDWords];" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + mOutput << endstr; +} + + +// Writes the material setup +void XFileExporter::WriteFrameTransform(aiMatrix4x4& m) +{ + mOutput << startstr << "FrameTransformMatrix {" << endstr << " "; + PushTag(); + mOutput << startstr << m.a1 << ", " << m.b1 << ", " << m.c1 << ", " << m.d1 << "," << endstr; + mOutput << startstr << m.a2 << ", " << m.b2 << ", " << m.c2 << ", " << m.d2 << "," << endstr; + mOutput << startstr << m.a3 << ", " << m.b3 << ", " << m.c3 << ", " << m.d3 << "," << endstr; + mOutput << startstr << m.a4 << ", " << m.b4 << ", " << m.c4 << ", " << m.d4 << ";;" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr << endstr; +} + + +// ------------------------------------------------------------------------------------------------ +// Recursively writes the given node +void XFileExporter::WriteNode( aiNode* pNode) +{ + if (pNode->mName.length==0) + { + std::stringstream ss; + ss << "Node_" << pNode; + pNode->mName.Set(ss.str()); + } + mOutput << startstr << "Frame " << toXFileString(pNode->mName) << " {" << endstr; + + PushTag(); + + aiMatrix4x4 m = pNode->mTransformation; + + WriteFrameTransform(m); + + for (size_t i = 0; i < pNode->mNumMeshes; ++i) + WriteMesh(mScene->mMeshes[pNode->mMeshes[i]]); + + // recursive call the Nodes + for (size_t i = 0; i < pNode->mNumChildren; ++i) + WriteNode(pNode->mChildren[i]); + + PopTag(); + + mOutput << startstr << "}" << endstr << endstr; +} + +void XFileExporter::WriteMesh(aiMesh* mesh) +{ + mOutput << startstr << "Mesh " << toXFileString(mesh->mName) << "_mShape" << " {" << endstr; + + PushTag(); + + // write all the vertices + mOutput << startstr << mesh->mNumVertices << ";" << endstr; + for (size_t a = 0; a < mesh->mNumVertices; a++) + { + aiVector3D &v = mesh->mVertices[a]; + mOutput << startstr << v[0] << ";"<< v[1] << ";" << v[2] << ";"; + if (a < mesh->mNumVertices - 1) + mOutput << "," << endstr; + else + mOutput << ";" << endstr; + } + + // write all the faces + mOutput << startstr << mesh->mNumFaces << ";" << endstr; + for( size_t a = 0; a < mesh->mNumFaces; ++a ) + { + const aiFace& face = mesh->mFaces[a]; + mOutput << startstr << face.mNumIndices << ";"; + // must be counter clockwise triangle + //for(int b = face.mNumIndices - 1; b >= 0 ; --b) + for(size_t b = 0; b < face.mNumIndices ; ++b) + { + mOutput << face.mIndices[b]; + //if (b > 0) + if (b<face.mNumIndices-1) + mOutput << ","; + else + mOutput << ";"; + } + + if (a < mesh->mNumFaces - 1) + mOutput << "," << endstr; + else + mOutput << ";" << endstr; + } + + mOutput << endstr; + + if (mesh->HasTextureCoords(0)) + { + const aiMaterial* mat = mScene->mMaterials[mesh->mMaterialIndex]; + aiString relpath; + mat->Get(_AI_MATKEY_TEXTURE_BASE, aiTextureType_DIFFUSE, 0, relpath); + + mOutput << startstr << "MeshMaterialList {" << endstr; + PushTag(); + mOutput << startstr << "1;" << endstr; // number of materials + mOutput << startstr << mesh->mNumFaces << ";" << endstr; // number of faces + mOutput << startstr; + for( size_t a = 0; a < mesh->mNumFaces; ++a ) + { + mOutput << "0"; // the material index + if (a < mesh->mNumFaces - 1) + mOutput << ", "; + else + mOutput << ";" << endstr; + } + mOutput << startstr << "Material {" << endstr; + PushTag(); + mOutput << startstr << "1.0; 1.0; 1.0; 1.000000;;" << endstr; + mOutput << startstr << "1.000000;" << endstr; // power + mOutput << startstr << "0.000000; 0.000000; 0.000000;;" << endstr; // specularity + mOutput << startstr << "0.000000; 0.000000; 0.000000;;" << endstr; // emission + mOutput << startstr << "TextureFilename { \""; + + writePath(relpath); + + mOutput << "\"; }" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + PopTag(); + mOutput << startstr << "}" << endstr; + } + + // write normals (every vertex has one) + if (mesh->HasNormals()) + { + mOutput << endstr << startstr << "MeshNormals {" << endstr; + mOutput << startstr << mesh->mNumVertices << ";" << endstr; + for (size_t a = 0; a < mesh->mNumVertices; a++) + { + aiVector3D &v = mesh->mNormals[a]; + // because we have a LHS and also changed wth winding, we need to invert the normals again + mOutput << startstr << -v[0] << ";"<< -v[1] << ";" << -v[2] << ";"; + if (a < mesh->mNumVertices - 1) + mOutput << "," << endstr; + else + mOutput << ";" << endstr; + } + + mOutput << startstr << mesh->mNumFaces << ";" << endstr; + for (size_t a = 0; a < mesh->mNumFaces; a++) + { + const aiFace& face = mesh->mFaces[a]; + mOutput << startstr << face.mNumIndices << ";"; + + //for(int b = face.mNumIndices-1; b >= 0 ; --b) + for(size_t b = 0; b < face.mNumIndices ; ++b) + { + mOutput << face.mIndices[b]; + //if (b > 0) + if (b<face.mNumIndices-1) + mOutput << ","; + else + mOutput << ";"; + } + + if (a < mesh->mNumFaces-1) + mOutput << "," << endstr; + else + mOutput << ";" << endstr; + } + mOutput << startstr << "}" << endstr; + } + + // write texture UVs if available + if (mesh->HasTextureCoords(0)) + { + mOutput << endstr << startstr << "MeshTextureCoords {" << endstr; + mOutput << startstr << mesh->mNumVertices << ";" << endstr; + for (size_t a = 0; a < mesh->mNumVertices; a++) + //for (int a = (int)mesh->mNumVertices-1; a >=0 ; a--) + { + aiVector3D& uv = mesh->mTextureCoords[0][a]; // uv of first uv layer for the vertex + mOutput << startstr << uv.x << ";" << uv.y; + if (a < mesh->mNumVertices-1) + //if (a >0 ) + mOutput << ";," << endstr; + else + mOutput << ";;" << endstr; + } + mOutput << startstr << "}" << endstr; + } + + // write color channel if available + if (mesh->HasVertexColors(0)) + { + mOutput << endstr << startstr << "MeshVertexColors {" << endstr; + mOutput << startstr << mesh->mNumVertices << ";" << endstr; + for (size_t a = 0; a < mesh->mNumVertices; a++) + { + aiColor4D& mColors = mesh->mColors[0][a]; // color of first vertex color set for the vertex + mOutput << startstr << a << ";" << mColors.r << ";" << mColors.g << ";" << mColors.b << ";" << mColors.a << ";;"; + if (a < mesh->mNumVertices-1) + mOutput << "," << endstr; + else + mOutput << ";" << endstr; + } + mOutput << startstr << "}" << endstr; + } + /* + else + { + mOutput << endstr << startstr << "MeshVertexColors {" << endstr; + mOutput << startstr << mesh->mNumVertices << ";" << endstr; + for (size_t a = 0; a < mesh->mNumVertices; a++) + { + aiColor4D* mColors = mesh->mColors[a]; + mOutput << startstr << a << ";0.500000;0.000000;0.000000;0.500000;;"; + if (a < mesh->mNumVertices-1) + mOutput << "," << endstr; + else + mOutput << ";" << endstr; + } + mOutput << startstr << "}" << endstr; + } + */ + PopTag(); + mOutput << startstr << "}" << endstr << endstr; + +} + +std::string XFileExporter::toXFileString(aiString &name) +{ + std::string pref = ""; // node name prefix to prevent unexpected start of string + std::string str = pref + std::string(name.C_Str()); + for (int i=0; i < (int) str.length(); ++i) + { + if ((str[i] >= '0' && str[i] <= '9') || // 0-9 + (str[i] >= 'A' && str[i] <= 'Z') || // A-Z + (str[i] >= 'a' && str[i] <= 'z')) // a-z + continue; + str[i] = '_'; + } + return str; +} + +void XFileExporter::writePath(const aiString &path) +{ + std::string str = std::string(path.C_Str()); + BaseImporter::ConvertUTF8toISO8859_1(str); + + while( str.find( "\\\\") != std::string::npos) + str.replace( str.find( "\\\\"), 2, "\\"); + + while (str.find('\\') != std::string::npos) + str.replace(str.find('\\'), 1, "/"); + + mOutput << str; + +} + +#endif +#endif diff --git a/libs/assimp/code/AssetLib/X/XFileExporter.h b/libs/assimp/code/AssetLib/X/XFileExporter.h new file mode 100644 index 0000000..1d9a5ae --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileExporter.h @@ -0,0 +1,140 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +@author: Richard Steffen, 2014 + +---------------------------------------------------------------------- +*/ + +/** @file XFileExporter.h + * Declares the exporter class to write a scene to a Collada file + */ +#ifndef AI_XFILEEXPORTER_H_INC +#define AI_XFILEEXPORTER_H_INC + +#include <assimp/ai_assert.h> +#include <assimp/matrix4x4.h> +#include <assimp/Exporter.hpp> +#include <sstream> + +struct aiScene; +struct aiNode; +struct aiMesh; +struct aiString; + +namespace Assimp { + +class IOSystem; + + +/// Helper class to export a given scene to a X-file. +/// Note: an xFile uses a left hand system. Assimp used a right hand system (OpenGL), therefore we have to transform everything +class XFileExporter +{ +public: + /// Constructor for a specific scene to export + XFileExporter(const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file, const ExportProperties* pProperties); + + /// Destructor + virtual ~XFileExporter(); + +protected: + /// Starts writing the contents + void WriteFile(); + + /// Writes the asset header + void WriteHeader(); + + /// write a frame transform + void WriteFrameTransform(aiMatrix4x4& m); + + /// Recursively writes the given node + void WriteNode( aiNode* pNode ); + + /// write a mesh entry of the scene + void WriteMesh( aiMesh* mesh); + + /// Enters a new xml element, which increases the indentation + void PushTag() { startstr.append( " "); } + + /// Leaves an element, decreasing the indentation + void PopTag() { + ai_assert( startstr.length() > 1); + startstr.erase( startstr.length() - 2); + } + +public: + /// Stringstream to write all output into + std::stringstream mOutput; + +protected: + + /// normalize the name to be accepted by xfile readers + std::string toXFileString(aiString &name); + + /// hold the properties pointer + const ExportProperties* mProperties; + + /// write a path + void writePath(const aiString &path); + + /// The IOSystem for output + IOSystem* mIOSystem; + + /// Path of the directory where the scene will be exported + const std::string mPath; + + /// Name of the file (without extension) where the scene will be exported + const std::string mFile; + + /// The scene to be written + const aiScene* mScene; + bool mSceneOwned; + + /// current line start string, contains the current indentation for simple stream insertion + std::string startstr; + + /// current line end string for simple stream insertion + std::string endstr; + +}; + +} + +#endif // !! AI_XFILEEXPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/X/XFileHelper.h b/libs/assimp/code/AssetLib/X/XFileHelper.h new file mode 100644 index 0000000..3830eb4 --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileHelper.h @@ -0,0 +1,234 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file Defines the helper data structures for importing XFiles */ +#ifndef AI_XFILEHELPER_H_INC +#define AI_XFILEHELPER_H_INC + +#include <cstdint> +#include <string> +#include <vector> + +#include <assimp/anim.h> +#include <assimp/mesh.h> +#include <assimp/quaternion.h> +#include <assimp/types.h> + +namespace Assimp { +namespace XFile { + +/** Helper structure representing a XFile mesh face */ +struct Face { + std::vector<unsigned int> mIndices; +}; + +/** Helper structure representing a texture filename inside a material and its potential source */ +struct TexEntry { + std::string mName; + bool mIsNormalMap; // true if the texname was specified in a NormalmapFilename tag + + TexEntry() AI_NO_EXCEPT : + mName(), + mIsNormalMap(false) { + // empty + } + TexEntry(const std::string &pName, bool pIsNormalMap = false) : + mName(pName), mIsNormalMap(pIsNormalMap) { + // empty + } +}; + +/** Helper structure representing a XFile material */ +struct Material { + std::string mName; + bool mIsReference; // if true, mName holds a name by which the actual material can be found in the material list + aiColor4D mDiffuse; + ai_real mSpecularExponent; + aiColor3D mSpecular; + aiColor3D mEmissive; + std::vector<TexEntry> mTextures; + size_t sceneIndex; ///< the index under which it was stored in the scene's material list + + Material() AI_NO_EXCEPT : + mIsReference(false), + mSpecularExponent(), + sceneIndex(SIZE_MAX) { + // empty + } +}; + +/** Helper structure to represent a bone weight */ +struct BoneWeight { + unsigned int mVertex; + ai_real mWeight; +}; + +/** Helper structure to represent a bone in a mesh */ +struct Bone { + std::string mName; + std::vector<BoneWeight> mWeights; + aiMatrix4x4 mOffsetMatrix; +}; + +/** Helper structure to represent an XFile mesh */ +struct Mesh { + std::string mName; + std::vector<aiVector3D> mPositions; + std::vector<Face> mPosFaces; + std::vector<aiVector3D> mNormals; + std::vector<Face> mNormFaces; + unsigned int mNumTextures; + std::vector<aiVector2D> mTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + unsigned int mNumColorSets; + std::vector<aiColor4D> mColors[AI_MAX_NUMBER_OF_COLOR_SETS]; + + std::vector<unsigned int> mFaceMaterials; + std::vector<Material> mMaterials; + + std::vector<Bone> mBones; + + explicit Mesh(const std::string &pName = std::string()) AI_NO_EXCEPT + : mName(pName), + mPositions(), + mPosFaces(), + mNormals(), + mNormFaces(), + mNumTextures(0), + mTexCoords{}, + mNumColorSets(0), + mColors{}, + mFaceMaterials(), + mMaterials(), + mBones() { + // empty + } +}; + +/** Helper structure to represent a XFile frame */ +struct Node { + std::string mName; + aiMatrix4x4 mTrafoMatrix; + Node *mParent; + std::vector<Node *> mChildren; + std::vector<Mesh *> mMeshes; + + Node() AI_NO_EXCEPT + : mName(), + mTrafoMatrix(), + mParent(nullptr), + mChildren(), + mMeshes() { + // empty + } + explicit Node(Node *pParent) : + mName(), mTrafoMatrix(), mParent(pParent), mChildren(), mMeshes() { + // empty + } + + ~Node() { + for (unsigned int a = 0; a < mChildren.size(); ++a) { + delete mChildren[a]; + } + for (unsigned int a = 0; a < mMeshes.size(); ++a) { + delete mMeshes[a]; + } + } +}; + +struct MatrixKey { + double mTime; + aiMatrix4x4 mMatrix; +}; + +/** Helper structure representing a single animated bone in a XFile */ +struct AnimBone { + std::string mBoneName; + std::vector<aiVectorKey> mPosKeys; // either three separate key sequences for position, rotation, scaling + std::vector<aiQuatKey> mRotKeys; + std::vector<aiVectorKey> mScaleKeys; + std::vector<MatrixKey> mTrafoKeys; // or a combined key sequence of transformation matrices. +}; + +/** Helper structure to represent an animation set in a XFile */ +struct Animation { + std::string mName; + std::vector<AnimBone *> mAnims; + + ~Animation() { + for (unsigned int a = 0; a < mAnims.size(); a++) + delete mAnims[a]; + } +}; + +/** Helper structure analogue to aiScene */ +struct Scene { + Node *mRootNode; + + std::vector<Mesh *> mGlobalMeshes; // global meshes found outside of any frames + std::vector<Material> mGlobalMaterials; // global materials found outside of any meshes. + + std::vector<Animation *> mAnims; + unsigned int mAnimTicksPerSecond; + + Scene() AI_NO_EXCEPT + : mRootNode(nullptr), + mGlobalMeshes(), + mGlobalMaterials(), + mAnimTicksPerSecond(0) { + // empty + } + ~Scene() { + delete mRootNode; + mRootNode = nullptr; + for (unsigned int a = 0; a < mGlobalMeshes.size(); ++a) { + delete mGlobalMeshes[a]; + } + for (unsigned int a = 0; a < mAnims.size(); ++a) { + delete mAnims[a]; + } + } +}; + +} // end of namespace XFile +} // end of namespace Assimp + +#endif // AI_XFILEHELPER_H_INC diff --git a/libs/assimp/code/AssetLib/X/XFileImporter.cpp b/libs/assimp/code/AssetLib/X/XFileImporter.cpp new file mode 100644 index 0000000..d23eb57 --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileImporter.cpp @@ -0,0 +1,691 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ +/** @file XFileImporter.cpp + * @brief Implementation of the XFile importer class + */ + +#ifndef ASSIMP_BUILD_NO_X_IMPORTER + +#include "AssetLib/X/XFileImporter.h" +#include "AssetLib/X/XFileParser.h" +#include "PostProcessing/ConvertToLHProcess.h" + +#include <assimp/TinyFormatter.h> +#include <assimp/IOSystem.hpp> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/importerdesc.h> + +#include <cctype> +#include <memory> + +using namespace Assimp; +using namespace Assimp::Formatter; + +static const aiImporterDesc desc = { + "Direct3D XFile Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour, + 1, + 3, + 1, + 5, + "x" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +XFileImporter::XFileImporter() +: mBuffer() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +XFileImporter::~XFileImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool XFileImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const { + static const uint32_t token[] = { AI_MAKE_MAGIC("xof ") }; + return CheckMagicToken(pIOHandler,pFile,token,AI_COUNT_OF(token)); +} + +// ------------------------------------------------------------------------------------------------ +// Get file extension list +const aiImporterDesc* XFileImporter::GetInfo () const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void XFileImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) { + // read file into memory + std::unique_ptr<IOStream> file( pIOHandler->Open( pFile)); + if ( file.get() == nullptr ) { + throw DeadlyImportError( "Failed to open file ", pFile, "." ); + } + + static const size_t MinSize = 16; + size_t fileSize = file->FileSize(); + if ( fileSize < MinSize ) { + throw DeadlyImportError( "XFile is too small." ); + } + + // in the hope that binary files will never start with a BOM ... + mBuffer.resize( fileSize + 1); + file->Read( &mBuffer.front(), 1, fileSize); + ConvertToUTF8(mBuffer); + + // parse the file into a temporary representation + XFileParser parser( mBuffer); + + // and create the proper return structures out of it + CreateDataRepresentationFromImport( pScene, parser.GetImportedData()); + + // if nothing came from it, report it as error + if ( !pScene->mRootNode ) { + throw DeadlyImportError( "XFile is ill-formatted - no content imported." ); + } +} + +// ------------------------------------------------------------------------------------------------ +// Constructs the return data structure out of the imported data. +void XFileImporter::CreateDataRepresentationFromImport( aiScene* pScene, XFile::Scene* pData) +{ + // Read the global materials first so that meshes referring to them can find them later + ConvertMaterials( pScene, pData->mGlobalMaterials); + + // copy nodes, extracting meshes and materials on the way + pScene->mRootNode = CreateNodes( pScene, nullptr, pData->mRootNode); + + // extract animations + CreateAnimations( pScene, pData); + + // read the global meshes that were stored outside of any node + if( !pData->mGlobalMeshes.empty() ) { + // create a root node to hold them if there isn't any, yet + if( pScene->mRootNode == nullptr ) { + pScene->mRootNode = new aiNode; + pScene->mRootNode->mName.Set( "$dummy_node"); + } + + // convert all global meshes and store them in the root node. + // If there was one before, the global meshes now suddenly have its transformation matrix... + // Don't know what to do there, I don't want to insert another node under the present root node + // just to avoid this. + CreateMeshes( pScene, pScene->mRootNode, pData->mGlobalMeshes); + } + + if (!pScene->mRootNode) { + throw DeadlyImportError( "No root node" ); + } + + // Convert everything to OpenGL space... it's the same operation as the conversion back, so we can reuse the step directly + MakeLeftHandedProcess convertProcess; + convertProcess.Execute( pScene); + + FlipWindingOrderProcess flipper; + flipper.Execute(pScene); + + // finally: create a dummy material if not material was imported + if( pScene->mNumMaterials == 0) { + pScene->mNumMaterials = 1; + // create the Material + aiMaterial* mat = new aiMaterial; + int shadeMode = (int) aiShadingMode_Gouraud; + mat->AddProperty<int>( &shadeMode, 1, AI_MATKEY_SHADING_MODEL); + // material colours + int specExp = 1; + + aiColor3D clr = aiColor3D( 0, 0, 0); + mat->AddProperty( &clr, 1, AI_MATKEY_COLOR_EMISSIVE); + mat->AddProperty( &clr, 1, AI_MATKEY_COLOR_SPECULAR); + + clr = aiColor3D( 0.5f, 0.5f, 0.5f); + mat->AddProperty( &clr, 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty( &specExp, 1, AI_MATKEY_SHININESS); + + pScene->mMaterials = new aiMaterial*[1]; + pScene->mMaterials[0] = mat; + } +} + +// ------------------------------------------------------------------------------------------------ +// Recursively creates scene nodes from the imported hierarchy. +aiNode* XFileImporter::CreateNodes( aiScene* pScene, aiNode* pParent, const XFile::Node* pNode) { + if ( !pNode ) { + return nullptr; + } + + // create node + aiNode* node = new aiNode; + node->mName.length = (ai_uint32)pNode->mName.length(); + node->mParent = pParent; + memcpy( node->mName.data, pNode->mName.c_str(), pNode->mName.length()); + node->mName.data[node->mName.length] = 0; + node->mTransformation = pNode->mTrafoMatrix; + + // convert meshes from the source node + CreateMeshes( pScene, node, pNode->mMeshes); + + // handle children + if( !pNode->mChildren.empty() ) { + node->mNumChildren = (unsigned int)pNode->mChildren.size(); + node->mChildren = new aiNode* [node->mNumChildren]; + + for ( unsigned int a = 0; a < pNode->mChildren.size(); ++a ) { + node->mChildren[ a ] = CreateNodes( pScene, node, pNode->mChildren[ a ] ); + } + } + + return node; +} + +// ------------------------------------------------------------------------------------------------ +// Creates the meshes for the given node. +void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vector<XFile::Mesh*>& pMeshes) { + if (pMeshes.empty()) { + return; + } + + // create a mesh for each mesh-material combination in the source node + std::vector<aiMesh*> meshes; + for( unsigned int a = 0; a < pMeshes.size(); ++a ) { + XFile::Mesh* sourceMesh = pMeshes[a]; + if ( nullptr == sourceMesh ) { + continue; + } + + // first convert its materials so that we can find them with their index afterwards + ConvertMaterials( pScene, sourceMesh->mMaterials); + + unsigned int numMaterials = std::max( (unsigned int)sourceMesh->mMaterials.size(), 1u); + for( unsigned int b = 0; b < numMaterials; ++b ) { + // collect the faces belonging to this material + std::vector<unsigned int> faces; + unsigned int numVertices = 0; + if( !sourceMesh->mFaceMaterials.empty() ) { + // if there is a per-face material defined, select the faces with the corresponding material + for( unsigned int c = 0; c < sourceMesh->mFaceMaterials.size(); ++c ) { + if( sourceMesh->mFaceMaterials[c] == b) { + faces.push_back( c); + numVertices += (unsigned int)sourceMesh->mPosFaces[c].mIndices.size(); + } + } + } else { + // if there is no per-face material, place everything into one mesh + for( unsigned int c = 0; c < sourceMesh->mPosFaces.size(); ++c ) { + faces.push_back( c); + numVertices += (unsigned int)sourceMesh->mPosFaces[c].mIndices.size(); + } + } + + // no faces/vertices using this material? strange... + if ( numVertices == 0 ) { + continue; + } + + // create a submesh using this material + aiMesh* mesh = new aiMesh; + meshes.push_back( mesh); + + // find the material in the scene's material list. Either own material + // or referenced material, it should already have a valid index + if( !sourceMesh->mFaceMaterials.empty() ) { + mesh->mMaterialIndex = static_cast<unsigned int>(sourceMesh->mMaterials[b].sceneIndex); + } else { + mesh->mMaterialIndex = 0; + } + + // Create properly sized data arrays in the mesh. We store unique vertices per face, + // as specified + mesh->mNumVertices = numVertices; + mesh->mVertices = new aiVector3D[numVertices]; + mesh->mNumFaces = (unsigned int)faces.size(); + mesh->mFaces = new aiFace[mesh->mNumFaces]; + + // name + mesh->mName.Set(sourceMesh->mName); + + // normals? + if ( sourceMesh->mNormals.size() > 0 ) { + mesh->mNormals = new aiVector3D[ numVertices ]; + } + // texture coords + for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c ) { + if ( !sourceMesh->mTexCoords[ c ].empty() ) { + mesh->mTextureCoords[ c ] = new aiVector3D[ numVertices ]; + } + } + // vertex colors + for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c ) { + if ( !sourceMesh->mColors[ c ].empty() ) { + mesh->mColors[ c ] = new aiColor4D[ numVertices ]; + } + } + + // now collect the vertex data of all data streams present in the imported mesh + unsigned int newIndex( 0 ); + std::vector<unsigned int> orgPoints; // from which original point each new vertex stems + orgPoints.resize( numVertices, 0); + + for( unsigned int c = 0; c < faces.size(); ++c ) { + unsigned int f = faces[c]; // index of the source face + const XFile::Face& pf = sourceMesh->mPosFaces[f]; // position source face + + // create face. either triangle or triangle fan depending on the index count + aiFace& df = mesh->mFaces[c]; // destination face + df.mNumIndices = (unsigned int)pf.mIndices.size(); + df.mIndices = new unsigned int[ df.mNumIndices]; + + // collect vertex data for indices of this face + for( unsigned int d = 0; d < df.mNumIndices; ++d ) { + df.mIndices[ d ] = newIndex; + const unsigned int newIdx( pf.mIndices[ d ] ); + if ( newIdx > sourceMesh->mPositions.size() ) { + continue; + } + + orgPoints[newIndex] = pf.mIndices[d]; + + // Position + mesh->mVertices[newIndex] = sourceMesh->mPositions[pf.mIndices[d]]; + // Normal, if present + if ( mesh->HasNormals() ) { + if ( sourceMesh->mNormFaces[ f ].mIndices.size() > d ) { + const size_t idx( sourceMesh->mNormFaces[ f ].mIndices[ d ] ); + mesh->mNormals[ newIndex ] = sourceMesh->mNormals[ idx ]; + } + } + + // texture coord sets + for( unsigned int e = 0; e < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++e ) { + if( mesh->HasTextureCoords( e)) { + aiVector2D tex = sourceMesh->mTexCoords[e][pf.mIndices[d]]; + mesh->mTextureCoords[e][newIndex] = aiVector3D( tex.x, 1.0f - tex.y, 0.0f); + } + } + // vertex color sets + for ( unsigned int e = 0; e < AI_MAX_NUMBER_OF_COLOR_SETS; ++e ) { + if ( mesh->HasVertexColors( e ) ) { + mesh->mColors[ e ][ newIndex ] = sourceMesh->mColors[ e ][ pf.mIndices[ d ] ]; + } + } + + newIndex++; + } + } + + // there should be as much new vertices as we calculated before + ai_assert( newIndex == numVertices); + + // convert all bones of the source mesh which influence vertices in this newly created mesh + const std::vector<XFile::Bone>& bones = sourceMesh->mBones; + std::vector<aiBone*> newBones; + for( unsigned int c = 0; c < bones.size(); ++c ) { + const XFile::Bone& obone = bones[c]; + // set up a vertex-linear array of the weights for quick searching if a bone influences a vertex + std::vector<ai_real> oldWeights( sourceMesh->mPositions.size(), 0.0); + for ( unsigned int d = 0; d < obone.mWeights.size(); ++d ) { + oldWeights[ obone.mWeights[ d ].mVertex ] = obone.mWeights[ d ].mWeight; + } + + // collect all vertex weights that influence a vertex in the new mesh + std::vector<aiVertexWeight> newWeights; + newWeights.reserve( numVertices); + for( unsigned int d = 0; d < orgPoints.size(); ++d ) { + // does the new vertex stem from an old vertex which was influenced by this bone? + ai_real w = oldWeights[orgPoints[d]]; + if ( w > 0.0 ) { + newWeights.push_back( aiVertexWeight( d, w ) ); + } + } + + // if the bone has no weights in the newly created mesh, ignore it + if ( newWeights.empty() ) { + continue; + } + + // create + aiBone* nbone = new aiBone; + newBones.push_back( nbone); + // copy name and matrix + nbone->mName.Set( obone.mName); + nbone->mOffsetMatrix = obone.mOffsetMatrix; + nbone->mNumWeights = (unsigned int)newWeights.size(); + nbone->mWeights = new aiVertexWeight[nbone->mNumWeights]; + for ( unsigned int d = 0; d < newWeights.size(); ++d ) { + nbone->mWeights[ d ] = newWeights[ d ]; + } + } + + // store the bones in the mesh + mesh->mNumBones = (unsigned int)newBones.size(); + if( !newBones.empty()) { + mesh->mBones = new aiBone*[mesh->mNumBones]; + std::copy( newBones.begin(), newBones.end(), mesh->mBones); + } + } + } + + // reallocate scene mesh array to be large enough + aiMesh** prevArray = pScene->mMeshes; + pScene->mMeshes = new aiMesh*[pScene->mNumMeshes + meshes.size()]; + if( prevArray) { + memcpy( pScene->mMeshes, prevArray, pScene->mNumMeshes * sizeof( aiMesh*)); + delete [] prevArray; + } + + // allocate mesh index array in the node + pNode->mNumMeshes = (unsigned int)meshes.size(); + pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; + + // store all meshes in the mesh library of the scene and store their indices in the node + for( unsigned int a = 0; a < meshes.size(); a++) { + pScene->mMeshes[pScene->mNumMeshes] = meshes[a]; + pNode->mMeshes[a] = pScene->mNumMeshes; + pScene->mNumMeshes++; + } +} + +// ------------------------------------------------------------------------------------------------ +// Converts the animations from the given imported data and creates them in the scene. +void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData) { + std::vector<aiAnimation*> newAnims; + + for( unsigned int a = 0; a < pData->mAnims.size(); ++a ) { + const XFile::Animation* anim = pData->mAnims[a]; + // some exporters mock me with empty animation tags. + if ( anim->mAnims.empty() ) { + continue; + } + + // create a new animation to hold the data + aiAnimation* nanim = new aiAnimation; + newAnims.push_back( nanim); + nanim->mName.Set( anim->mName); + // duration will be determined by the maximum length + nanim->mDuration = 0; + nanim->mTicksPerSecond = pData->mAnimTicksPerSecond; + nanim->mNumChannels = (unsigned int)anim->mAnims.size(); + nanim->mChannels = new aiNodeAnim*[nanim->mNumChannels]; + + for( unsigned int b = 0; b < anim->mAnims.size(); ++b ) { + const XFile::AnimBone* bone = anim->mAnims[b]; + aiNodeAnim* nbone = new aiNodeAnim; + nbone->mNodeName.Set( bone->mBoneName); + nanim->mChannels[b] = nbone; + + // key-frames are given as combined transformation matrix keys + if( !bone->mTrafoKeys.empty() ) + { + nbone->mNumPositionKeys = (unsigned int)bone->mTrafoKeys.size(); + nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys]; + nbone->mNumRotationKeys = (unsigned int)bone->mTrafoKeys.size(); + nbone->mRotationKeys = new aiQuatKey[nbone->mNumRotationKeys]; + nbone->mNumScalingKeys = (unsigned int)bone->mTrafoKeys.size(); + nbone->mScalingKeys = new aiVectorKey[nbone->mNumScalingKeys]; + + for( unsigned int c = 0; c < bone->mTrafoKeys.size(); ++c) { + // deconstruct each matrix into separate position, rotation and scaling + double time = bone->mTrafoKeys[c].mTime; + aiMatrix4x4 trafo = bone->mTrafoKeys[c].mMatrix; + + // extract position + aiVector3D pos( trafo.a4, trafo.b4, trafo.c4); + + nbone->mPositionKeys[c].mTime = time; + nbone->mPositionKeys[c].mValue = pos; + + // extract scaling + aiVector3D scale; + scale.x = aiVector3D( trafo.a1, trafo.b1, trafo.c1).Length(); + scale.y = aiVector3D( trafo.a2, trafo.b2, trafo.c2).Length(); + scale.z = aiVector3D( trafo.a3, trafo.b3, trafo.c3).Length(); + nbone->mScalingKeys[c].mTime = time; + nbone->mScalingKeys[c].mValue = scale; + + // reconstruct rotation matrix without scaling + aiMatrix3x3 rotmat( + trafo.a1 / scale.x, trafo.a2 / scale.y, trafo.a3 / scale.z, + trafo.b1 / scale.x, trafo.b2 / scale.y, trafo.b3 / scale.z, + trafo.c1 / scale.x, trafo.c2 / scale.y, trafo.c3 / scale.z); + + // and convert it into a quaternion + nbone->mRotationKeys[c].mTime = time; + nbone->mRotationKeys[c].mValue = aiQuaternion( rotmat); + } + + // longest lasting key sequence determines duration + nanim->mDuration = std::max( nanim->mDuration, bone->mTrafoKeys.back().mTime); + } else { + // separate key sequences for position, rotation, scaling + nbone->mNumPositionKeys = (unsigned int)bone->mPosKeys.size(); + if (nbone->mNumPositionKeys != 0) { + nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys]; + for( unsigned int c = 0; c < nbone->mNumPositionKeys; ++c ) { + aiVector3D pos = bone->mPosKeys[c].mValue; + + nbone->mPositionKeys[c].mTime = bone->mPosKeys[c].mTime; + nbone->mPositionKeys[c].mValue = pos; + } + } + + // rotation + nbone->mNumRotationKeys = (unsigned int)bone->mRotKeys.size(); + if (nbone->mNumRotationKeys != 0) { + nbone->mRotationKeys = new aiQuatKey[nbone->mNumRotationKeys]; + for( unsigned int c = 0; c < nbone->mNumRotationKeys; ++c ) { + aiMatrix3x3 rotmat = bone->mRotKeys[c].mValue.GetMatrix(); + + nbone->mRotationKeys[c].mTime = bone->mRotKeys[c].mTime; + nbone->mRotationKeys[c].mValue = aiQuaternion( rotmat); + nbone->mRotationKeys[c].mValue.w *= -1.0f; // needs quat inversion + } + } + + // scaling + nbone->mNumScalingKeys = (unsigned int)bone->mScaleKeys.size(); + if (nbone->mNumScalingKeys != 0) { + nbone->mScalingKeys = new aiVectorKey[nbone->mNumScalingKeys]; + for( unsigned int c = 0; c < nbone->mNumScalingKeys; c++) + nbone->mScalingKeys[c] = bone->mScaleKeys[c]; + } + + // longest lasting key sequence determines duration + if( bone->mPosKeys.size() > 0) + nanim->mDuration = std::max( nanim->mDuration, bone->mPosKeys.back().mTime); + if( bone->mRotKeys.size() > 0) + nanim->mDuration = std::max( nanim->mDuration, bone->mRotKeys.back().mTime); + if( bone->mScaleKeys.size() > 0) + nanim->mDuration = std::max( nanim->mDuration, bone->mScaleKeys.back().mTime); + } + } + } + + // store all converted animations in the scene + if( newAnims.size() > 0) + { + pScene->mNumAnimations = (unsigned int)newAnims.size(); + pScene->mAnimations = new aiAnimation* [pScene->mNumAnimations]; + for( unsigned int a = 0; a < newAnims.size(); a++) + pScene->mAnimations[a] = newAnims[a]; + } +} + +// ------------------------------------------------------------------------------------------------ +// Converts all materials in the given array and stores them in the scene's material list. +void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector<XFile::Material>& pMaterials) +{ + // count the non-referrer materials in the array + unsigned int numNewMaterials( 0 ); + for ( unsigned int a = 0; a < pMaterials.size(); ++a ) { + if ( !pMaterials[ a ].mIsReference ) { + ++numNewMaterials; + } + } + + // resize the scene's material list to offer enough space for the new materials + if( numNewMaterials > 0 ) { + aiMaterial** prevMats = pScene->mMaterials; + pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials + numNewMaterials]; + if( nullptr != prevMats) { + ::memcpy( pScene->mMaterials, prevMats, pScene->mNumMaterials * sizeof( aiMaterial*)); + delete [] prevMats; + } + } + + // convert all the materials given in the array + for( unsigned int a = 0; a < pMaterials.size(); ++a ) { + XFile::Material& oldMat = pMaterials[a]; + if( oldMat.mIsReference) { + // find the material it refers to by name, and store its index + for( size_t b = 0; b < pScene->mNumMaterials; ++b ) { + aiString name; + pScene->mMaterials[b]->Get( AI_MATKEY_NAME, name); + if( strcmp( name.C_Str(), oldMat.mName.data()) == 0 ) { + oldMat.sceneIndex = a; + break; + } + } + + if( oldMat.sceneIndex == SIZE_MAX ) { + ASSIMP_LOG_WARN( "Could not resolve global material reference \"", oldMat.mName, "\"" ); + oldMat.sceneIndex = 0; + } + + continue; + } + + aiMaterial* mat = new aiMaterial; + aiString name; + name.Set( oldMat.mName); + mat->AddProperty( &name, AI_MATKEY_NAME); + + // Shading model: hard-coded to PHONG, there is no such information in an XFile + // FIX (aramis): If the specular exponent is 0, use gouraud shading. This is a bugfix + // for some models in the SDK (e.g. good old tiny.x) + int shadeMode = (int)oldMat.mSpecularExponent == 0.0f + ? aiShadingMode_Gouraud : aiShadingMode_Phong; + + mat->AddProperty<int>( &shadeMode, 1, AI_MATKEY_SHADING_MODEL); + // material colours + // Unclear: there's no ambient colour, but emissive. What to put for ambient? + // Probably nothing at all, let the user select a suitable default. + mat->AddProperty( &oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); + mat->AddProperty( &oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + mat->AddProperty( &oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); + mat->AddProperty( &oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS); + + + // texture, if there is one + if (1 == oldMat.mTextures.size() ) { + const XFile::TexEntry& otex = oldMat.mTextures.back(); + if (otex.mName.length()) { + // if there is only one texture assume it contains the diffuse color + aiString tex( otex.mName); + if ( otex.mIsNormalMap ) { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_NORMALS( 0 ) ); + } else { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_DIFFUSE( 0 ) ); + } + } + } else { + // Otherwise ... try to search for typical strings in the + // texture's file name like 'bump' or 'diffuse' + unsigned int iHM = 0,iNM = 0,iDM = 0,iSM = 0,iAM = 0,iEM = 0; + for( unsigned int b = 0; b < oldMat.mTextures.size(); ++b ) { + const XFile::TexEntry& otex = oldMat.mTextures[b]; + std::string sz = otex.mName; + if ( !sz.length() ) { + continue; + } + + // find the file name + std::string::size_type s = sz.find_last_of("\\/"); + if ( std::string::npos == s ) { + s = 0; + } + + // cut off the file extension + std::string::size_type sExt = sz.find_last_of('.'); + if (std::string::npos != sExt){ + sz[sExt] = '\0'; + } + + // convert to lower case for easier comparison + for ( unsigned int c = 0; c < sz.length(); ++c ) { + sz[ c ] = (char) tolower( (unsigned char) sz[ c ] ); + } + + // Place texture filename property under the corresponding name + aiString tex( oldMat.mTextures[b].mName); + + // bump map + if (std::string::npos != sz.find("bump", s) || std::string::npos != sz.find("height", s)) { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_HEIGHT(iHM++)); + } else if (otex.mIsNormalMap || std::string::npos != sz.find( "normal", s) || std::string::npos != sz.find("nm", s)) { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_NORMALS(iNM++)); + } else if (std::string::npos != sz.find( "spec", s) || std::string::npos != sz.find( "glanz", s)) { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_SPECULAR(iSM++)); + } else if (std::string::npos != sz.find( "ambi", s) || std::string::npos != sz.find( "env", s)) { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_AMBIENT(iAM++)); + } else if (std::string::npos != sz.find( "emissive", s) || std::string::npos != sz.find( "self", s)) { + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_EMISSIVE(iEM++)); + } else { + // Assume it is a diffuse texture + mat->AddProperty( &tex, AI_MATKEY_TEXTURE_DIFFUSE(iDM++)); + } + } + } + + pScene->mMaterials[pScene->mNumMaterials] = mat; + oldMat.sceneIndex = pScene->mNumMaterials; + pScene->mNumMaterials++; + } +} + +#endif // !! ASSIMP_BUILD_NO_X_IMPORTER diff --git a/libs/assimp/code/AssetLib/X/XFileImporter.h b/libs/assimp/code/AssetLib/X/XFileImporter.h new file mode 100644 index 0000000..6481390 --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileImporter.h @@ -0,0 +1,148 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file XFileImporter.h + * @brief Definition of the XFile importer class. + */ +#pragma once +#ifndef AI_XFILEIMPORTER_H_INC +#define AI_XFILEIMPORTER_H_INC + +#include <map> + +#include "XFileHelper.h" +#include <assimp/BaseImporter.h> + +#include <assimp/types.h> + +struct aiNode; + +namespace Assimp { + +namespace XFile { + struct Scene; + struct Node; +} + +// --------------------------------------------------------------------------- +/** The XFileImporter is a worker class capable of importing a scene from a + * DirectX file .x + */ +class XFileImporter : public BaseImporter { +public: + XFileImporter(); + ~XFileImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool CheckSig) const override; + +protected: + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc* GetInfo () const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler) override; + + // ------------------------------------------------------------------- + /** Constructs the return data structure out of the imported data. + * @param pScene The scene to construct the return data in. + * @param pData The imported data in the internal temporary + * representation. + */ + void CreateDataRepresentationFromImport( aiScene* pScene, XFile::Scene* pData); + + // ------------------------------------------------------------------- + /** Recursively creates scene nodes from the imported hierarchy. + * The meshes and materials of the nodes will be extracted on the way. + * @param pScene The scene to construct the return data in. + * @param pParent The parent node where to create new child nodes + * @param pNode The temporary node to copy. + * @return The created node + */ + aiNode* CreateNodes( aiScene* pScene, aiNode* pParent, + const XFile::Node* pNode); + + // ------------------------------------------------------------------- + /** Converts all meshes in the given mesh array. Each mesh is split + * up per material, the indices of the generated meshes are stored in + * the node structure. + * @param pScene The scene to construct the return data in. + * @param pNode The target node structure that references the + * constructed meshes. + * @param pMeshes The array of meshes to convert + */ + void CreateMeshes( aiScene* pScene, aiNode* pNode, + const std::vector<XFile::Mesh*>& pMeshes); + + // ------------------------------------------------------------------- + /** Converts the animations from the given imported data and creates + * them in the scene. + * @param pScene The scene to hold to converted animations + * @param pData The data to read the animations from + */ + void CreateAnimations( aiScene* pScene, const XFile::Scene* pData); + + // ------------------------------------------------------------------- + /** Converts all materials in the given array and stores them in the + * scene's material list. + * @param pScene The scene to hold the converted materials. + * @param pMaterials The material array to convert. + */ + void ConvertMaterials( aiScene* pScene, std::vector<XFile::Material>& pMaterials); + +protected: + /// Buffer to hold the loaded file + std::vector<char> mBuffer; +}; + +} // end of namespace Assimp + +#endif // AI_BASEIMPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/X/XFileParser.cpp b/libs/assimp/code/AssetLib/X/XFileParser.cpp new file mode 100644 index 0000000..558b979 --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileParser.cpp @@ -0,0 +1,1360 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/** @file Implementation of the XFile parser helper class */ + +#ifndef ASSIMP_BUILD_NO_X_IMPORTER + +#include "XFileParser.h" +#include "XFileHelper.h" +#include <assimp/ByteSwapper.h> +#include <assimp/Exceptional.h> +#include <assimp/StringUtils.h> +#include <assimp/TinyFormatter.h> +#include <assimp/fast_atof.h> +#include <assimp/DefaultLogger.hpp> + +using namespace Assimp; +using namespace Assimp::XFile; +using namespace Assimp::Formatter; + +#ifndef ASSIMP_BUILD_NO_COMPRESSED_X + +#include "Common/Compression.h" + +// Magic identifier for MSZIP compressed data +constexpr unsigned int MSZIP_MAGIC = 0x4B43; +constexpr size_t MSZIP_BLOCK = 32786l; + +#endif // !! ASSIMP_BUILD_NO_COMPRESSED_X + +// ------------------------------------------------------------------------------------------------ +// Throws an exception with a line number and the given text. +template<typename... T> +AI_WONT_RETURN void XFileParser::ThrowException(T&&... args) { + if (mIsBinaryFormat) { + throw DeadlyImportError(args...); + } else { + throw DeadlyImportError("Line ", mLineNumber, ": ", args...); + } +} + +// ------------------------------------------------------------------------------------------------ +// Constructor. Creates a data structure out of the XFile given in the memory block. +XFileParser::XFileParser(const std::vector<char> &pBuffer) : + mMajorVersion(0), mMinorVersion(0), mIsBinaryFormat(false), mBinaryNumCount(0), mP(nullptr), mEnd(nullptr), mLineNumber(0), mScene(nullptr) { + // vector to store uncompressed file for INFLATE'd X files + std::vector<char> uncompressed; + + // set up memory pointers + mP = &pBuffer.front(); + mEnd = mP + pBuffer.size() - 1; + + // check header + if (0 != strncmp(mP, "xof ", 4)) { + throw DeadlyImportError("Header mismatch, file is not an XFile."); + } + + // read version. It comes in a four byte format such as "0302" + mMajorVersion = (unsigned int)(mP[4] - 48) * 10 + (unsigned int)(mP[5] - 48); + mMinorVersion = (unsigned int)(mP[6] - 48) * 10 + (unsigned int)(mP[7] - 48); + + bool compressed = false; + + // txt - pure ASCII text format + if (strncmp(mP + 8, "txt ", 4) == 0) + mIsBinaryFormat = false; + + // bin - Binary format + else if (strncmp(mP + 8, "bin ", 4) == 0) + mIsBinaryFormat = true; + + // tzip - Inflate compressed text format + else if (strncmp(mP + 8, "tzip", 4) == 0) { + mIsBinaryFormat = false; + compressed = true; + } + // bzip - Inflate compressed binary format + else if (strncmp(mP + 8, "bzip", 4) == 0) { + mIsBinaryFormat = true; + compressed = true; + } else + ThrowException("Unsupported x-file format '", mP[8], mP[9], mP[10], mP[11], "'"); + + // float size + mBinaryFloatSize = (unsigned int)(mP[12] - 48) * 1000 + (unsigned int)(mP[13] - 48) * 100 + (unsigned int)(mP[14] - 48) * 10 + (unsigned int)(mP[15] - 48); + + if (mBinaryFloatSize != 32 && mBinaryFloatSize != 64) + ThrowException("Unknown float size ", mBinaryFloatSize, " specified in x-file header."); + + // The x format specifies size in bits, but we work in bytes + mBinaryFloatSize /= 8; + + mP += 16; + + // If this is a compressed X file, apply the inflate algorithm to it + if (compressed) { +#ifdef ASSIMP_BUILD_NO_COMPRESSED_X + throw DeadlyImportError("Assimp was built without compressed X support"); +#else + /* /////////////////////////////////////////////////////////////////////// + * COMPRESSED X FILE FORMAT + * /////////////////////////////////////////////////////////////////////// + * [xhead] + * 2 major + * 2 minor + * 4 type // bzip,tzip + * [mszip_master_head] + * 4 unkn // checksum? + * 2 unkn // flags? (seems to be constant) + * [mszip_head] + * 2 ofs // offset to next section + * 2 magic // 'CK' + * ... ofs bytes of data + * ... next mszip_head + * + * http://www.kdedevelopers.org/node/3181 has been very helpful. + * /////////////////////////////////////////////////////////////////////// + */ + + // skip unknown data (checksum, flags?) + mP += 6; + + // First find out how much storage we'll need. Count sections. + const char *P1 = mP; + unsigned int est_out = 0; + + while (P1 + 3 < mEnd) { + // read next offset + uint16_t ofs = *((uint16_t *)P1); + AI_SWAP2(ofs); + P1 += 2; + + if (ofs >= MSZIP_BLOCK) + throw DeadlyImportError("X: Invalid offset to next MSZIP compressed block"); + + // check magic word + uint16_t magic = *((uint16_t *)P1); + AI_SWAP2(magic); + P1 += 2; + + if (magic != MSZIP_MAGIC) + throw DeadlyImportError("X: Unsupported compressed format, expected MSZIP header"); + + // and advance to the next offset + P1 += ofs; + est_out += MSZIP_BLOCK; // one decompressed block is 327861 in size + } + + // Allocate storage and terminating zero and do the actual uncompressing + Compression compression; + uncompressed.resize(est_out + 1); + char *out = &uncompressed.front(); + if (compression.open(mIsBinaryFormat ? Compression::Format::Binary : Compression::Format::ASCII, + Compression::FlushMode::SyncFlush, -Compression::MaxWBits)) { + while (mP + 3 < mEnd) { + uint16_t ofs = *((uint16_t *)mP); + AI_SWAP2(ofs); + mP += 4; + + if (mP + ofs > mEnd + 2) { + throw DeadlyImportError("X: Unexpected EOF in compressed chunk"); + } + out += compression.decompressBlock(mP, ofs, out, MSZIP_BLOCK); + mP += ofs; + } + compression.close(); + } + + // ok, update pointers to point to the uncompressed file data + mP = &uncompressed[0]; + mEnd = out; + + // FIXME: we don't need the compressed data anymore, could release + // it already for better memory usage. Consider breaking const-co. + ASSIMP_LOG_INFO("Successfully decompressed MSZIP-compressed file"); +#endif // !! ASSIMP_BUILD_NO_COMPRESSED_X + } else { + // start reading here + ReadUntilEndOfLine(); + } + + mScene = new Scene; + ParseFile(); + + // filter the imported hierarchy for some degenerated cases + if (mScene->mRootNode) { + FilterHierarchy(mScene->mRootNode); + } +} + +// ------------------------------------------------------------------------------------------------ +// Destructor. Destroys all imported data along with it +XFileParser::~XFileParser() { + // kill everything we created + delete mScene; +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseFile() { + bool running = true; + while (running) { + // read name of next object + std::string objectName = GetNextToken(); + if (objectName.length() == 0) { + break; + } + + // parse specific object + if (objectName == "template") { + ParseDataObjectTemplate(); + } else if (objectName == "Frame") { + ParseDataObjectFrame(nullptr); + } else if (objectName == "Mesh") { + // some meshes have no frames at all + Mesh *mesh = new Mesh; + ParseDataObjectMesh(mesh); + mScene->mGlobalMeshes.push_back(mesh); + } else if (objectName == "AnimTicksPerSecond") + ParseDataObjectAnimTicksPerSecond(); + else if (objectName == "AnimationSet") + ParseDataObjectAnimationSet(); + else if (objectName == "Material") { + // Material outside of a mesh or node + Material material; + ParseDataObjectMaterial(&material); + mScene->mGlobalMaterials.push_back(material); + } else if (objectName == "}") { + // whatever? + ASSIMP_LOG_WARN("} found in dataObject"); + } else { + // unknown format + ASSIMP_LOG_WARN("Unknown data object in animation of .x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectTemplate() { + // parse a template data object. Currently not stored. + std::string name; + readHeadOfDataObject(&name); + + // read GUID + std::string guid = GetNextToken(); + + // read and ignore data members + bool running = true; + while (running) { + std::string s = GetNextToken(); + + if (s == "}") { + break; + } + + if (s.length() == 0) { + ThrowException("Unexpected end of file reached while parsing template definition"); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectFrame(Node *pParent) { + // 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. + std::string name; + readHeadOfDataObject(&name); + + // create a named node and place it at its parent, if given + Node *node = new Node(pParent); + node->mName = name; + if (pParent) { + pParent->mChildren.push_back(node); + } else { + // there might be multiple root nodes + if (mScene->mRootNode != nullptr) { + // place a dummy root if not there + if (mScene->mRootNode->mName != "$dummy_root") { + Node *exroot = mScene->mRootNode; + mScene->mRootNode = new Node(nullptr); + mScene->mRootNode->mName = "$dummy_root"; + mScene->mRootNode->mChildren.push_back(exroot); + exroot->mParent = mScene->mRootNode; + } + // put the new node as its child instead + mScene->mRootNode->mChildren.push_back(node); + node->mParent = mScene->mRootNode; + } else { + // it's the first node imported. place it as root + mScene->mRootNode = node; + } + } + + // Now inside a frame. + // read tokens until closing brace is reached. + bool running = true; + while (running) { + std::string objectName = GetNextToken(); + if (objectName.size() == 0) + ThrowException("Unexpected end of file reached while parsing frame"); + + if (objectName == "}") + break; // frame finished + else if (objectName == "Frame") + ParseDataObjectFrame(node); // child frame + else if (objectName == "FrameTransformMatrix") + ParseDataObjectTransformationMatrix(node->mTrafoMatrix); + else if (objectName == "Mesh") { + Mesh *mesh = new Mesh(name); + node->mMeshes.push_back(mesh); + ParseDataObjectMesh(mesh); + } else { + ASSIMP_LOG_WARN("Unknown data object in frame in x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectTransformationMatrix(aiMatrix4x4 &pMatrix) { + // read header, we're not interested if it has a name + readHeadOfDataObject(); + + // read its components + pMatrix.a1 = ReadFloat(); + pMatrix.b1 = ReadFloat(); + pMatrix.c1 = ReadFloat(); + pMatrix.d1 = ReadFloat(); + pMatrix.a2 = ReadFloat(); + pMatrix.b2 = ReadFloat(); + pMatrix.c2 = ReadFloat(); + pMatrix.d2 = ReadFloat(); + pMatrix.a3 = ReadFloat(); + pMatrix.b3 = ReadFloat(); + pMatrix.c3 = ReadFloat(); + pMatrix.d3 = ReadFloat(); + pMatrix.a4 = ReadFloat(); + pMatrix.b4 = ReadFloat(); + pMatrix.c4 = ReadFloat(); + pMatrix.d4 = ReadFloat(); + + // trailing symbols + CheckForSemicolon(); + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectMesh(Mesh *pMesh) { + std::string name; + readHeadOfDataObject(&name); + + // read vertex count + unsigned int numVertices = ReadInt(); + pMesh->mPositions.resize(numVertices); + + // read vertices + for (unsigned int a = 0; a < numVertices; a++) + pMesh->mPositions[a] = ReadVector3(); + + // read position faces + unsigned int numPosFaces = ReadInt(); + pMesh->mPosFaces.resize(numPosFaces); + for (unsigned int a = 0; a < numPosFaces; ++a) { + // read indices + unsigned int numIndices = ReadInt(); + Face &face = pMesh->mPosFaces[a]; + for (unsigned int b = 0; b < numIndices; ++b) { + const int idx(ReadInt()); + if (static_cast<unsigned int>(idx) <= numVertices) { + face.mIndices.push_back(idx); + } + } + TestForSeparator(); + } + + // here, other data objects may follow + bool running = true; + while (running) { + std::string objectName = GetNextToken(); + + if (objectName.empty()) + ThrowException("Unexpected end of file while parsing mesh structure"); + else if (objectName == "}") + break; // mesh finished + else if (objectName == "MeshNormals") + ParseDataObjectMeshNormals(pMesh); + else if (objectName == "MeshTextureCoords") + ParseDataObjectMeshTextureCoords(pMesh); + else if (objectName == "MeshVertexColors") + ParseDataObjectMeshVertexColors(pMesh); + else if (objectName == "MeshMaterialList") + ParseDataObjectMeshMaterialList(pMesh); + else if (objectName == "VertexDuplicationIndices") + ParseUnknownDataObject(); // we'll ignore vertex duplication indices + else if (objectName == "XSkinMeshHeader") + ParseDataObjectSkinMeshHeader(pMesh); + else if (objectName == "SkinWeights") + ParseDataObjectSkinWeights(pMesh); + else { + ASSIMP_LOG_WARN("Unknown data object in mesh in x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectSkinWeights(Mesh *pMesh) { + if (nullptr == pMesh) { + return; + } + readHeadOfDataObject(); + + std::string transformNodeName; + GetNextTokenAsString(transformNodeName); + + pMesh->mBones.push_back(Bone()); + Bone &bone = pMesh->mBones.back(); + bone.mName = transformNodeName; + + // read vertex weights + unsigned int numWeights = ReadInt(); + bone.mWeights.reserve(numWeights); + + for (unsigned int a = 0; a < numWeights; a++) { + BoneWeight weight = {}; + weight.mVertex = ReadInt(); + bone.mWeights.push_back(weight); + } + + // read vertex weights + for (unsigned int a = 0; a < numWeights; a++) + bone.mWeights[a].mWeight = ReadFloat(); + + // read matrix offset + bone.mOffsetMatrix.a1 = ReadFloat(); + bone.mOffsetMatrix.b1 = ReadFloat(); + bone.mOffsetMatrix.c1 = ReadFloat(); + bone.mOffsetMatrix.d1 = ReadFloat(); + bone.mOffsetMatrix.a2 = ReadFloat(); + bone.mOffsetMatrix.b2 = ReadFloat(); + bone.mOffsetMatrix.c2 = ReadFloat(); + bone.mOffsetMatrix.d2 = ReadFloat(); + bone.mOffsetMatrix.a3 = ReadFloat(); + bone.mOffsetMatrix.b3 = ReadFloat(); + bone.mOffsetMatrix.c3 = ReadFloat(); + bone.mOffsetMatrix.d3 = ReadFloat(); + bone.mOffsetMatrix.a4 = ReadFloat(); + bone.mOffsetMatrix.b4 = ReadFloat(); + bone.mOffsetMatrix.c4 = ReadFloat(); + bone.mOffsetMatrix.d4 = ReadFloat(); + + CheckForSemicolon(); + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectSkinMeshHeader(Mesh * /*pMesh*/) { + readHeadOfDataObject(); + + /*unsigned int maxSkinWeightsPerVertex =*/ReadInt(); + /*unsigned int maxSkinWeightsPerFace =*/ReadInt(); + /*unsigned int numBonesInMesh = */ ReadInt(); + + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectMeshNormals(Mesh *pMesh) { + readHeadOfDataObject(); + + // read count + unsigned int numNormals = ReadInt(); + pMesh->mNormals.resize(numNormals); + + // read normal vectors + for (unsigned int a = 0; a < numNormals; ++a) { + pMesh->mNormals[a] = ReadVector3(); + } + + // read normal indices + unsigned int numFaces = ReadInt(); + if (numFaces != pMesh->mPosFaces.size()) { + ThrowException("Normal face count does not match vertex face count."); + } + + // do not crah when no face definitions are there + if (numFaces > 0) { + // normal face creation + pMesh->mNormFaces.resize(numFaces); + for (unsigned int a = 0; a < numFaces; ++a) { + unsigned int numIndices = ReadInt(); + pMesh->mNormFaces[a] = Face(); + Face &face = pMesh->mNormFaces[a]; + for (unsigned int b = 0; b < numIndices; ++b) { + face.mIndices.push_back(ReadInt()); + } + + TestForSeparator(); + } + } + + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectMeshTextureCoords(Mesh *pMesh) { + readHeadOfDataObject(); + if (pMesh->mNumTextures + 1 > AI_MAX_NUMBER_OF_TEXTURECOORDS) + ThrowException("Too many sets of texture coordinates"); + + std::vector<aiVector2D> &coords = pMesh->mTexCoords[pMesh->mNumTextures++]; + + unsigned int numCoords = ReadInt(); + if (numCoords != pMesh->mPositions.size()) + ThrowException("Texture coord count does not match vertex count"); + + coords.resize(numCoords); + for (unsigned int a = 0; a < numCoords; a++) + coords[a] = ReadVector2(); + + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectMeshVertexColors(Mesh *pMesh) { + readHeadOfDataObject(); + if (pMesh->mNumColorSets + 1 > AI_MAX_NUMBER_OF_COLOR_SETS) + ThrowException("Too many colorsets"); + std::vector<aiColor4D> &colors = pMesh->mColors[pMesh->mNumColorSets++]; + + unsigned int numColors = ReadInt(); + if (numColors != pMesh->mPositions.size()) + ThrowException("Vertex color count does not match vertex count"); + + colors.resize(numColors, aiColor4D(0, 0, 0, 1)); + for (unsigned int a = 0; a < numColors; a++) { + unsigned int index = ReadInt(); + if (index >= pMesh->mPositions.size()) + ThrowException("Vertex color index out of bounds"); + + colors[index] = ReadRGBA(); + // HACK: (thom) Maxon Cinema XPort plugin puts a third separator here, kwxPort puts a comma. + // Ignore gracefully. + if (!mIsBinaryFormat) { + FindNextNoneWhiteSpace(); + if (*mP == ';' || *mP == ',') + mP++; + } + } + + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectMeshMaterialList(Mesh *pMesh) { + readHeadOfDataObject(); + + // read material count + /*unsigned int numMaterials =*/ReadInt(); + // read non triangulated face material index count + unsigned int numMatIndices = ReadInt(); + + // some models have a material index count of 1... to be able to read them we + // replicate this single material index on every face + if (numMatIndices != pMesh->mPosFaces.size() && numMatIndices != 1) + ThrowException("Per-Face material index count does not match face count."); + + // read per-face material indices + for (unsigned int a = 0; a < numMatIndices; a++) + pMesh->mFaceMaterials.push_back(ReadInt()); + + // 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 (!mIsBinaryFormat) // && MajorVersion == 3 && MinorVersion <= 2) + { + if (mP < mEnd && *mP == ';') + ++mP; + } + + // if there was only a single material index, replicate it on all faces + while (pMesh->mFaceMaterials.size() < pMesh->mPosFaces.size()) + pMesh->mFaceMaterials.push_back(pMesh->mFaceMaterials.front()); + + // read following data objects + bool running = true; + while (running) { + std::string objectName = GetNextToken(); + if (objectName.size() == 0) + ThrowException("Unexpected end of file while parsing mesh material list."); + else if (objectName == "}") + break; // material list finished + else if (objectName == "{") { + // template materials + std::string matName = GetNextToken(); + Material material; + material.mIsReference = true; + material.mName = matName; + pMesh->mMaterials.push_back(material); + + CheckForClosingBrace(); // skip } + } else if (objectName == "Material") { + pMesh->mMaterials.push_back(Material()); + ParseDataObjectMaterial(&pMesh->mMaterials.back()); + } else if (objectName == ";") { + // ignore + } else { + ASSIMP_LOG_WARN("Unknown data object in material list in x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectMaterial(Material *pMaterial) { + std::string matName; + readHeadOfDataObject(&matName); + if (matName.empty()) + matName = std::string("material") + ai_to_string(mLineNumber); + pMaterial->mName = matName; + pMaterial->mIsReference = false; + + // read material values + pMaterial->mDiffuse = ReadRGBA(); + pMaterial->mSpecularExponent = ReadFloat(); + pMaterial->mSpecular = ReadRGB(); + pMaterial->mEmissive = ReadRGB(); + + // read other data objects + bool running = true; + while (running) { + std::string objectName = GetNextToken(); + if (objectName.size() == 0) + ThrowException("Unexpected end of file while parsing mesh material"); + else if (objectName == "}") + break; // material finished + else if (objectName == "TextureFilename" || objectName == "TextureFileName") { + // some exporters write "TextureFileName" instead. + std::string texname; + ParseDataObjectTextureFilename(texname); + pMaterial->mTextures.push_back(TexEntry(texname)); + } else if (objectName == "NormalmapFilename" || objectName == "NormalmapFileName") { + // one exporter writes out the normal map in a separate filename tag + std::string texname; + ParseDataObjectTextureFilename(texname); + pMaterial->mTextures.push_back(TexEntry(texname, true)); + } else { + ASSIMP_LOG_WARN("Unknown data object in material in x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectAnimTicksPerSecond() { + readHeadOfDataObject(); + mScene->mAnimTicksPerSecond = ReadInt(); + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectAnimationSet() { + std::string animName; + readHeadOfDataObject(&animName); + + Animation *anim = new Animation; + mScene->mAnims.push_back(anim); + anim->mName = animName; + + bool running = true; + while (running) { + std::string objectName = GetNextToken(); + if (objectName.length() == 0) + ThrowException("Unexpected end of file while parsing animation set."); + else if (objectName == "}") + break; // animation set finished + else if (objectName == "Animation") + ParseDataObjectAnimation(anim); + else { + ASSIMP_LOG_WARN("Unknown data object in animation set in x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectAnimation(Animation *pAnim) { + readHeadOfDataObject(); + AnimBone *banim = new AnimBone; + pAnim->mAnims.push_back(banim); + + bool running = true; + while (running) { + std::string objectName = GetNextToken(); + + if (objectName.length() == 0) + ThrowException("Unexpected end of file while parsing animation."); + else if (objectName == "}") + break; // animation finished + else if (objectName == "AnimationKey") + ParseDataObjectAnimationKey(banim); + else if (objectName == "AnimationOptions") + ParseUnknownDataObject(); // not interested + else if (objectName == "{") { + // read frame name + banim->mBoneName = GetNextToken(); + CheckForClosingBrace(); + } else { + ASSIMP_LOG_WARN("Unknown data object in animation in x file"); + ParseUnknownDataObject(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectAnimationKey(AnimBone *pAnimBone) { + readHeadOfDataObject(); + + // read key type + unsigned int keyType = ReadInt(); + + // read number of keys + unsigned int numKeys = ReadInt(); + + for (unsigned int a = 0; a < numKeys; a++) { + // read time + unsigned int time = ReadInt(); + + // read keys + switch (keyType) { + case 0: // rotation quaternion + { + // read count + if (ReadInt() != 4) + ThrowException("Invalid number of arguments for quaternion key in animation"); + + aiQuatKey key; + key.mTime = double(time); + key.mValue.w = ReadFloat(); + key.mValue.x = ReadFloat(); + key.mValue.y = ReadFloat(); + key.mValue.z = ReadFloat(); + pAnimBone->mRotKeys.push_back(key); + + CheckForSemicolon(); + break; + } + + case 1: // scale vector + case 2: // position vector + { + // read count + if (ReadInt() != 3) + ThrowException("Invalid number of arguments for vector key in animation"); + + aiVectorKey key; + key.mTime = double(time); + key.mValue = ReadVector3(); + + if (keyType == 2) + pAnimBone->mPosKeys.push_back(key); + else + pAnimBone->mScaleKeys.push_back(key); + + break; + } + + case 3: // combined transformation matrix + case 4: // denoted both as 3 or as 4 + { + // read count + if (ReadInt() != 16) + ThrowException("Invalid number of arguments for matrix key in animation"); + + // read matrix + MatrixKey key; + key.mTime = double(time); + key.mMatrix.a1 = ReadFloat(); + key.mMatrix.b1 = ReadFloat(); + key.mMatrix.c1 = ReadFloat(); + key.mMatrix.d1 = ReadFloat(); + key.mMatrix.a2 = ReadFloat(); + key.mMatrix.b2 = ReadFloat(); + key.mMatrix.c2 = ReadFloat(); + key.mMatrix.d2 = ReadFloat(); + key.mMatrix.a3 = ReadFloat(); + key.mMatrix.b3 = ReadFloat(); + key.mMatrix.c3 = ReadFloat(); + key.mMatrix.d3 = ReadFloat(); + key.mMatrix.a4 = ReadFloat(); + key.mMatrix.b4 = ReadFloat(); + key.mMatrix.c4 = ReadFloat(); + key.mMatrix.d4 = ReadFloat(); + pAnimBone->mTrafoKeys.push_back(key); + + CheckForSemicolon(); + break; + } + + default: + ThrowException("Unknown key type ", keyType, " in animation."); + break; + } // end switch + + // key separator + CheckForSeparator(); + } + + CheckForClosingBrace(); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseDataObjectTextureFilename(std::string &pName) { + readHeadOfDataObject(); + GetNextTokenAsString(pName); + CheckForClosingBrace(); + + // FIX: some files (e.g. AnimationTest.x) have "" as texture file name + if (!pName.length()) { + ASSIMP_LOG_WARN("Length of texture file name is zero. Skipping this texture."); + } + + // some exporters write double backslash paths out. We simply replace them if we find them + while (pName.find("\\\\") != std::string::npos) + pName.replace(pName.find("\\\\"), 2, "\\"); +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ParseUnknownDataObject() { + // find opening delimiter + bool running = true; + while (running) { + std::string t = GetNextToken(); + if (t.length() == 0) + ThrowException("Unexpected end of file while parsing unknown segment."); + + if (t == "{") + break; + } + + unsigned int counter = 1; + + // parse until closing delimiter + while (counter > 0) { + std::string t = GetNextToken(); + + if (t.length() == 0) + ThrowException("Unexpected end of file while parsing unknown segment."); + + if (t == "{") + ++counter; + else if (t == "}") + --counter; + } +} + +// ------------------------------------------------------------------------------------------------ +//! checks for closing curly brace +void XFileParser::CheckForClosingBrace() { + if (GetNextToken() != "}") + ThrowException("Closing brace expected."); +} + +// ------------------------------------------------------------------------------------------------ +//! checks for one following semicolon +void XFileParser::CheckForSemicolon() { + if (mIsBinaryFormat) + return; + + if (GetNextToken() != ";") + ThrowException("Semicolon expected."); +} + +// ------------------------------------------------------------------------------------------------ +//! checks for a separator char, either a ',' or a ';' +void XFileParser::CheckForSeparator() { + if (mIsBinaryFormat) + return; + + std::string token = GetNextToken(); + if (token != "," && token != ";") + ThrowException("Separator character (';' or ',') expected."); +} + +// ------------------------------------------------------------------------------------------------ +// tests and possibly consumes a separator char, but does nothing if there was no separator +void XFileParser::TestForSeparator() { + if (mIsBinaryFormat) + return; + + FindNextNoneWhiteSpace(); + if (mP >= mEnd) + return; + + // test and skip + if (*mP == ';' || *mP == ',') + mP++; +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::readHeadOfDataObject(std::string *poName) { + std::string nameOrBrace = GetNextToken(); + if (nameOrBrace != "{") { + if (poName) + *poName = nameOrBrace; + + if (GetNextToken() != "{") { + delete mScene; + ThrowException("Opening brace expected."); + } + } +} + +// ------------------------------------------------------------------------------------------------ +std::string XFileParser::GetNextToken() { + std::string s; + + // process binary-formatted file + if (mIsBinaryFormat) { + // in binary mode it will only return NAME and STRING token + // and (correctly) skip over other tokens. + if (mEnd - mP < 2) { + return s; + } + unsigned int tok = ReadBinWord(); + unsigned int len; + + // standalone tokens + switch (tok) { + case 1: { + // name token + if (mEnd - mP < 4) { + return s; + } + len = ReadBinDWord(); + const int bounds = int(mEnd - mP); + const int iLen = int(len); + if (iLen < 0) { + return s; + } + if (bounds < iLen) { + return s; + } + s = std::string(mP, len); + mP += len; + } + return s; + + case 2: + // string token + if (mEnd - mP < 4) return s; + len = ReadBinDWord(); + if (mEnd - mP < int(len)) return s; + s = std::string(mP, len); + mP += (len + 2); + return s; + case 3: + // integer token + mP += 4; + return "<integer>"; + case 5: + // GUID token + mP += 16; + return "<guid>"; + case 6: + if (mEnd - mP < 4) return s; + len = ReadBinDWord(); + mP += (len * 4); + return "<int_list>"; + case 7: + if (mEnd - mP < 4) return s; + len = ReadBinDWord(); + mP += (len * mBinaryFloatSize); + return "<flt_list>"; + 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 (mP >= mEnd) + return s; + + while ((mP < mEnd) && !isspace((unsigned char)*mP)) { + // either keep token delimiters when already holding a token, or return if first valid char + if (*mP == ';' || *mP == '}' || *mP == '{' || *mP == ',') { + if (!s.size()) + s.append(mP++, 1); + break; // stop for delimiter + } + s.append(mP++, 1); + } + } + return s; +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::FindNextNoneWhiteSpace() { + if (mIsBinaryFormat) + return; + + bool running = true; + while (running) { + while (mP < mEnd && isspace((unsigned char)*mP)) { + if (*mP == '\n') + mLineNumber++; + ++mP; + } + + if (mP >= mEnd) + return; + + // check if this is a comment + if ((mP[0] == '/' && mP[1] == '/') || mP[0] == '#') + ReadUntilEndOfLine(); + else + break; + } +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::GetNextTokenAsString(std::string &poString) { + if (mIsBinaryFormat) { + poString = GetNextToken(); + return; + } + + FindNextNoneWhiteSpace(); + if (mP >= mEnd) { + delete mScene; + ThrowException("Unexpected end of file while parsing string"); + } + + if (*mP != '"') { + delete mScene; + ThrowException("Expected quotation mark."); + } + ++mP; + + while (mP < mEnd && *mP != '"') + poString.append(mP++, 1); + + if (mP >= mEnd - 1) { + delete mScene; + ThrowException("Unexpected end of file while parsing string"); + } + + if (mP[1] != ';' || mP[0] != '"') { + delete mScene; + ThrowException("Expected quotation mark and semicolon at the end of a string."); + } + mP += 2; +} + +// ------------------------------------------------------------------------------------------------ +void XFileParser::ReadUntilEndOfLine() { + if (mIsBinaryFormat) + return; + + while (mP < mEnd) { + if (*mP == '\n' || *mP == '\r') { + ++mP; + mLineNumber++; + return; + } + + ++mP; + } +} + +// ------------------------------------------------------------------------------------------------ +unsigned short XFileParser::ReadBinWord() { + ai_assert(mEnd - mP >= 2); + const unsigned char *q = (const unsigned char *)mP; + unsigned short tmp = q[0] | (q[1] << 8); + mP += 2; + return tmp; +} + +// ------------------------------------------------------------------------------------------------ +unsigned int XFileParser::ReadBinDWord() { + ai_assert(mEnd - mP >= 4); + + const unsigned char *q = (const unsigned char *)mP; + unsigned int tmp = q[0] | (q[1] << 8) | (q[2] << 16) | (q[3] << 24); + mP += 4; + return tmp; +} + +// ------------------------------------------------------------------------------------------------ +unsigned int XFileParser::ReadInt() { + if (mIsBinaryFormat) { + if (mBinaryNumCount == 0 && mEnd - mP >= 2) { + unsigned short tmp = ReadBinWord(); // 0x06 or 0x03 + if (tmp == 0x06 && mEnd - mP >= 4) // array of ints follows + mBinaryNumCount = ReadBinDWord(); + else // single int follows + mBinaryNumCount = 1; + } + + --mBinaryNumCount; + const size_t len(mEnd - mP); + if (len >= 4) { + return ReadBinDWord(); + } else { + mP = mEnd; + return 0; + } + } else { + FindNextNoneWhiteSpace(); + + // TODO: consider using strtol10 instead??? + + // check preceding minus sign + bool isNegative = false; + if (*mP == '-') { + isNegative = true; + mP++; + } + + // at least one digit expected + if (!isdigit((unsigned char)*mP)) + ThrowException("Number expected."); + + // read digits + unsigned int number = 0; + while (mP < mEnd) { + if (!isdigit((unsigned char)*mP)) + break; + number = number * 10 + (*mP - 48); + mP++; + } + + CheckForSeparator(); + + return isNegative ? ((unsigned int)-int(number)) : number; + } +} + +// ------------------------------------------------------------------------------------------------ +ai_real XFileParser::ReadFloat() { + if (mIsBinaryFormat) { + if (mBinaryNumCount == 0 && mEnd - mP >= 2) { + unsigned short tmp = ReadBinWord(); // 0x07 or 0x42 + if (tmp == 0x07 && mEnd - mP >= 4) // array of floats following + mBinaryNumCount = ReadBinDWord(); + else // single float following + mBinaryNumCount = 1; + } + + --mBinaryNumCount; + if (mBinaryFloatSize == 8) { + if (mEnd - mP >= 8) { + double res; + ::memcpy(&res, mP, 8); + mP += 8; + const ai_real result(static_cast<ai_real>(res)); + return result; + } else { + mP = mEnd; + return 0; + } + } else { + if (mEnd - mP >= 4) { + ai_real result; + ::memcpy(&result, mP, 4); + mP += 4; + return result; + } else { + mP = mEnd; + return 0; + } + } + } + + // text version + FindNextNoneWhiteSpace(); + // check for various special strings to allow reading files from faulty exporters + // I mean you, Blender! + // Reading is safe because of the terminating zero + if (strncmp(mP, "-1.#IND00", 9) == 0 || strncmp(mP, "1.#IND00", 8) == 0) { + mP += 9; + CheckForSeparator(); + return 0.0; + } else if (strncmp(mP, "1.#QNAN0", 8) == 0) { + mP += 8; + CheckForSeparator(); + return 0.0; + } + + ai_real result = 0.0; + mP = fast_atoreal_move<ai_real>(mP, result); + + CheckForSeparator(); + + return result; +} + +// ------------------------------------------------------------------------------------------------ +aiVector2D XFileParser::ReadVector2() { + aiVector2D vector; + vector.x = ReadFloat(); + vector.y = ReadFloat(); + TestForSeparator(); + + return vector; +} + +// ------------------------------------------------------------------------------------------------ +aiVector3D XFileParser::ReadVector3() { + aiVector3D vector; + vector.x = ReadFloat(); + vector.y = ReadFloat(); + vector.z = ReadFloat(); + TestForSeparator(); + + return vector; +} + +// ------------------------------------------------------------------------------------------------ +aiColor4D XFileParser::ReadRGBA() { + aiColor4D color; + color.r = ReadFloat(); + color.g = ReadFloat(); + color.b = ReadFloat(); + color.a = ReadFloat(); + TestForSeparator(); + + return color; +} + +// ------------------------------------------------------------------------------------------------ +aiColor3D XFileParser::ReadRGB() { + aiColor3D color; + color.r = ReadFloat(); + color.g = ReadFloat(); + color.b = ReadFloat(); + TestForSeparator(); + + return color; +} + +// ------------------------------------------------------------------------------------------------ +// Filters the imported hierarchy for some degenerated cases that some exporters produce. +void XFileParser::FilterHierarchy(XFile::Node *pNode) { + // if the node has just a single unnamed child containing a mesh, remove + // the anonymous node between. The 3DSMax kwXport plugin seems to produce this + // mess in some cases + if (pNode->mChildren.size() == 1 && pNode->mMeshes.empty()) { + XFile::Node *child = pNode->mChildren.front(); + if (child->mName.length() == 0 && child->mMeshes.size() > 0) { + // transfer its meshes to us + for (unsigned int a = 0; a < child->mMeshes.size(); a++) + pNode->mMeshes.push_back(child->mMeshes[a]); + child->mMeshes.clear(); + + // transfer the transform as well + pNode->mTrafoMatrix = pNode->mTrafoMatrix * child->mTrafoMatrix; + + // then kill it + delete child; + pNode->mChildren.clear(); + } + } + + // recurse + for (unsigned int a = 0; a < pNode->mChildren.size(); a++) + FilterHierarchy(pNode->mChildren[a]); +} + +#endif // !! ASSIMP_BUILD_NO_X_IMPORTER diff --git a/libs/assimp/code/AssetLib/X/XFileParser.h b/libs/assimp/code/AssetLib/X/XFileParser.h new file mode 100644 index 0000000..36eac2a --- /dev/null +++ b/libs/assimp/code/AssetLib/X/XFileParser.h @@ -0,0 +1,158 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file Helper class to parse a XFile into a temporary structure */ +#ifndef AI_XFILEPARSER_H_INC +#define AI_XFILEPARSER_H_INC + +#include <string> +#include <vector> + +#include <assimp/types.h> + +namespace Assimp { + namespace XFile { + struct Node; + struct Mesh; + struct Scene; + struct Material; + struct Animation; + struct AnimBone; + } + +/** + * @brief The XFileParser reads a XFile either in text or binary form and builds a temporary + * data structure out of it. + */ +class XFileParser { +public: + /// Constructor. Creates a data structure out of the XFile given in the memory block. + /// @param pBuffer Null-terminated memory buffer containing the XFile + explicit XFileParser( const std::vector<char>& pBuffer); + + /// Destructor. Destroys all imported data along with it + ~XFileParser(); + + /// Returns the temporary representation of the imported data. + XFile::Scene* GetImportedData() const { return mScene; } + +protected: + void ParseFile(); + void ParseDataObjectTemplate(); + void ParseDataObjectFrame( XFile::Node *pParent); + void ParseDataObjectTransformationMatrix( aiMatrix4x4& pMatrix); + void ParseDataObjectMesh( XFile::Mesh* pMesh); + void ParseDataObjectSkinWeights( XFile::Mesh* pMesh); + void ParseDataObjectSkinMeshHeader( XFile::Mesh* pMesh); + void ParseDataObjectMeshNormals( XFile::Mesh* pMesh); + void ParseDataObjectMeshTextureCoords( XFile::Mesh* pMesh); + void ParseDataObjectMeshVertexColors( XFile::Mesh* pMesh); + void ParseDataObjectMeshMaterialList( XFile::Mesh* pMesh); + void ParseDataObjectMaterial( XFile::Material* pMaterial); + void ParseDataObjectAnimTicksPerSecond(); + void ParseDataObjectAnimationSet(); + void ParseDataObjectAnimation( XFile::Animation* pAnim); + void ParseDataObjectAnimationKey( XFile::AnimBone *pAnimBone); + void ParseDataObjectTextureFilename( std::string& pName); + void ParseUnknownDataObject(); + + //! places pointer to next begin of a token, and ignores comments + void FindNextNoneWhiteSpace(); + + //! returns next valid token. Returns empty string if no token there + std::string GetNextToken(); + + //! reads header of data object including the opening brace. + //! returns false if error happened, and writes name of object + //! if there is one + void readHeadOfDataObject(std::string *poName = nullptr); + + //! checks for closing curly brace, throws exception if not there + void CheckForClosingBrace(); + + //! checks for one following semicolon, throws exception if not there + void CheckForSemicolon(); + + //! checks for a separator char, either a ',' or a ';' + void CheckForSeparator(); + + /// tests and possibly consumes a separator char, but does nothing if there was no separator + void TestForSeparator(); + + //! reads a x file style string + void GetNextTokenAsString( std::string& poString); + + void ReadUntilEndOfLine(); + + unsigned short ReadBinWord(); + unsigned int ReadBinDWord(); + unsigned int ReadInt(); + ai_real ReadFloat(); + aiVector2D ReadVector2(); + aiVector3D ReadVector3(); + aiColor3D ReadRGB(); + aiColor4D ReadRGBA(); + + /** Throws an exception with a line number and the given text. */ + template<typename... T> + AI_WONT_RETURN void ThrowException(T&&... args) AI_WONT_RETURN_SUFFIX; + + /** + * @brief Filters the imported hierarchy for some degenerated cases that some exporters produce. + * @param pData The sub-hierarchy to filter + */ + void FilterHierarchy( XFile::Node* pNode); + +protected: + unsigned int mMajorVersion, mMinorVersion; ///< version numbers + bool mIsBinaryFormat; ///< true if the file is in binary, false if it's in text form + unsigned int mBinaryFloatSize; ///< float size in bytes, either 4 or 8 + unsigned int mBinaryNumCount; /// < counter for number arrays in binary format + const char* mP; + const char* mEnd; + unsigned int mLineNumber; ///< Line number when reading in text format + XFile::Scene* mScene; ///< Imported data +}; + +} //! ns Assimp + +#endif // AI_XFILEPARSER_H_INC |