diff --git a/Engine/lib/CMakeLists.txt b/Engine/lib/CMakeLists.txt index d48ff6642..13c0cc25e 100644 --- a/Engine/lib/CMakeLists.txt +++ b/Engine/lib/CMakeLists.txt @@ -143,8 +143,15 @@ IF(MSVC) advanced_option( ASSIMP_INSTALL_PDB "Install MSVC debug files." ON ) endif() advanced_option(ASSIMP_RAPIDJSON_NO_MEMBER_ITERATOR "Suppress rapidjson warning on MSVC (NOTE: breaks android build)" ON ) -advanced_option(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_IMPORTER values" ON) -advanced_option(ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_EXPORTER values" ON) +advanced_option(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_IMPORTER values" OFF) +advanced_option(ASSIMP_BUILD_FBX_IMPORTER "FBX Importer" ON) +advanced_option(ASSIMP_BUILD_GLTF_IMPORTER "GLTF Importer" ON) +advanced_option(ASSIMP_BUILD_OBJ_IMPORTER "OBJ Importer" ON) +advanced_option(ASSIMP_BUILD_BLEND_IMPORTER "BLENDER Importer" ON) +advanced_option(ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_EXPORTER values" OFF) +advanced_option(ASSIMP_BUILD_FBX_EXPORTER "FBX Importer" ON) +advanced_option(ASSIMP_BUILD_GLTF_EXPORTER "GLTF Importer" ON) +advanced_option(ASSIMP_BUILD_OBJ_EXPORTER "OBJ Importer" ON) mark_as_advanced(ASSIMP_ARCHIVE_OUTPUT_DIRECTORY) mark_as_advanced(ASSIMP_BIN_INSTALL_DIR) mark_as_advanced(ASSIMP_LIB_INSTALL_DIR) diff --git a/Engine/source/T3D/assets/assetImporter.cpp b/Engine/source/T3D/assets/assetImporter.cpp index 6c22ce7d2..3851c945b 100644 --- a/Engine/source/T3D/assets/assetImporter.cpp +++ b/Engine/source/T3D/assets/assetImporter.cpp @@ -2012,7 +2012,7 @@ void AssetImporter::processShapeAsset(AssetImportObject* assetItem) { enumColladaForImport(filePath, shapeInfo, false); } - else if ((fileExt.compare("dts") == 0) || (fileExt.compare("dsq") == 0)) + else if ((fileExt.compare("dts") == 0)) { enumDTSForImport(filePath, shapeInfo); } diff --git a/Engine/source/gui/editor/guiShapeEdPreview.cpp b/Engine/source/gui/editor/guiShapeEdPreview.cpp index e47880233..d5c0e2b6c 100644 --- a/Engine/source/gui/editor/guiShapeEdPreview.cpp +++ b/Engine/source/gui/editor/guiShapeEdPreview.cpp @@ -405,6 +405,53 @@ bool GuiShapeEdPreview::setObjectModel(const char* modelName) return true; } +bool GuiShapeEdPreview::findCompanionShape(const Torque::Path& dsqPath, Torque::Path& outShapePath) +{ + // AssimpLoader and ColladaLoader exports as "modelname_sequencename.dsq" alongside "modelname.cached.dts" + // so strip everything from the last underscore to find the base name + String fileName = dsqPath.getFileName(); + String::SizeType sep = fileName.find('_'); + + if (sep != String::NPos) + { + Torque::Path candidate(dsqPath); + candidate.setFileName(fileName.substr(0, sep)); + + candidate.setExtension("cached.dts"); + if (Torque::FS::IsFile(candidate.getFullPath())) + { + outShapePath = candidate; + return true; + } + + candidate.setExtension("dts"); + if (Torque::FS::IsFile(candidate.getFullPath())) + { + outShapePath = candidate; + return true; + } + } + + // fallback: same filename, just swap extension + Torque::Path direct(dsqPath); + + direct.setExtension("cached.dts"); + if (Torque::FS::IsFile(direct.getFullPath())) + { + outShapePath = direct; + return true; + } + + direct.setExtension("dts"); + if (Torque::FS::IsFile(direct.getFullPath())) + { + outShapePath = direct; + return true; + } + + return false; +} + bool GuiShapeEdPreview::setObjectShapeAsset(const char* assetId) { SAFE_DELETE(mModel); @@ -422,16 +469,68 @@ bool GuiShapeEdPreview::setObjectShapeAsset(const char* assetId) ShapeAsset* asset = AssetDatabase.acquireAsset(id); modelName = asset->getShapeFile(); AssetDatabase.releaseAsset(id); + return setObjectModel(modelName); } else if (assetType == StringTable->insert("ShapeAnimationAsset")) { ShapeAnimationAsset* asset = AssetDatabase.acquireAsset(id); - modelName = asset->getAnimationPath(); + StringTableEntry animPath = asset->getAnimationPath(); AssetDatabase.releaseAsset(id); + + Torque::Path dsqPath(animPath); + Torque::Path shapePath; + if (!findCompanionShape(dsqPath, shapePath)) + { + Con::warnf("GuiShapeEdPreview::setObjectShapeAsset - " + "No companion shape found for '%s'", animPath); + return false; + } + + if (!setObjectModel(shapePath.getFullPath())) + { + Con::warnf("GuiShapeEdPreview::setObjectShapeAsset - " + "Could not load companion shape for '%s'", animPath); + return false; + } + + FileStream dsqStream; + if (!dsqStream.open(animPath, Torque::FS::File::Read)) + { + Con::warnf("GuiShapeEdPreview::setObjectShapeAsset - " + "Could not open '%s'", animPath); + SAFE_DELETE(mModel); + return false; + } + + TSShape* shape = mModel->getShape(); + + bool ok = shape->importSequences(&dsqStream, String(animPath)); + dsqStream.close(); + + if (!ok) + { + Con::warnf("GuiShapeEdPreview::setObjectShapeAsset - " + "importSequences failed for '%s'", animPath); + SAFE_DELETE(mModel); + return false; + } + + setAllMeshesHidden(true); + mRenderNodes = true; + if (shape->sequences.size() > 0) + { + // importSequences appends, so the new one is always last + const String& seqName = shape->getSequenceName(shape->sequences.size() - 1); + addThread(); + mActiveThread = 0; + setActiveThreadSequence(seqName.c_str(), 0.0f, 0.0f, true); + } + + return true; } } - return setObjectModel(modelName); + return false; } void GuiShapeEdPreview::_onResourceChanged(const Torque::Path& path) diff --git a/Engine/source/gui/editor/guiShapeEdPreview.h b/Engine/source/gui/editor/guiShapeEdPreview.h index 36cad41aa..b9757b8c5 100644 --- a/Engine/source/gui/editor/guiShapeEdPreview.h +++ b/Engine/source/gui/editor/guiShapeEdPreview.h @@ -199,6 +199,7 @@ public: void setCurrentDetail(S32 dl); bool setObjectModel(const char * modelName); + bool findCompanionShape(const Torque::Path& dsqPath, Torque::Path& outShapePath); bool setObjectShapeAsset(const char* assetId); void _onResourceChanged(const Torque::Path& path); diff --git a/Engine/source/ts/assimp/assimpAppMesh.cpp b/Engine/source/ts/assimp/assimpAppMesh.cpp index d78898c32..60cee3a2e 100644 --- a/Engine/source/ts/assimp/assimpAppMesh.cpp +++ b/Engine/source/ts/assimp/assimpAppMesh.cpp @@ -44,6 +44,7 @@ bool AssimpAppMesh::fixedSizeEnabled = false; S32 AssimpAppMesh::fixedSize = 2; +Vector AssimpAppMesh::sMaterialRemap; //------------------------------------------------------------------------------ @@ -164,7 +165,8 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset) primitives.increment(); TSDrawPrimitive& primitive = primitives.last(); primitive.start = 0; - primitive.matIndex = (TSDrawPrimitive::Triangles | TSDrawPrimitive::Indexed) | (S32)mMeshData->mMaterialIndex; + S32 mappedMat = (mMeshData->mMaterialIndex < (U32)AssimpAppMesh::sMaterialRemap.size()) ? AssimpAppMesh::sMaterialRemap[mMeshData->mMaterialIndex] : TSDrawPrimitive::NoMaterial; + primitive.matIndex = (TSDrawPrimitive::Triangles | TSDrawPrimitive::Indexed) | mappedMat; primitive.numElements = indicesCount; for ( U32 n = 0; n < mMeshData->mNumFaces; ++n) diff --git a/Engine/source/ts/assimp/assimpAppMesh.h b/Engine/source/ts/assimp/assimpAppMesh.h index 81f0b251d..48c1cfc34 100644 --- a/Engine/source/ts/assimp/assimpAppMesh.h +++ b/Engine/source/ts/assimp/assimpAppMesh.h @@ -119,6 +119,8 @@ public: /// @return The mesh transform at the specified time MatrixF getMeshTransform(F32 time) override; F32 getVisValue(F32 t) override; + + static Vector sMaterialRemap; }; #endif // _COLLADA_APPMESH_H_ diff --git a/Engine/source/ts/assimp/assimpShapeLoader.cpp b/Engine/source/ts/assimp/assimpShapeLoader.cpp index 4bcc1df91..5a52d4e44 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.cpp +++ b/Engine/source/ts/assimp/assimpShapeLoader.cpp @@ -56,12 +56,13 @@ #undef new #endif #endif -// assimp include files. +// assimp include files. #include #include #include #include #include +#include #include #if !defined(TORQUE_DISABLE_MEMORY_MANAGER) @@ -69,56 +70,68 @@ # define new _new #endif -extern bool gTryUseDSQs; +static bool sReadAssimp(const Torque::Path& path, TSShape*& shape); -MODULE_BEGIN( AssimpShapeLoader ) - MODULE_INIT_AFTER( ShapeLoader ) - MODULE_INIT +static struct _privateRegisterAssimp +{ + _privateRegisterAssimp() { - TSShapeLoader::addFormat("DirectX X", "x"); - TSShapeLoader::addFormat("Autodesk FBX", "fbx"); - TSShapeLoader::addFormat("Blender 3D", "blend" ); - TSShapeLoader::addFormat("3ds Max 3DS", "3ds"); - TSShapeLoader::addFormat("3ds Max ASE", "ase"); - TSShapeLoader::addFormat("Wavefront Object", "obj"); - TSShapeLoader::addFormat("Industry Foundation Classes (IFC/Step)", "ifc"); - TSShapeLoader::addFormat("Stanford Polygon Library", "ply"); - TSShapeLoader::addFormat("AutoCAD DXF", "dxf"); - TSShapeLoader::addFormat("LightWave", "lwo"); - TSShapeLoader::addFormat("LightWave Scene", "lws"); - TSShapeLoader::addFormat("Modo", "lxo"); - TSShapeLoader::addFormat("Stereolithography", "stl"); - TSShapeLoader::addFormat("AC3D", "ac"); - TSShapeLoader::addFormat("Milkshape 3D", "ms3d"); - TSShapeLoader::addFormat("TrueSpace COB", "cob"); - TSShapeLoader::addFormat("TrueSpace SCN", "scn"); - TSShapeLoader::addFormat("Ogre XML", "xml"); - TSShapeLoader::addFormat("Irrlicht Mesh", "irrmesh"); - TSShapeLoader::addFormat("Irrlicht Scene", "irr"); - TSShapeLoader::addFormat("Quake I", "mdl" ); - TSShapeLoader::addFormat("Quake II", "md2" ); - TSShapeLoader::addFormat("Quake III Mesh", "md3"); - TSShapeLoader::addFormat("Quake III Map/BSP", "pk3"); - TSShapeLoader::addFormat("Return to Castle Wolfenstein", "mdc"); - TSShapeLoader::addFormat("Doom 3", "md5" ); - TSShapeLoader::addFormat("Valve SMD", "smd"); - TSShapeLoader::addFormat("Valve VTA", "vta"); - TSShapeLoader::addFormat("Starcraft II M3", "m3"); - TSShapeLoader::addFormat("Unreal", "3d"); - TSShapeLoader::addFormat("BlitzBasic 3D", "b3d" ); - TSShapeLoader::addFormat("Quick3D Q3D", "q3d"); - TSShapeLoader::addFormat("Quick3D Q3S", "q3s"); - TSShapeLoader::addFormat("Neutral File Format", "nff"); - TSShapeLoader::addFormat("Object File Format", "off"); - TSShapeLoader::addFormat("PovRAY Raw", "raw"); - TSShapeLoader::addFormat("Terragen Terrain", "ter"); - TSShapeLoader::addFormat("3D GameStudio (3DGS)", "mdl"); - TSShapeLoader::addFormat("3D GameStudio (3DGS) Terrain", "hmp"); - TSShapeLoader::addFormat("Izware Nendo", "ndo"); - TSShapeLoader::addFormat("gltf", "gltf"); - TSShapeLoader::addFormat("gltf binary", "glb"); + TSShape::ShapeRegistration reg; + + Assimp::Importer importer; + for (U32 i = 0; i < importer.GetImporterCount(); i++) + { + const aiImporterDesc* desc = importer.GetImporterInfo(i); + + String extensions(desc->mFileExtensions); + + Vector tokens; + extensions.split(" ", tokens); + for (U32 t = 0; t < tokens.size(); ++t) + { + const String& ext = tokens[t]; + if (ext.isEmpty() || + ext.equal("dae", String::NoCase) || // filter out collada importer formats (for now). + ext.equal("zae", String::NoCase) || + ext.equal("xml", String::NoCase) + ) + continue; + + reg.extensions.push_back({ + String(desc->mName), // convert from const char* + ext + }); + } + + } + + Assimp::Exporter exporter; + + for (U32 i = 0; i < exporter.GetExportFormatCount(); ++i) + { + const aiExportFormatDesc* desc = exporter.GetExportFormatDescription(i); + String ext(desc->fileExtension); + + if (ext.isEmpty() || + ext.equal("dae", String::NoCase) || // filter out collada importer formats (for now). + ext.equal("zae", String::NoCase) || + ext.equal("xml", String::NoCase) + ) + continue; + + reg.export_extensions.push_back({ + String(desc->description), + ext + }); + } + + reg.readFunc = sReadAssimp; + reg.writeFunc = NULL; + + TSShape::sRegisterFormat(reg); } -MODULE_END; +} sStaticRegisterAssimp; + //----------------------------------------------------------------------------- @@ -272,7 +285,16 @@ void AssimpShapeLoader::enumerateScene() // Load all materials AssimpAppMaterial::sDefaultMatNumber = 0; - for (U32 i = 0; i < mScene->mNumMaterials; ++i) { + AssimpAppMesh::sMaterialRemap.setSize(mScene->mNumMaterials); + for (U32 i = 0; i < mScene->mNumMaterials; ++i) + { + if (FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().neverImportMat, + mScene->mMaterials[i]->GetName().C_Str(), false)) + { + AssimpAppMesh::sMaterialRemap[i] = TSDrawPrimitive::NoMaterial; // TSDrawPrimitive::NoMaterial + continue; + } + AssimpAppMesh::sMaterialRemap[i] = AppMesh::appMaterials.size(); AppMesh::appMaterials.push_back(new AssimpAppMaterial(mScene->mMaterials[i])); } @@ -391,54 +413,34 @@ void AssimpShapeLoader::getRootAxisTransform() MatrixF rot(true); - // ===== Y-UP SOURCE ===== - if (upAxis == 1) + // Build source basis + auto axisToVector = [](int axis, int sign) -> Point3F { - if (frontAxis == 2) - { - // Y-up, Z-forward → Z-up, Y-forward - // Rotate 180° Y, then 90° X - rot(0, 0) = -1.0f; - rot(1, 1) = 0.0f; rot(2, 1) = 1.0f; - rot(1, 2) = 1.0f; rot(2, 2) = 0.0f; - } - else if (frontAxis == 0) - { - // Y-up, X-forward → Z-up, Y-forward - // Rotate -90° around Z then 90° around X - rot(0, 0) = 0.0f; rot(0, 1) = -1.0f; - rot(1, 0) = 1.0f; rot(1, 1) = 0.0f; - rot(2, 2) = 1.0f; - } - } + Point3F v(0, 0, 0); + v[axis] = (F32)sign; + return v; + }; - // ===== Z-UP SOURCE ===== - if (upAxis == 2) - { - if (frontAxis == 1) - { - // Already Z-up, Y-forward → no change - } - else if (frontAxis == 0) - { - // Z-up, X-forward → rotate -90° around Z - rot(0, 0) = 0.0f; rot(0, 1) = -1.0f; - rot(1, 0) = 1.0f; rot(1, 1) = 0.0f; - } - } + Point3F forward = axisToVector(frontAxis, -frontSign); + Point3F up = axisToVector(upAxis, upSign); + Point3F right = mCross(forward, up); - // ===== X-UP SOURCE ===== - if (upAxis == 0) - { - if (frontAxis == 2) - { - // X-up, Z-forward → Z-up, Y-forward - // Rotate -90° around Y then -90° around Z - rot(0, 0) = 0.0f; rot(0, 1) = 0.0f; rot(0, 2) = -1.0f; - rot(1, 0) = 1.0f; rot(1, 1) = 0.0f; rot(1, 2) = 0.0f; - rot(2, 0) = 0.0f; rot(2, 1) = -1.0f; rot(2, 2) = 0.0f; - } - } + // Recompute forward + forward = mCross(up, right); + + // Normalize (defensive, though they should already be unit) + right.normalize(); + forward.normalize(); + up.normalize(); + + MatrixF srcBasis(true); + srcBasis.setColumn(0, right); + srcBasis.setColumn(1, forward); + srcBasis.setColumn(2, up); + + // Convert to Torque space + rot = srcBasis; + rot.inverse(); ColladaUtils::getOptions().axisCorrectionMat = rot; } @@ -689,30 +691,6 @@ bool AssimpShapeLoader::canLoadCachedDTS(const Torque::Path& path) return false; } -/// Check if an up-to-date cached DSQ is available for this file -bool AssimpShapeLoader::canLoadCachedDSQ(const Torque::Path& path) -{ - // Generate the cached filename - Torque::Path cachedPath(path); - cachedPath.setExtension("dsq"); - - // Check if a cached DTS newer than this file is available - FileTime cachedModifyTime; - if (Platform::getFileTimes(cachedPath.getFullPath(), NULL, &cachedModifyTime)) - { - bool forceLoad = Con::getBoolVariable("$assimp::forceLoad", false); - - FileTime daeModifyTime; - if (!Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime) || - (!forceLoad && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0))) - { - // Original file not found, or cached DTS is newer - return true; - } - } - return false; -} - void AssimpShapeLoader::assimpLogCallback(const char* message, char* user) { Con::printf("[Assimp log message] %s", StringUnit::getUnit(message, 0, "\n")); @@ -992,65 +970,12 @@ bool AssimpShapeLoader::getMetaString(const char* key, String& stringVal) } //----------------------------------------------------------------------------- /// This function is invoked by the resource manager based on file extension. -TSShape* assimpLoadShape(const Torque::Path &path) +static bool sReadAssimp(const Torque::Path &path, TSShape*& res_shape) { - // TODO: add .cached.dts generation. - // Generate the cached filename - Torque::Path cachedPath(path); - bool canLoadCached = false; - bool canLoadDSQ = false; - - // Check if an up-to-date cached DTS version of this file exists, and - // if so, use that instead. - if (AssimpShapeLoader::canLoadCachedDTS(path)) - { - cachedPath.setExtension("cached.dts"); - canLoadCached = true; - } - else if (gTryUseDSQs && AssimpShapeLoader::canLoadCachedDSQ(path)) - { - cachedPath.setExtension("dsq"); - canLoadDSQ = true; - } - if (canLoadCached || canLoadDSQ) - { - FileStream cachedStream; - cachedStream.open(cachedPath.getFullPath(), Torque::FS::File::Read); - if (cachedStream.getStatus() == Stream::Ok) - { - TSShape *shape = new TSShape; - bool readSuccess = false; - if (canLoadCached) - { - readSuccess = shape->read(&cachedStream); - } - else - { - readSuccess = shape->importSequences(&cachedStream, cachedPath); - } - cachedStream.close(); - - if (readSuccess) - { - #ifdef TORQUE_DEBUG - Con::printf("Loaded cached shape from %s", cachedPath.getFullPath().c_str()); - #endif - return shape; - } - else - { - #ifdef TORQUE_DEBUG - Con::errorf("assimpLoadShape: Load sequence file '%s' failed", cachedPath.getFullPath().c_str()); - #endif - delete shape; - } - } - } - if (!Torque::FS::IsFile(path)) { // File does not exist, bail. - return NULL; + return false; } // Allow TSShapeConstructor object to override properties @@ -1068,40 +993,40 @@ TSShape* assimpLoadShape(const Torque::Path &path) TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import complete"); Con::printf("[ASSIMP] Shape created successfully."); - bool realMesh = false; - for (U32 i = 0; i < tss->meshes.size(); ++i) + Torque::Path cachedPath(path); + // Cache the model to a DTS file for faster loading next time. + cachedPath.setExtension("cached.dts"); + // Cache the model to a DTS file for faster loading next time. + FileStream dtsStream; + if (dtsStream.open(cachedPath.getFullPath(), Torque::FS::File::Write)) { - if (tss->meshes[i] && tss->meshes[i]->getMeshType() != TSMesh::NullMeshType) - realMesh = true; + Con::printf("Writing cached shape to %s", cachedPath.getFullPath().c_str()); + tss->write(&dtsStream); } - if (!realMesh && gTryUseDSQs) + if (tss->sequences.size() > 0) { Torque::Path dsqPath(cachedPath); dsqPath.setExtension("dsq"); FileStream animOutStream; - dsqPath.setFileName(cachedPath.getFileName()); - if (animOutStream.open(dsqPath.getFullPath(), Torque::FS::File::Write)) + + for (S32 i = 0; i < tss->sequences.size(); i++) { - Con::printf("Writing DSQ Animation File for '%s'", dsqPath.getFileName().c_str()); - tss->exportSequences(&animOutStream); - } - } - else - { - // Cache the model to a DTS file for faster loading next time. - cachedPath.setExtension("cached.dts"); - // Cache the model to a DTS file for faster loading next time. - FileStream dtsStream; - if (dtsStream.open(cachedPath.getFullPath(), Torque::FS::File::Write)) - { - Con::printf("Writing cached shape to %s", cachedPath.getFullPath().c_str()); - tss->write(&dtsStream); + const String& seqName = tss->getName(tss->sequences[i].nameIndex); + Con::printf("Writing DSQ Animation File for sequence '%s'", seqName.c_str()); + + dsqPath.setFileName(cachedPath.getFileName() + "_" + seqName); + if (animOutStream.open(dsqPath.getFullPath(), Torque::FS::File::Write)) + { + tss->exportSequence(&animOutStream, tss->sequences[i], false); + animOutStream.close(); + } } } } loader.releaseImport(); - return tss; + res_shape = tss; + return true; } DefineEngineFunction(GetShapeInfo, bool, (const char* shapePath, const char* ctrl, bool loadCachedDts), ("", "", true), diff --git a/Engine/source/ts/assimp/assimpShapeLoader.h b/Engine/source/ts/assimp/assimpShapeLoader.h index ac81c5d85..37c4aa308 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.h +++ b/Engine/source/ts/assimp/assimpShapeLoader.h @@ -41,8 +41,6 @@ struct aiMetadata; //----------------------------------------------------------------------------- class AssimpShapeLoader : public TSShapeLoader { - friend TSShape* assimpLoadShape(const Torque::Path &path); - protected: Assimp::Importer mImporter; const aiScene* mScene; @@ -79,7 +77,6 @@ public: bool fillGuiTreeView(const char* shapePath, GuiTreeViewCtrl* tree); static bool canLoadCachedDTS(const Torque::Path& path); - static bool canLoadCachedDSQ(const Torque::Path& path); static void assimpLogCallback(const char* message, char* user); }; diff --git a/Engine/source/ts/collada/colladaShapeLoader.cpp b/Engine/source/ts/collada/colladaShapeLoader.cpp index 66a859934..65ea667aa 100644 --- a/Engine/source/ts/collada/colladaShapeLoader.cpp +++ b/Engine/source/ts/collada/colladaShapeLoader.cpp @@ -50,15 +50,23 @@ #include "core/util/zip/zipVolume.h" #include "gfx/bitmap/gBitmap.h" -extern bool gTryUseDSQs; -MODULE_BEGIN( ColladaShapeLoader ) - MODULE_INIT_AFTER( ShapeLoader ) - MODULE_INIT +static bool sReadCollada(const Torque::Path& path, TSShape*& shape); + +static struct _privateRegisterCollada +{ + _privateRegisterCollada() { - TSShapeLoader::addFormat("Collada", "dae"); - TSShapeLoader::addFormat("Google Earth", "kmz"); + TSShape::ShapeRegistration reg; + reg.extensions.push_back({ "Collada", "dae" }); + reg.export_extensions.push_back({ "Collada", "dae" }); + reg.extensions.push_back({ "Google Earth", "kmz" }); + + reg.readFunc = sReadCollada; + reg.writeFunc = NULL; + + TSShape::sRegisterFormat(reg); } -MODULE_END; +} sStaticRegisterCollada; // static DAE sDAE; // Collada model database (holds the last loaded file) @@ -549,39 +557,6 @@ bool ColladaShapeLoader::canLoadCachedDTS(const Torque::Path& path) return false; } -bool ColladaShapeLoader::canLoadCachedDSQ(const Torque::Path& path) -{ - // Generate the cached filename - Torque::Path cachedPath(path); - cachedPath.setExtension("dsq"); - - // Check if a cached DSQ newer than this file is available - FileTime cachedModifyTime; - if (Platform::getFileTimes(cachedPath.getFullPath(), NULL, &cachedModifyTime)) - { - bool forceLoadDAE = Con::getBoolVariable("$collada::forceLoadDAE", false); - - FileTime daeModifyTime; - if (!Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime) || - (!forceLoadDAE && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0))) - { - // DAE not found, or cached DTS is newer - return true; - } - } - - //assume the dts is good since it was zipped on purpose - Torque::FS::FileSystemRef ref = Torque::FS::GetFileSystem(cachedPath); - if (ref && !String::compare("Zip", ref->getTypeStr().c_str())) - { - bool forceLoadDAE = Con::getBoolVariable("$collada::forceLoadDAE", false); - - if (!forceLoadDAE && Torque::FS::IsFile(cachedPath)) - return true; - } - - return false; -} bool ColladaShapeLoader::checkAndMountSketchup(const Torque::Path& path, String& mountPoint, Torque::Path& daePath) { bool isSketchup = path.getExtension().equal("kmz", String::NoCase); @@ -683,61 +658,8 @@ domCOLLADA* ColladaShapeLoader::readColladaFile(const String& path) //----------------------------------------------------------------------------- /// This function is invoked by the resource manager based on file extension. -TSShape* loadColladaShape(const Torque::Path &path) +static bool sReadCollada(const Torque::Path& path, TSShape*& res_shape) { -#ifndef DAE2DTS_TOOL - // Generate the cached filename - Torque::Path cachedPath(path); - bool canLoadCached = false; - bool canLoadDSQ = false; - // Check if an up-to-date cached DTS version of this file exists, and - // if so, use that instead. - if (ColladaShapeLoader::canLoadCachedDTS(path)) - { - cachedPath.setExtension("cached.dts"); - canLoadCached = true; - } - else if (gTryUseDSQs && ColladaShapeLoader::canLoadCachedDSQ(path)) - { - cachedPath.setExtension("dsq"); - canLoadDSQ = true; - } - if (canLoadCached || canLoadDSQ) - { - FileStream cachedStream; - cachedStream.open(cachedPath.getFullPath(), Torque::FS::File::Read); - if (cachedStream.getStatus() == Stream::Ok) - { - TSShape *shape = new TSShape; - bool readSuccess = false; - if (canLoadCached) - { - readSuccess = shape->read(&cachedStream); - } - else - { - readSuccess = shape->importSequences(&cachedStream, cachedPath); - } - cachedStream.close(); - - if (readSuccess) - { - #ifdef TORQUE_DEBUG - Con::printf("Loaded cached Collada shape from %s", cachedPath.getFullPath().c_str()); - #endif - return shape; - } - else - { - #ifdef TORQUE_DEBUG - Con::errorf("loadColladaShape: Load sequence file '%s' failed", cachedPath.getFullPath().c_str()); - #endif - delete shape; - } - } - } -#endif // DAE2DTS_TOOL - if (!Torque::FS::IsFile(path)) { // DAE file does not exist, bail. @@ -777,58 +699,51 @@ TSShape* loadColladaShape(const Torque::Path &path) tss = loader.generateShape(daePath); if (tss) { -#ifndef DAE2DTS_TOOL + TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import complete"); + Con::printf("[COLLADA] Shape created successfully."); - bool realMesh = false; - for (U32 i = 0; i < tss->meshes.size(); ++i) + Torque::Path cachedPath(path); + // Cache the model to a DTS file for faster loading next time. + cachedPath.setExtension("cached.dts"); + // Cache the model to a DTS file for faster loading next time. + FileStream dtsStream; + if (dtsStream.open(cachedPath.getFullPath(), Torque::FS::File::Write)) { - if (tss->meshes[i] && tss->meshes[i]->getMeshType() != TSMesh::NullMeshType) - realMesh = true; + Con::printf("Writing cached shape to %s", cachedPath.getFullPath().c_str()); + tss->write(&dtsStream); } - if (!realMesh && gTryUseDSQs) + // Add collada materials to materials.tscript + updateMaterialsScript(path, isSketchup); + + if (tss->sequences.size() > 0) { Torque::Path dsqPath(cachedPath); dsqPath.setExtension("dsq"); FileStream animOutStream; - dsqPath.setFileName(cachedPath.getFileName()); - if (animOutStream.open(dsqPath.getFullPath(), Torque::FS::File::Write)) + + for (S32 i = 0; i < tss->sequences.size(); i++) { - Con::printf("Writing DSQ Animation File for '%s'", dsqPath.getFileName().c_str()); - tss->exportSequences(&animOutStream); - animOutStream.close(); + const String& seqName = tss->getName(tss->sequences[i].nameIndex); + Con::printf("Writing DSQ Animation File for sequence '%s'", seqName.c_str()); + + dsqPath.setFileName(cachedPath.getFileName() + "_" + seqName); + if (animOutStream.open(dsqPath.getFullPath(), Torque::FS::File::Write)) + { + tss->exportSequence(&animOutStream, tss->sequences[i], false); + animOutStream.close(); + } } } - else - { - // Cache the Collada model to a DTS file for faster loading next time. - cachedPath.setExtension("cached.dts"); - FileStream dtsStream; - if (dtsStream.open(cachedPath.getFullPath(), Torque::FS::File::Write)) - { - Torque::FS::FileSystemRef ref = Torque::FS::GetFileSystem(daePath); - if (ref && !String::compare("Zip", ref->getTypeStr().c_str())) - Con::errorf("No cached dts file found in archive for %s. Forcing cache to disk.", daePath.getFullFileName().c_str()); - - Con::printf("Writing cached COLLADA shape to %s", cachedPath.getFullPath().c_str()); - tss->write(&dtsStream); - } - } -#endif // DAE2DTS_TOOL - - // Add collada materials to materials.tscript - updateMaterialsScript(path, isSketchup); } } - // Close progress dialog - TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import complete"); - if (isSketchup) { // Unmount the zip if we mounted it Torque::FS::Unmount(mountPoint); } - return tss; + res_shape = tss; + return true; } diff --git a/Engine/source/ts/collada/colladaShapeLoader.h b/Engine/source/ts/collada/colladaShapeLoader.h index 54cb2e70f..780b403db 100644 --- a/Engine/source/ts/collada/colladaShapeLoader.h +++ b/Engine/source/ts/collada/colladaShapeLoader.h @@ -34,8 +34,6 @@ struct AnimChannels; //----------------------------------------------------------------------------- class ColladaShapeLoader : public TSShapeLoader { - friend TSShape* loadColladaShape(const Torque::Path &path); - domCOLLADA* root; Vector animations; ///< Holds all animation channels for deletion after loading @@ -53,7 +51,6 @@ public: void computeBounds(Box3F& bounds) override; static bool canLoadCachedDTS(const Torque::Path& path); - static bool canLoadCachedDSQ(const Torque::Path& path); static bool checkAndMountSketchup(const Torque::Path& path, String& mountPoint, Torque::Path& daePath); static domCOLLADA* getDomCOLLADA(const Torque::Path& path); static domCOLLADA* readColladaFile(const String& path); diff --git a/Engine/source/ts/loader/tsShapeLoader.cpp b/Engine/source/ts/loader/tsShapeLoader.cpp index 7baceec71..10d54bfec 100644 --- a/Engine/source/ts/loader/tsShapeLoader.cpp +++ b/Engine/source/ts/loader/tsShapeLoader.cpp @@ -36,11 +36,9 @@ MODULE_BEGIN( ShapeLoader ) MODULE_INIT { TSShapeLoader::addFormat("Torque DTS", "dts"); - TSShapeLoader::addFormat("Torque DSQ", "dsq"); } MODULE_END; -bool gTryUseDSQs = false; const F32 TSShapeLoader::DefaultTime = -1.0f; const F64 TSShapeLoader::MinFrameRate = 15.0f; const F64 TSShapeLoader::MaxFrameRate = 60.0f; @@ -1304,11 +1302,16 @@ String TSShapeLoader::getFormatExtensions() { // "*.dsq TAB *.dae TAB StringBuilder output; - for(U32 n = 0; n < smFormats.size(); ++n) + for(U32 n = 0; n < TSShape::sRegistrations.size(); ++n) { - output.append("*."); - output.append(smFormats[n].mExtension); - output.append("\t"); + TSShape::ShapeRegistration reg = TSShape::sRegistrations[n]; + for (U32 i = 0; i < reg.extensions.size(); i++) + { + TSShape::ShapeFormat format = reg.extensions[i]; + output.append("*."); + output.append(format.mExtension); + output.append("\t"); + } } return output.end(); } @@ -1317,12 +1320,17 @@ String TSShapeLoader::getFormatFilters() { // "DSQ Files|*.dsq|COLLADA Files|*.dae|" StringBuilder output; - for(U32 n = 0; n < smFormats.size(); ++n) + for (U32 n = 0; n < TSShape::sRegistrations.size(); ++n) { - output.append(smFormats[n].mName); - output.append("|*."); - output.append(smFormats[n].mExtension); - output.append("|"); + TSShape::ShapeRegistration reg = TSShape::sRegistrations[n]; + for (U32 i = 0; i < reg.extensions.size(); i++) + { + TSShape::ShapeFormat format = reg.extensions[i]; + output.append(format.mName); + output.append("|*."); + output.append(format.mExtension); + output.append("|"); + } } return output.end(); } diff --git a/Engine/source/ts/tsShape.cpp b/Engine/source/ts/tsShape.cpp index 19b873a33..72cc8af52 100644 --- a/Engine/source/ts/tsShape.cpp +++ b/Engine/source/ts/tsShape.cpp @@ -38,16 +38,33 @@ #include "core/stream/fileStream.h" #include "core/fileObject.h" -#ifdef TORQUE_COLLADA -extern TSShape* loadColladaShape(const Torque::Path &path); -#endif +Vector TSShape::sRegistrations(__FILE__, __LINE__); -#ifdef TORQUE_ASSIMP -extern TSShape* assimpLoadShape(const Torque::Path &path); -#endif +void TSShape::sRegisterFormat(const ShapeRegistration& reg) +{ + U32 insert = sRegistrations.size(); + sRegistrations.insert(insert, reg); +} + +const TSShape::ShapeRegistration* TSShape::sFindRegInfo(const String& extension, bool exporting) +{ + for (U32 i = 0; i < TSShape::sRegistrations.size(); i++) + { + const TSShape::ShapeRegistration& reg = TSShape::sRegistrations[i]; + const Vector& extensions = exporting ? reg.export_extensions : reg.extensions; + + for (U32 j = 0; j < extensions.size(); j++) + { + if (extensions[j].mExtension.equal(extension, String::NoCase)) + return ® + } + } + + return NULL; +} /// most recent version -- this is the version we write -S32 TSShape::smVersion = 28; +S32 TSShape::smVersion = TORQUE_DTS_VERSION; /// the version currently being read...valid only during a read S32 TSShape::smReadVersion = -1; const U32 TSShape::smMostRecentExporterVersion = DTS_EXPORTER_CURRENT_VERSION; @@ -168,6 +185,34 @@ TSShape::~TSShape() delete[] mShapeData; } +void TSShape::compressionKey(U8* keyOut, U32 keyLen) +{ + // Concatenate app name and zip password at compile time + static const char kSeed[] = TORQUE_APP_NAME DEFAULT_ZIP_PASSWORD; + + // djb2 hash to collapse the seed string into a 32-bit value + U32 state = 5381u; + for (const char* c = kSeed; *c; ++c) + state = ((state << 5) + state) ^ (U8)*c; + + // Expand into a key stream via a linear-congruential generator + // (Numerical Recipes coefficients) + for (U32 i = 0; i < keyLen; ++i) + { + state = state * 1664525u + 1013904223u; + keyOut[i] = (U8)(state >> 16); + } +} + +void TSShape::xorBufferAtOffset(void* data, U32 byteCount, + U32 startOffset, + const U8* key, U32 keyLen) +{ + U8* p = (U8*)data; + for (U32 i = 0; i < byteCount; ++i) + p[i] ^= key[(startOffset + i) % keyLen]; +} + const String& TSShape::getName( S32 nameIndex ) const { AssertFatal(nameIndex>=0 && nameIndex>= 2; + if (smVersion >= 29) + { + const U32 KEY_LEN = 256; + U8 key[KEY_LEN]; + compressionKey(key, KEY_LEN); + + U32 offset = 0; + xorBufferAtOffset(buffer32, size32 * 4, offset, key, KEY_LEN); + offset += size32 * 4; + xorBufferAtOffset(buffer16, size16 * 4, offset, key, KEY_LEN); + offset += size16 * 4; + xorBufferAtOffset(buffer8, size8 * 4, offset, key, KEY_LEN); + } + S32 sizeMemBuffer, start16, start8; sizeMemBuffer = size32 + size16 + size8; start16 = size32; @@ -1971,7 +2030,18 @@ bool TSShape::read(Stream * s) } S32 * tmp = new S32[sizeMemBuffer]; - s->read(sizeof(S32)*sizeMemBuffer,(U8*)tmp); + s->read(sizeof(S32) * sizeMemBuffer, (U8*)tmp); + + if (smReadVersion >= 29) + { + const U32 KEY_LEN = 256; + U8 key[KEY_LEN]; + compressionKey(key, KEY_LEN); + + // The whole tmp block is one contiguous encrypted region + xorBufferAtOffset(tmp, sizeMemBuffer * 4, 0, key, KEY_LEN); + } + memBuffer32 = tmp; memBuffer16 = (S16*)(tmp+startU16); memBuffer8 = (S8*)(tmp+startU8); @@ -2166,11 +2236,41 @@ template<> void *Resource::create(const Torque::Path &path) TSShape * ret = 0; bool readSuccess = false; const String extension = path.getExtension(); + bool canLoadCached = false; - if ( extension.equal( "dts", String::NoCase ) ) + // Generate the cached filename + Torque::Path cachedPath(path); + cachedPath.setExtension("cached.dts"); + + // Check if a cached DTS newer than this file is available + FileTime cachedModifyTime; + if (Platform::getFileTimes(cachedPath.getFullPath(), NULL, &cachedModifyTime)) + { + bool forceLoadDAE = Con::getBoolVariable("$collada::forceLoadDAE", false); + + FileTime daeModifyTime; + if (!Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime) || + (!forceLoadDAE && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0))) + { + // Non DTS not found, or cached DTS is newer + canLoadCached = true; + } + } + + //assume the dts is good since it was zipped on purpose + Torque::FS::FileSystemRef ref = Torque::FS::GetFileSystem(cachedPath); + if (ref && !String::compare("Zip", ref->getTypeStr().c_str())) + { + bool forceLoadDAE = Con::getBoolVariable("$collada::forceLoadDAE", false); + + if (!forceLoadDAE && Torque::FS::IsFile(cachedPath)) + canLoadCached = true; + } + + if (extension.equal("dts", String::NoCase) || canLoadCached) { FileStream stream; - stream.open( path.getFullPath(), Torque::FS::File::Read ); + stream.open(canLoadCached ? cachedPath.getFullPath() : path.getFullPath(), Torque::FS::File::Read); if ( stream.getStatus() != Stream::Ok ) { Con::errorf( "Resource::create - Could not open '%s'", path.getFullPath().c_str() ); @@ -2180,46 +2280,16 @@ template<> void *Resource::create(const Torque::Path &path) ret = new TSShape; readSuccess = ret->read(&stream); } - else if ( extension.equal( "dae", String::NoCase ) || extension.equal( "kmz", String::NoCase ) ) - { -#ifdef TORQUE_COLLADA - // Attempt to load the DAE file - ret = loadColladaShape(path); - readSuccess = (ret != NULL); -#else - // No COLLADA support => attempt to load the cached DTS file instead - Torque::Path cachedPath = path; - cachedPath.setExtension("cached.dts"); - - FileStream stream; - stream.open( cachedPath.getFullPath(), Torque::FS::File::Read ); - if ( stream.getStatus() != Stream::Ok ) - { - Con::errorf( "Resource::create - Could not open '%s'", cachedPath.getFullPath().c_str() ); - return NULL; - } - ret = new TSShape; - readSuccess = ret->read(&stream); -#endif - } else { - //Con::errorf( "Resource::create - '%s' has an unknown file format", path.getFullPath().c_str() ); - //delete ret; - //return NULL; - - // andrewmac: Open Asset Import Library -#ifdef TORQUE_ASSIMP - ret = assimpLoadShape(path); - readSuccess = (ret != NULL); -#endif - - // andrewmac : I could have used another conditional macro but I think this is suffice: - if (!readSuccess) + const TSShape::ShapeRegistration* regInfo = TSShape::sFindRegInfo(extension); + if (regInfo == NULL) { - Con::errorf("Resource::create - '%s' has an unknown file format", path.getFullPath().c_str()); - delete ret; - return NULL; + readSuccess = false; + } + else + { + readSuccess = regInfo->readFunc(path, ret); } } diff --git a/Engine/source/ts/tsShape.h b/Engine/source/ts/tsShape.h index 8e2071fb2..733a36008 100644 --- a/Engine/source/ts/tsShape.h +++ b/Engine/source/ts/tsShape.h @@ -83,18 +83,47 @@ struct TSShapeVertexArray /// @see TSShapeInstance for a further discussion of the 3space system. class TSShape { - public: - enum +public: + enum + { + UniformScale = BIT(0), + AlignedScale = BIT(1), + ArbitraryScale = BIT(2), + Blend = BIT(3), + Cyclic = BIT(4), + MakePath = BIT(5), + HasTranslucency= BIT(6), + AnyScale = UniformScale | AlignedScale | ArbitraryScale + }; + + struct ShapeFormat + { + String mName; + String mExtension; + }; + + struct ShapeRegistration + { + typedef bool(*ReadShape)(const Torque::Path& path, TSShape*& shape); + typedef bool(*WriteShape)(const Torque::Path& path, TSShape* shape); + Vector extensions; ///< the list of file extensions for this Loader [these should be lower case] + Vector export_extensions; ///< the list of file extensions for this Loader [these should be lower case] + + ReadShape readFunc; ///< the read function to read from a file. + WriteShape writeFunc; ///< the write function to write to a file. + + ShapeRegistration() { - UniformScale = BIT(0), - AlignedScale = BIT(1), - ArbitraryScale = BIT(2), - Blend = BIT(3), - Cyclic = BIT(4), - MakePath = BIT(5), - HasTranslucency= BIT(6), - AnyScale = UniformScale | AlignedScale | ArbitraryScale - }; + readFunc = NULL; + writeFunc = NULL; + VECTOR_SET_ASSOCIATION(extensions); + VECTOR_SET_ASSOCIATION(export_extensions); + } + }; + + static void sRegisterFormat(const ShapeRegistration& reg); + static const ShapeRegistration* sFindRegInfo(const String& extension, bool exporting = false); + static Vector sRegistrations; /// Nodes hold the transforms in the shape's tree. They are the bones of the skeleton. struct Node @@ -422,6 +451,8 @@ class TSShape // constructor/destructor TSShape(); ~TSShape(); + void compressionKey(U8* keyOut, U32 keyLen); + void xorBufferAtOffset(void* data, U32 byteCount, U32 startOffset, const U8* key, U32 keyLen); void init(); void initMaterialList(); ///< you can swap in a new material list, but call this if you do void finalizeEditable(); diff --git a/Engine/source/ts/tsShapeEdit.cpp b/Engine/source/ts/tsShapeEdit.cpp index db4c61549..042fb130f 100644 --- a/Engine/source/ts/tsShapeEdit.cpp +++ b/Engine/source/ts/tsShapeEdit.cpp @@ -1361,16 +1361,7 @@ bool TSShape::isShapeFileType(Torque::Path filePath) { String fileExt = filePath.getExtension(); - if ( - fileExt.equal("dts", String::NoCase) || - fileExt.equal("dsq", String::NoCase) || - fileExt.equal("dae", String::NoCase) || - fileExt.equal("fbx", String::NoCase) || - fileExt.equal("blend", String::NoCase) || - fileExt.equal("obj", String::NoCase) || - fileExt.equal("gltf", String::NoCase) || - fileExt.equal("glb", String::NoCase) - ) + if (TSShape::sFindRegInfo(fileExt)) return true; return false; diff --git a/Tools/CMake/torqueConfig.h.in b/Tools/CMake/torqueConfig.h.in index 11eba5fbe..8e50b16ef 100644 --- a/Tools/CMake/torqueConfig.h.in +++ b/Tools/CMake/torqueConfig.h.in @@ -231,3 +231,4 @@ /// Password to use when opening encrypted zip files. Change this to whatever the password is for your zips. #define DEFAULT_ZIP_PASSWORD "@TORQUE_APP_PASSWORD@" +#define TORQUE_DTS_VERSION @TORQUE_DTS_VERSION@ \ No newline at end of file diff --git a/Tools/CMake/torque_configs.cmake b/Tools/CMake/torque_configs.cmake index 241fb1e9e..c71fd3a13 100644 --- a/Tools/CMake/torque_configs.cmake +++ b/Tools/CMake/torque_configs.cmake @@ -114,6 +114,12 @@ advanced_option(TORQUE_DISABLE_MEMORY_MANAGER "Disable memory manager" ON) set(TORQUE_ENTRY_FUNCTION "" CACHE STRING "Specify a console function to execute instead of looking for a main.tscript file") mark_as_advanced(TORQUE_ENTRY_FUNCTION) +set(TORQUE_DTS_VERSION "29" CACHE STRING "Device Tree Source version") + +set_property(CACHE TORQUE_DTS_VERSION PROPERTY STRINGS + 28 29 +) + #fileIO set(TORQUE_APP_PASSWORD "changeme" CACHE STRING "zip file password") advanced_option(TORQUE_DISABLE_VIRTUAL_MOUNT_SYSTEM "Disable virtual mount system" OFF)