diff --git a/Engine/lib/assimp/CMakeLists.txt b/Engine/lib/assimp/CMakeLists.txt index 8eb9c54eb..fb1e0d1d8 100644 --- a/Engine/lib/assimp/CMakeLists.txt +++ b/Engine/lib/assimp/CMakeLists.txt @@ -70,7 +70,7 @@ if (NOT ASSIMP_BUILD_M3D_EXPORTER) endif () # if (not ASSIMP_BUILD_M3D_EXPORTER) # Toggles the use of the hunter package manager -option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" ON) +option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" OFF) IF(ASSIMP_HUNTER_ENABLED) include("cmake-modules/HunterGate.cmake") @@ -127,7 +127,7 @@ OPTION ( ASSIMP_COVERALLS ) OPTION( ASSIMP_INSTALL "Disable this if you want to use assimp as a submodule." - OFF + ON ) OPTION ( ASSIMP_WARNINGS_AS_ERRORS "Treat all warnings as errors." @@ -511,12 +511,12 @@ IF(ASSIMP_HUNTER_ENABLED) find_package(ZLIB CONFIG REQUIRED) add_definitions(-DASSIMP_BUILD_NO_OWN_ZLIB) - set( TRUE) + set(ZLIB_FOUND TRUE) set(ZLIB_LIBRARIES ZLIB::zlib) set(ASSIMP_BUILD_MINIZIP TRUE) ELSE() # If the zlib is already found outside, add an export in case assimpTargets can't find it. - IF( AND ASSIMP_INSTALL) + IF( ZLIB_FOUND AND ASSIMP_INSTALL) INSTALL( TARGETS zlib zlibstatic EXPORT "${TARGETS_EXPORT_NAME}") ENDIF() @@ -525,11 +525,11 @@ ELSE() FIND_PACKAGE(ZLIB) ENDIF() - IF ( NOT AND NOT ASSIMP_BUILD_ZLIB ) + IF ( NOT ZLIB_FOUND AND NOT ASSIMP_BUILD_ZLIB ) message( FATAL_ERROR "Build configured with -DASSIMP_BUILD_ZLIB=OFF but unable to find zlib" ) - ELSEIF( NOT ) + ELSEIF( NOT ZLIB_FOUND ) MESSAGE(STATUS "compiling zlib from sources") INCLUDE(CheckIncludeFile) INCLUDE(CheckTypeSize) @@ -545,7 +545,7 @@ ELSE() # compile from sources ADD_SUBDIRECTORY(contrib/zlib) - SET( 1) + SET(ZLIB_FOUND 1) SET(ZLIB_LIBRARIES zlibstatic) SET(ZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/contrib/zlib ${CMAKE_CURRENT_BINARY_DIR}/contrib/zlib) # need to ensure we don't link with system zlib or minizip as well. diff --git a/Engine/source/ts/assimp/assimpAppNode.cpp b/Engine/source/ts/assimp/assimpAppNode.cpp index 5385bece4..7f384a6b2 100644 --- a/Engine/source/ts/assimp/assimpAppNode.cpp +++ b/Engine/source/ts/assimp/assimpAppNode.cpp @@ -35,14 +35,17 @@ aiAnimation* AssimpAppNode::sActiveSequence = NULL; F32 AssimpAppNode::sTimeMultiplier = 1.0f; AssimpAppNode::AssimpAppNode(const struct aiScene* scene, const struct aiNode* node, AssimpAppNode* parent) -: mInvertMeshes(false), - mLastTransformTime(TSShapeLoader::DefaultTime - 1), - mDefaultTransformValid(false) + : mScene(scene), + mNode(node ? node : scene->mRootNode), + appParent(parent), + mInvertMeshes(false), + mLastTransformTime(TSShapeLoader::DefaultTime - 1), + mDefaultTransformValid(false) { - mScene = scene; - mNode = node; - appParent = parent; + mScene = scene; + mNode = node ? node : scene->mRootNode; + // Initialize node and parent names. mName = dStrdup(mNode->mName.C_Str()); if ( dStrlen(mName) == 0 ) { @@ -51,6 +54,8 @@ AssimpAppNode::AssimpAppNode(const struct aiScene* scene, const struct aiNode* n } mParentName = dStrdup(parent ? parent->getName() : "ROOT"); + + // Convert transformation matrix assimpToTorqueMat(node->mTransformation, mNodeTransform); Con::printf("[ASSIMP] Node Created: %s, Parent: %s", mName, mParentName); } @@ -58,12 +63,21 @@ AssimpAppNode::AssimpAppNode(const struct aiScene* scene, const struct aiNode* n // Get all child nodes void AssimpAppNode::buildChildList() { - if (!mNode) - { - mNode = mScene->mRootNode; + // Ensure mNode is valid + if (!mNode) { + Con::errorf("[ASSIMP] Error: mNode is null in buildChildList"); + return; } + if (!mNode->mChildren) + return; + for (U32 n = 0; n < mNode->mNumChildren; ++n) { + if (!mNode->mChildren[n]) { + Con::errorf("[ASSIMP] Warning: Null child node at index %d", n); + continue; + } + mChildNodes.push_back(new AssimpAppNode(mScene, mNode->mChildren[n], this)); } } diff --git a/Engine/source/ts/assimp/assimpAppNode.h b/Engine/source/ts/assimp/assimpAppNode.h index 41b90cd3a..85b81dc9f 100644 --- a/Engine/source/ts/assimp/assimpAppNode.h +++ b/Engine/source/ts/assimp/assimpAppNode.h @@ -50,8 +50,8 @@ class AssimpAppNode : public AppNode protected: - const struct aiScene* mScene; - const struct aiNode* mNode; ///< Pointer to the assimp scene node + const aiScene* mScene; + const aiNode* mNode; ///< Pointer to the assimp scene node AssimpAppNode* appParent; ///< Parent node MatrixF mNodeTransform; ///< Scene node transform converted to TorqueSpace (filled for ALL nodes) diff --git a/Engine/source/ts/assimp/assimpShapeLoader.cpp b/Engine/source/ts/assimp/assimpShapeLoader.cpp index 8478e4b78..da033800c 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.cpp +++ b/Engine/source/ts/assimp/assimpShapeLoader.cpp @@ -59,8 +59,6 @@ #include #include -#include - MODULE_BEGIN( AssimpShapeLoader ) MODULE_INIT_AFTER( ShapeLoader ) MODULE_INIT @@ -124,7 +122,80 @@ AssimpShapeLoader::~AssimpShapeLoader() void AssimpShapeLoader::releaseImport() { - aiReleaseImport(mScene); +} + +void debugSceneMetaData(const aiScene* scene) { + if (!scene->mMetaData) { + Con::printf("[ASSIMP] No metadata available."); + return; + } + + for (unsigned int i = 0; i < scene->mMetaData->mNumProperties; ++i) { + const char* key = scene->mMetaData->mKeys[i].C_Str(); + aiMetadataType type = scene->mMetaData->mValues[i].mType; + Con::printf("[ASSIMP] Metadata key: %s", key); + + switch (type) { + case AI_BOOL: + Con::printf(" Value: %d (bool)", *(bool*)scene->mMetaData->mValues[i].mData); + break; + case AI_INT32: + Con::printf(" Value: %d (int)", *(int*)scene->mMetaData->mValues[i].mData); + break; + case AI_UINT64: + Con::printf(" Value: %llu (uint64)", *(uint64_t*)scene->mMetaData->mValues[i].mData); + break; + case AI_FLOAT: + Con::printf(" Value: %f (float)", *(float*)scene->mMetaData->mValues[i].mData); + break; + case AI_DOUBLE: + Con::printf(" Value: %f (double)", *(double*)scene->mMetaData->mValues[i].mData); + break; + case AI_AISTRING: + Con::printf(" Value: %s (string)", ((aiString*)scene->mMetaData->mValues[i].mData)->C_Str()); + break; + case AI_AIVECTOR3D: + { + aiVector3D* vec = (aiVector3D*)scene->mMetaData->mValues[i].mData; + Con::printf(" Value: (%f, %f, %f) (vector3d)", vec->x, vec->y, vec->z); + } + break; + default: + Con::printf(" Unknown metadata type."); + } + } +} + +void applyTransformation(aiNode* node, const aiMatrix4x4& transform) { + node->mTransformation = transform * node->mTransformation; // Apply transformation to the node +} + +void reorientGLTFScene(const aiScene* scene) { + aiMatrix4x4 rotationMatrix; + rotationMatrix = aiMatrix4x4::RotationX(AI_MATH_PI / 2, rotationMatrix); // Rotate -90 degrees around X-axis + + applyTransformation(scene->mRootNode, rotationMatrix); + + rotationMatrix = aiMatrix4x4::RotationZ(AI_MATH_PI, rotationMatrix); // Rotate -90 degrees around X-axis + applyTransformation(scene->mRootNode, rotationMatrix); +} + +float getUnitScaleFactor(const aiScene* scene) { + float scale = 1.0f; + + if (scene->mMetaData) { + double unitScaleFactor; + if (scene->mMetaData->Get("UnitScaleFactor", unitScaleFactor)) { + scale = static_cast(unitScaleFactor); + } + } + + return scale; +} + +void scaleScene(const aiScene* scene, float scaleFactor) { + aiMatrix4x4 scaleMatrix = aiMatrix4x4::Scaling(aiVector3D(scaleFactor, scaleFactor, scaleFactor), aiMatrix4x4()); + applyTransformation(scene->mRootNode, scaleMatrix); } void AssimpShapeLoader::enumerateScene() @@ -132,31 +203,28 @@ void AssimpShapeLoader::enumerateScene() TSShapeLoader::updateProgress(TSShapeLoader::Load_ReadFile, "Reading File"); Con::printf("[ASSIMP] Attempting to load file: %s", shapePath.getFullPath().c_str()); - // Post-Processing - unsigned int ppsteps = - (ColladaUtils::getOptions().convertLeftHanded ? aiProcess_MakeLeftHanded : 0) | - (ColladaUtils::getOptions().reverseWindingOrder ? aiProcess_FlipWindingOrder : 0) | - (ColladaUtils::getOptions().calcTangentSpace ? aiProcess_CalcTangentSpace : 0) | - (ColladaUtils::getOptions().joinIdenticalVerts ? aiProcess_JoinIdenticalVertices : 0) | - (ColladaUtils::getOptions().removeRedundantMats ? aiProcess_RemoveRedundantMaterials : 0) | - (ColladaUtils::getOptions().genUVCoords ? aiProcess_GenUVCoords : 0) | - (ColladaUtils::getOptions().transformUVCoords ? aiProcess_TransformUVCoords : 0) | - (ColladaUtils::getOptions().flipUVCoords ? aiProcess_FlipUVs : 0) | - (ColladaUtils::getOptions().findInstances ? aiProcess_FindInstances : 0) | - (ColladaUtils::getOptions().limitBoneWeights ? aiProcess_LimitBoneWeights : 0); + // Define post-processing steps + unsigned int ppsteps = aiProcess_Triangulate | aiProcess_ValidateDataStructure | aiProcess_MakeLeftHanded | aiProcess_FlipUVs; - if (Con::getBoolVariable("$Assimp::OptimizeMeshes", false)) + const auto& options = ColladaUtils::getOptions(); + if (options.convertLeftHanded) ppsteps |= aiProcess_MakeLeftHanded; + if (options.reverseWindingOrder) ppsteps |= aiProcess_FlipWindingOrder; + if (options.calcTangentSpace) ppsteps |= aiProcess_CalcTangentSpace; + if (options.joinIdenticalVerts) ppsteps |= aiProcess_JoinIdenticalVertices; + if (options.removeRedundantMats) ppsteps |= aiProcess_RemoveRedundantMaterials; + if (options.genUVCoords) ppsteps |= aiProcess_GenUVCoords; + if (options.transformUVCoords) ppsteps |= aiProcess_TransformUVCoords; + if (options.findInstances) ppsteps |= aiProcess_FindInstances; + if (options.limitBoneWeights) ppsteps |= aiProcess_LimitBoneWeights; + + if (Con::getBoolVariable("$Assimp::OptimizeMeshes", false)) { ppsteps |= aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph; - - if (Con::getBoolVariable("$Assimp::SplitLargeMeshes", false)) + } + if (Con::getBoolVariable("$Assimp::SplitLargeMeshes", false)) { ppsteps |= aiProcess_SplitLargeMeshes; + } - // Mandatory options - //ppsteps |= aiProcess_ValidateDataStructure | aiProcess_Triangulate | aiProcess_ImproveCacheLocality; - ppsteps |= aiProcess_Triangulate; - //aiProcess_SortByPType | // make 'clean' meshes which consist of a single typ of primitives - - aiPropertyStore* props = aiCreatePropertyStore(); + ppsteps |= aiProcess_ValidateDataStructure; struct aiLogStream shapeLog = aiGetPredefinedLogStream(aiDefaultLogStream_STDOUT, NULL); shapeLog.callback = assimpLogCallback; @@ -165,94 +233,113 @@ void AssimpShapeLoader::enumerateScene() #ifdef TORQUE_DEBUG aiEnableVerboseLogging(true); #endif + + // Read the file + mScene = mImporter.ReadFile(shapePath.getFullPath().c_str(), ppsteps); - mScene = (aiScene*)aiImportFileExWithProperties(shapePath.getFullPath().c_str(), ppsteps, NULL, props); - - aiReleasePropertyStore(props); - - if ( mScene ) - { - Con::printf("[ASSIMP] Mesh Count: %d", mScene->mNumMeshes); - Con::printf("[ASSIMP] Material Count: %d", mScene->mNumMaterials); - - // Setup default units for shape format - String importFormat; - - String fileExt = String::ToLower(shapePath.getExtension()); - const aiImporterDesc* importerDescription = aiGetImporterDesc(fileExt.c_str()); - if (importerDescription && StringTable->insert(importerDescription->mName) == StringTable->insert("Autodesk FBX Importer")) - { - ColladaUtils::getOptions().formatScaleFactor = 0.01f; - } - - // Set import options (if they are not set to override) - if (ColladaUtils::getOptions().unit <= 0.0f) - { - F64 unit; - if (!getMetaDouble("UnitScaleFactor", unit)) - { - F32 floatVal; - S32 intVal; - if (getMetaFloat("UnitScaleFactor", floatVal)) - unit = (F64)floatVal; - else if (getMetaInt("UnitScaleFactor", intVal)) - unit = (F64)intVal; - else - unit = 1.0; - } - ColladaUtils::getOptions().unit = (F32)unit; - } - - if (ColladaUtils::getOptions().upAxis == UPAXISTYPE_COUNT) - { - S32 upAxis; - if (!getMetaInt("UpAxis", upAxis)) - upAxis = UPAXISTYPE_Z_UP; - ColladaUtils::getOptions().upAxis = (domUpAxisType) upAxis; - } - - // Extract embedded textures - for (U32 i = 0; i < mScene->mNumTextures; ++i) - extractTexture(i, mScene->mTextures[i]); - - // Load all the materials. - AssimpAppMaterial::sDefaultMatNumber = 0; - for ( U32 i = 0; i < mScene->mNumMaterials; i++ ) - AppMesh::appMaterials.push_back(new AssimpAppMaterial(mScene->mMaterials[i])); - - // Setup LOD checks - detectDetails(); - - // Define the root node, and process down the chain. - AssimpAppNode* node = new AssimpAppNode(mScene, mScene->mRootNode, 0); - - if (!processNode(node)) - delete node; - - // add bounds node. - if (!boundsNode) - { - aiNode* req[1]; - req[0] = new aiNode("bounds"); - mScene->mRootNode->addChildren(1, req); - - AssimpAppNode* appBounds = new AssimpAppNode(mScene, req[0]); - if (!processNode(appBounds)) - delete appBounds; - } - - // Check for animations and process those. - processAnimations(); - } - else - { + if (!mScene || (mScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) || !mScene->mRootNode) { + Con::errorf("[ASSIMP] ERROR: Could not load file: %s", shapePath.getFullPath().c_str()); + Con::errorf("[ASSIMP] Importer error: %s", mImporter.GetErrorString()); TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import failed"); - Con::printf("[ASSIMP] Import Error: %s", aiGetErrorString()); + return; } + //debugSceneMetaData(mScene); + + Con::printf("[ASSIMP] Mesh Count: %d", mScene->mNumMeshes); + Con::printf("[ASSIMP] Material Count: %d", mScene->mNumMaterials); + + // Format-specific adjustments + String fileExt = String::ToLower(shapePath.getExtension()); + const aiImporterDesc* importerDescription = aiGetImporterDesc(fileExt.c_str()); + if (fileExt == String::ToString("gltf") || fileExt == String::ToString("glb")) { + Con::printf("[ASSIMP] Detected GLTF format, applying reorientation..."); + reorientGLTFScene(mScene); // Reorient GLTF + } + + if (importerDescription && dStrcmp(importerDescription->mName, "Autodesk FBX Importer") == 0) { + Con::printf("[ASSIMP] Detected FBX format, checking unit scale..."); + F32 scaleFactor = getUnitScaleFactor(mScene); + if (scaleFactor != 1.0f) { + Con::printf("[ASSIMP] Applying FBX scale factor: %f", scaleFactor); + scaleScene(mScene, scaleFactor); + } + else + { + scaleScene(mScene, 0.01f); + } + } + + // Handle scaling and up-axis conversions if necessary + configureImportUnitsAndAxis(); + + // Extract embedded textures + for (unsigned int i = 0; i < mScene->mNumTextures; ++i) { + extractTexture(i, mScene->mTextures[i]); + } + + // Load all materials + AssimpAppMaterial::sDefaultMatNumber = 0; + for (unsigned int i = 0; i < mScene->mNumMaterials; ++i) { + AppMesh::appMaterials.push_back(new AssimpAppMaterial(mScene->mMaterials[i])); + } + + // Setup LOD checks + detectDetails(); + + // Process the scene graph + AssimpAppNode* rootNode = new AssimpAppNode(mScene, mScene->mRootNode, 0); + if (!processNode(rootNode)) { + delete rootNode; + } + + // Add a bounds node if none exists + if (!boundsNode) { + aiNode* req[1]; + req[0] = new aiNode("bounds"); + mScene->mRootNode->addChildren(1, req); + + auto* appBoundsNode = new AssimpAppNode(mScene, req[0]); + if (!processNode(appBoundsNode)) { + delete appBoundsNode; + } + } + + // Process animations if available + processAnimations(); + + // Clean up log stream aiDetachLogStream(&shapeLog); } +void AssimpShapeLoader::configureImportUnitsAndAxis() { + auto& options = ColladaUtils::getOptions(); + + // Configure unit scaling + if (options.unit <= 0.0f) { + F64 unitScaleFactor = 1.0; + if (!getMetaDouble("UnitScaleFactor", unitScaleFactor)) { + float floatVal; + int intVal; + if (getMetaFloat("UnitScaleFactor", floatVal)) { + unitScaleFactor = static_cast(floatVal); + } + else if (getMetaInt("UnitScaleFactor", intVal)) { + unitScaleFactor = static_cast(intVal); + } + } + options.unit = static_cast(unitScaleFactor); + } + + // Configure up-axis + if (options.upAxis == UPAXISTYPE_COUNT) { + int upAxis = UPAXISTYPE_Z_UP; + if (getMetaInt("UpAxis", upAxis)) { + options.upAxis = static_cast(upAxis); + } + } +} + void AssimpShapeLoader::processAnimations() { // add all animations into 1 ambient animation. @@ -266,6 +353,7 @@ void AssimpShapeLoader::processAnimations() for (U32 i = 0; i < mScene->mNumAnimations; ++i) { aiAnimation* anim = mScene->mAnimations[i]; + duration = anim->mDuration; for (U32 j = 0; j < anim->mNumChannels; j++) { aiNodeAnim* nodeAnim = anim->mChannels[j]; diff --git a/Engine/source/ts/assimp/assimpShapeLoader.h b/Engine/source/ts/assimp/assimpShapeLoader.h index 26a163154..7caf8741b 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.h +++ b/Engine/source/ts/assimp/assimpShapeLoader.h @@ -26,6 +26,7 @@ #ifndef _TSSHAPELOADER_H_ #include "ts/loader/tsShapeLoader.h" #endif +#include #include class GuiTreeViewCtrl; @@ -37,7 +38,8 @@ class AssimpShapeLoader : public TSShapeLoader friend TSShape* assimpLoadShape(const Torque::Path &path); protected: - const struct aiScene* mScene; + Assimp::Importer mImporter; + const aiScene* mScene; bool ignoreNode(const String& name) override; bool ignoreMesh(const String& name) override; @@ -59,6 +61,7 @@ public: void releaseImport(); void enumerateScene() override; + void configureImportUnitsAndAxis(); void updateMaterialsScript(const Torque::Path &path); void processAnimations();