assimpShapeLoader.cpp
Engine/source/ts/assimp/assimpShapeLoader.cpp
Public Variables
Public Functions
TSShape *
assimpLoadShape(const Torque::Path & path)
This function is invoked by the resource manager based on file extension.
DefineEngineFunction(GetShapeInfo , bool , (const char *shapePath, const char *ctrl, bool loadCachedDts) , ("", "", true) , "(string shapePath, <a href="/coding/class/classguitreeviewctrl/">GuiTreeViewCtrl</a> ctrl) Collect scene information from " "<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> shape <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> and store it in <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> GuiTreeView control. This function is " "used by the assimp import gui <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> show <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> preview of the scene contents " "prior <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> import, and is probably not much use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> anything <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">else.\n</a>" " @param shapePath shape <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">filename\n</a>" " @param ctrl GuiTreeView <a href="/coding/file/guieditctrl_8cpp/#guieditctrl_8cpp_1abb04e3738c4c5a96b3ade6fa47013a6c">control</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> add elements <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">to\n</a>" " @return true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> successful, false <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">otherwise\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editors\n</a>" " @internal" )
Detailed Description
Public Variables
MODULE_END
MODULE_INIT
Public Functions
assimpLoadShape(const Torque::Path & path)
This function is invoked by the resource manager based on file extension.
DefineEngineFunction(GetShapeInfo , bool , (const char *shapePath, const char *ctrl, bool loadCachedDts) , ("", "", true) , "(string shapePath, <a href="/coding/class/classguitreeviewctrl/">GuiTreeViewCtrl</a> ctrl) Collect scene information from " "<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> shape <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> and store it in <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> GuiTreeView control. This function is " "used by the assimp import gui <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> show <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> preview of the scene contents " "prior <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> import, and is probably not much use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> anything <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">else.\n</a>" " @param shapePath shape <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">filename\n</a>" " @param ctrl GuiTreeView <a href="/coding/file/guieditctrl_8cpp/#guieditctrl_8cpp_1abb04e3738c4c5a96b3ade6fa47013a6c">control</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> add elements <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">to\n</a>" " @return true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> successful, false <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">otherwise\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editors\n</a>" " @internal" )
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24/* 25 Resource stream -> Buffer 26 Buffer -> Collada DOM 27 Collada DOM -> TSShapeLoader 28 TSShapeLoader installed into TSShape 29*/ 30 31//----------------------------------------------------------------------------- 32 33#include "platform/platform.h" 34 35#include "ts/assimp/assimpShapeLoader.h" 36#include "ts/assimp/assimpAppNode.h" 37#include "ts/assimp/assimpAppMesh.h" 38#include "ts/assimp/assimpAppMaterial.h" 39#include "ts/assimp/assimpAppSequence.h" 40 41#include "core/util/tVector.h" 42#include "core/strings/findMatch.h" 43#include "core/strings/stringUnit.h" 44#include "core/stream/fileStream.h" 45#include "core/fileObject.h" 46#include "ts/tsShape.h" 47#include "ts/tsShapeInstance.h" 48#include "materials/materialManager.h" 49#include "console/persistenceManager.h" 50#include "ts/tsShapeConstruct.h" 51#include "core/util/zip/zipVolume.h" 52#include "gfx/bitmap/gBitmap.h" 53#include "gui/controls/guiTreeViewCtrl.h" 54 55// assimp include files. 56#include <assimp/cimport.h> 57#include <assimp/scene.h> 58#include <assimp/postprocess.h> 59#include <assimp/types.h> 60#include <assimp/config.h> 61#include <exception> 62 63#include <assimp/Importer.hpp> 64 65MODULE_BEGIN( AssimpShapeLoader ) 66 MODULE_INIT_AFTER( ShapeLoader ) 67 MODULE_INIT 68 { 69 TSShapeLoader::addFormat("DirectX X", "x"); 70 TSShapeLoader::addFormat("Autodesk FBX", "fbx"); 71 TSShapeLoader::addFormat("Blender 3D", "blend" ); 72 TSShapeLoader::addFormat("3ds Max 3DS", "3ds"); 73 TSShapeLoader::addFormat("3ds Max ASE", "ase"); 74 TSShapeLoader::addFormat("Wavefront Object", "obj"); 75 TSShapeLoader::addFormat("Industry Foundation Classes (IFC/Step)", "ifc"); 76 TSShapeLoader::addFormat("Stanford Polygon Library", "ply"); 77 TSShapeLoader::addFormat("AutoCAD DXF", "dxf"); 78 TSShapeLoader::addFormat("LightWave", "lwo"); 79 TSShapeLoader::addFormat("LightWave Scene", "lws"); 80 TSShapeLoader::addFormat("Modo", "lxo"); 81 TSShapeLoader::addFormat("Stereolithography", "stl"); 82 TSShapeLoader::addFormat("AC3D", "ac"); 83 TSShapeLoader::addFormat("Milkshape 3D", "ms3d"); 84 TSShapeLoader::addFormat("TrueSpace COB", "cob"); 85 TSShapeLoader::addFormat("TrueSpace SCN", "scn"); 86 TSShapeLoader::addFormat("Ogre XML", "xml"); 87 TSShapeLoader::addFormat("Irrlicht Mesh", "irrmesh"); 88 TSShapeLoader::addFormat("Irrlicht Scene", "irr"); 89 TSShapeLoader::addFormat("Quake I", "mdl" ); 90 TSShapeLoader::addFormat("Quake II", "md2" ); 91 TSShapeLoader::addFormat("Quake III Mesh", "md3"); 92 TSShapeLoader::addFormat("Quake III Map/BSP", "pk3"); 93 TSShapeLoader::addFormat("Return to Castle Wolfenstein", "mdc"); 94 TSShapeLoader::addFormat("Doom 3", "md5" ); 95 TSShapeLoader::addFormat("Valve SMD", "smd"); 96 TSShapeLoader::addFormat("Valve VTA", "vta"); 97 TSShapeLoader::addFormat("Starcraft II M3", "m3"); 98 TSShapeLoader::addFormat("Unreal", "3d"); 99 TSShapeLoader::addFormat("BlitzBasic 3D", "b3d" ); 100 TSShapeLoader::addFormat("Quick3D Q3D", "q3d"); 101 TSShapeLoader::addFormat("Quick3D Q3S", "q3s"); 102 TSShapeLoader::addFormat("Neutral File Format", "nff"); 103 TSShapeLoader::addFormat("Object File Format", "off"); 104 TSShapeLoader::addFormat("PovRAY Raw", "raw"); 105 TSShapeLoader::addFormat("Terragen Terrain", "ter"); 106 TSShapeLoader::addFormat("3D GameStudio (3DGS)", "mdl"); 107 TSShapeLoader::addFormat("3D GameStudio (3DGS) Terrain", "hmp"); 108 TSShapeLoader::addFormat("Izware Nendo", "ndo"); 109 TSShapeLoader::addFormat("gltf", "gltf"); 110 TSShapeLoader::addFormat("gltf binary", "glb"); 111 } 112MODULE_END; 113 114//----------------------------------------------------------------------------- 115 116AssimpShapeLoader::AssimpShapeLoader() 117{ 118 mScene = NULL; 119} 120 121AssimpShapeLoader::~AssimpShapeLoader() 122{ 123 124} 125 126void AssimpShapeLoader::releaseImport() 127{ 128 aiReleaseImport(mScene); 129} 130 131void AssimpShapeLoader::enumerateScene() 132{ 133 TSShapeLoader::updateProgress(TSShapeLoader::Load_ReadFile, "Reading File"); 134 Con::printf("[ASSIMP] Attempting to load file: %s", shapePath.getFullPath().c_str()); 135 136 // Post-Processing 137 unsigned int ppsteps = 138 (ColladaUtils::getOptions().convertLeftHanded ? aiProcess_MakeLeftHanded : 0) | 139 (ColladaUtils::getOptions().reverseWindingOrder ? aiProcess_FlipWindingOrder : 0) | 140 (ColladaUtils::getOptions().calcTangentSpace ? aiProcess_CalcTangentSpace : 0) | 141 (ColladaUtils::getOptions().joinIdenticalVerts ? aiProcess_JoinIdenticalVertices : 0) | 142 (ColladaUtils::getOptions().removeRedundantMats ? aiProcess_RemoveRedundantMaterials : 0) | 143 (ColladaUtils::getOptions().genUVCoords ? aiProcess_GenUVCoords : 0) | 144 (ColladaUtils::getOptions().transformUVCoords ? aiProcess_TransformUVCoords : 0) | 145 (ColladaUtils::getOptions().flipUVCoords ? aiProcess_FlipUVs : 0) | 146 (ColladaUtils::getOptions().findInstances ? aiProcess_FindInstances : 0) | 147 (ColladaUtils::getOptions().limitBoneWeights ? aiProcess_LimitBoneWeights : 0); 148 149 if (Con::getBoolVariable("$Assimp::OptimizeMeshes", false)) 150 ppsteps |= aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph; 151 152 if (Con::getBoolVariable("$Assimp::SplitLargeMeshes", false)) 153 ppsteps |= aiProcess_SplitLargeMeshes; 154 155 // Mandatory options 156 //ppsteps |= aiProcess_ValidateDataStructure | aiProcess_Triangulate | aiProcess_ImproveCacheLocality; 157 ppsteps |= aiProcess_Triangulate; 158 //aiProcess_SortByPType | // make 'clean' meshes which consist of a single typ of primitives 159 160 aiPropertyStore* props = aiCreatePropertyStore(); 161 162 struct aiLogStream shapeLog = aiGetPredefinedLogStream(aiDefaultLogStream_STDOUT, NULL); 163 shapeLog.callback = assimpLogCallback; 164 shapeLog.user = 0; 165 aiAttachLogStream(&shapeLog); 166#ifdef TORQUE_DEBUG 167 aiEnableVerboseLogging(true); 168#endif 169 170 mScene = (aiScene*)aiImportFileExWithProperties(shapePath.getFullPath().c_str(), ppsteps, NULL, props); 171 172 aiReleasePropertyStore(props); 173 174 if ( mScene ) 175 { 176 Con::printf("[ASSIMP] Mesh Count: %d", mScene->mNumMeshes); 177 Con::printf("[ASSIMP] Material Count: %d", mScene->mNumMaterials); 178 179 // Setup default units for shape format 180 String importFormat; 181 if (getMetaString("SourceAsset_Format", importFormat)) 182 { 183 // FBX uses cm as standard unit, so convert to meters 184 if (importFormat.equal("Autodesk FBX Importer", String::NoCase)) 185 ColladaUtils::getOptions().formatScaleFactor = 0.01f; 186 } 187 188 // Set import options (if they are not set to override) 189 if (ColladaUtils::getOptions().unit <= 0.0f) 190 { 191 F64 unit; 192 if (!getMetaDouble("UnitScaleFactor", unit)) 193 { 194 F32 floatVal; 195 S32 intVal; 196 if (getMetaFloat("UnitScaleFactor", floatVal)) 197 unit = (F64)floatVal; 198 else if (getMetaInt("UnitScaleFactor", intVal)) 199 unit = (F64)intVal; 200 else 201 unit = 1.0; 202 } 203 ColladaUtils::getOptions().unit = (F32)unit; 204 } 205 206 if (ColladaUtils::getOptions().upAxis == UPAXISTYPE_COUNT) 207 { 208 S32 upAxis; 209 if (!getMetaInt("UpAxis", upAxis)) 210 upAxis = UPAXISTYPE_Z_UP; 211 ColladaUtils::getOptions().upAxis = (domUpAxisType) upAxis; 212 } 213 214 // Extract embedded textures 215 for (U32 i = 0; i < mScene->mNumTextures; ++i) 216 extractTexture(i, mScene->mTextures[i]); 217 218 // Load all the materials. 219 AssimpAppMaterial::sDefaultMatNumber = 0; 220 for ( U32 i = 0; i < mScene->mNumMaterials; i++ ) 221 AppMesh::appMaterials.push_back(new AssimpAppMaterial(mScene->mMaterials[i])); 222 223 // Setup LOD checks 224 detectDetails(); 225 226 // Define the root node, and process down the chain. 227 AssimpAppNode* node = new AssimpAppNode(mScene, mScene->mRootNode, 0); 228 229 if (!processNode(node)) 230 delete node; 231 232 // Check for animations and process those. 233 processAnimations(); 234 } 235 else 236 { 237 TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import failed"); 238 Con::printf("[ASSIMP] Import Error: %s", aiGetErrorString()); 239 } 240 241 aiDetachLogStream(&shapeLog); 242} 243 244void AssimpShapeLoader::processAnimations() 245{ 246 for(U32 n = 0; n < mScene->mNumAnimations; ++n) 247 { 248 Con::printf("[ASSIMP] Animation Found: %s", mScene->mAnimations[n]->mName.C_Str()); 249 250 AssimpAppSequence* newAssimpSeq = new AssimpAppSequence(mScene->mAnimations[n]); 251 appSequences.push_back(newAssimpSeq); 252 } 253} 254 255void AssimpShapeLoader::computeBounds(Box3F& bounds) 256{ 257 TSShapeLoader::computeBounds(bounds); 258 259 // Check if the model origin needs adjusting 260 bool adjustCenter = ColladaUtils::getOptions().adjustCenter; 261 bool adjustFloor = ColladaUtils::getOptions().adjustFloor; 262 if (bounds.isValidBox() && (adjustCenter || adjustFloor)) 263 { 264 // Compute shape offset 265 Point3F shapeOffset = Point3F::Zero; 266 if (adjustCenter) 267 { 268 bounds.getCenter(&shapeOffset); 269 shapeOffset = -shapeOffset; 270 } 271 if (adjustFloor) 272 shapeOffset.z = -bounds.minExtents.z; 273 274 // Adjust bounds 275 bounds.minExtents += shapeOffset; 276 bounds.maxExtents += shapeOffset; 277 278 // Now adjust all positions for root level nodes (nodes with no parent) 279 for (S32 iNode = 0; iNode < shape->nodes.size(); iNode++) 280 { 281 if (!appNodes[iNode]->isParentRoot()) 282 continue; 283 284 // Adjust default translation 285 shape->defaultTranslations[iNode] += shapeOffset; 286 287 // Adjust animated translations 288 for (S32 iSeq = 0; iSeq < shape->sequences.size(); iSeq++) 289 { 290 const TSShape::Sequence& seq = shape->sequences[iSeq]; 291 if (seq.translationMatters.test(iNode)) 292 { 293 for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++) 294 { 295 S32 index = seq.baseTranslation + seq.translationMatters.count(iNode)*seq.numKeyframes + iFrame; 296 shape->nodeTranslations[index] += shapeOffset; 297 } 298 } 299 } 300 } 301 } 302} 303 304bool AssimpShapeLoader::fillGuiTreeView(const char* sourceShapePath, GuiTreeViewCtrl* tree) 305{ 306 Assimp::Importer importer; 307 Torque::Path path(sourceShapePath); 308 String cleanFile = AppMaterial::cleanString(path.getFileName()); 309 310 // Attempt to import with Assimp. 311 const aiScene* shapeScene = importer.ReadFile(path.getFullPath().c_str(), (aiProcessPreset_TargetRealtime_Quality | aiProcess_CalcTangentSpace) 312 & ~aiProcess_RemoveRedundantMaterials & ~aiProcess_GenSmoothNormals); 313 314 if (!shapeScene) 315 { 316 Con::printf("AssimpShapeLoader::fillGuiTreeView - Assimp Error: %s", importer.GetErrorString()); 317 return false; 318 } 319 mScene = shapeScene; 320 321 // Initialize tree 322 tree->removeItem(0); 323 S32 meshItem = tree->insertItem(0, "Meshes", String::ToString("%i", shapeScene->mNumMeshes)); 324 S32 matItem = tree->insertItem(0, "Materials", String::ToString("%i", shapeScene->mNumMaterials)); 325 S32 animItem = tree->insertItem(0, "Animations", String::ToString("%i", shapeScene->mNumAnimations)); 326 //S32 lightsItem = tree->insertItem(0, "Lights", String::ToString("%i", shapeScene->mNumLights)); 327 //S32 texturesItem = tree->insertItem(0, "Textures", String::ToString("%i", shapeScene->mNumTextures)); 328 329 //Details! 330 U32 numPolys = 0; 331 U32 numVerts = 0; 332 for (U32 i = 0; i < shapeScene->mNumMeshes; i++) 333 { 334 tree->insertItem(meshItem, String::ToString("%s", shapeScene->mMeshes[i]->mName.C_Str())); 335 numPolys += shapeScene->mMeshes[i]->mNumFaces; 336 numVerts += shapeScene->mMeshes[i]->mNumVertices; 337 } 338 339 U32 defaultMatNumber = 0; 340 for (U32 i = 0; i < shapeScene->mNumMaterials; i++) 341 { 342 aiMaterial* aiMat = shapeScene->mMaterials[i]; 343 344 aiString matName; 345 aiMat->Get(AI_MATKEY_NAME, matName); 346 String name = matName.C_Str(); 347 if (name.isEmpty()) 348 { 349 name = AppMaterial::cleanString(path.getFileName()); 350 name += "_defMat"; 351 name += String::ToString("%d", defaultMatNumber); 352 defaultMatNumber++; 353 } 354 355 aiString texPath; 356 aiMat->GetTexture(aiTextureType::aiTextureType_DIFFUSE, 0, &texPath); 357 String texName = texPath.C_Str(); 358 if (texName.isEmpty()) 359 { 360 aiColor3D read_color(1.f, 1.f, 1.f); 361 if (AI_SUCCESS == aiMat->Get(AI_MATKEY_COLOR_DIFFUSE, read_color)) 362 texName = String::ToString("Color: %0.3f %0.3f %0.3f", (F32)read_color.r, (F32)read_color.g, (F32)read_color.b); //formatted as words for easy parsing 363 else 364 texName = "No Texture"; 365 } 366 else 367 texName = AssimpAppMaterial::cleanTextureName(texName, cleanFile, sourceShapePath, true); 368 369 tree->insertItem(matItem, String::ToString("%s", name.c_str()), String::ToString("%s", texName.c_str())); 370 } 371 372 for (U32 i = 0; i < shapeScene->mNumAnimations; i++) 373 { 374 String sequenceName = shapeScene->mAnimations[i]->mName.C_Str(); 375 if (sequenceName.isEmpty()) 376 sequenceName = "ambient"; 377 tree->insertItem(animItem, sequenceName.c_str()); 378 } 379 380 U32 numNodes = 0; 381 if (shapeScene->mRootNode) 382 { 383 S32 nodesItem = tree->insertItem(0, "Nodes", ""); 384 addNodeToTree(nodesItem, shapeScene->mRootNode, tree, numNodes); 385 tree->setItemValue(nodesItem, String::ToString("%i", numNodes)); 386 } 387 388 U32 numMetaTags = shapeScene->mMetaData ? shapeScene->mMetaData->mNumProperties : 0; 389 if (numMetaTags) 390 addMetaDataToTree(shapeScene->mMetaData, tree); 391 392 F64 unit; 393 if (!getMetaDouble("UnitScaleFactor", unit)) 394 unit = 1.0f; 395 396 S32 upAxis; 397 if (!getMetaInt("UpAxis", upAxis)) 398 upAxis = UPAXISTYPE_Z_UP; 399 400 /*for (U32 i = 0; i < shapeScene->mNumLights; i++) 401 { 402 treeObj->insertItem(lightsItem, String::ToString("%s", shapeScene->mLights[i]->mType)); 403 }*/ 404 405 // Store shape information in the tree control 406 tree->setDataField(StringTable->insert("_nodeCount"), 0, avar("%d", numNodes)); 407 tree->setDataField(StringTable->insert("_meshCount"), 0, avar("%d", shapeScene->mNumMeshes)); 408 tree->setDataField(StringTable->insert("_polygonCount"), 0, avar("%d", numPolys)); 409 tree->setDataField(StringTable->insert("_materialCount"), 0, avar("%d", shapeScene->mNumMaterials)); 410 tree->setDataField(StringTable->insert("_lightCount"), 0, avar("%d", shapeScene->mNumLights)); 411 tree->setDataField(StringTable->insert("_animCount"), 0, avar("%d", shapeScene->mNumAnimations)); 412 tree->setDataField(StringTable->insert("_textureCount"), 0, avar("%d", shapeScene->mNumTextures)); 413 tree->setDataField(StringTable->insert("_vertCount"), 0, avar("%d", numVerts)); 414 tree->setDataField(StringTable->insert("_metaTagCount"), 0, avar("%d", numMetaTags)); 415 tree->setDataField(StringTable->insert("_unit"), 0, avar("%g", (F32)unit)); 416 417 if (upAxis == UPAXISTYPE_X_UP) 418 tree->setDataField(StringTable->insert("_upAxis"), 0, "X_AXIS"); 419 else if (upAxis == UPAXISTYPE_Y_UP) 420 tree->setDataField(StringTable->insert("_upAxis"), 0, "Y_AXIS"); 421 else 422 tree->setDataField(StringTable->insert("_upAxis"), 0, "Z_AXIS"); 423 424 return true; 425} 426 427void AssimpShapeLoader::updateMaterialsScript(const Torque::Path &path) 428{ 429 Torque::Path scriptPath(path); 430 scriptPath.setFileName("materials"); 431 scriptPath.setExtension(TORQUE_SCRIPT_EXTENSION); 432 433 // First see what materials we need to update 434 PersistenceManager persistMgr; 435 for ( U32 iMat = 0; iMat < AppMesh::appMaterials.size(); iMat++ ) 436 { 437 AssimpAppMaterial *mat = dynamic_cast<AssimpAppMaterial*>( AppMesh::appMaterials[iMat] ); 438 if ( mat ) 439 { 440 Material *mappedMat; 441 if ( Sim::findObject( MATMGR->getMapEntry( mat->getName() ), mappedMat ) ) 442 { 443 // Only update existing materials if forced to 444 if (ColladaUtils::getOptions().forceUpdateMaterials) 445 { 446 mat->initMaterial(scriptPath, mappedMat); 447 persistMgr.setDirty(mappedMat); 448 } 449 } 450 else 451 { 452 // Create a new material definition 453 persistMgr.setDirty( mat->createMaterial( scriptPath ), scriptPath.getFullPath() ); 454 } 455 } 456 } 457 458 if ( persistMgr.getDirtyList().empty() ) 459 return; 460 461 persistMgr.saveDirty(); 462} 463 464/// Check if an up-to-date cached DTS is available for this DAE file 465bool AssimpShapeLoader::canLoadCachedDTS(const Torque::Path& path) 466{ 467 // Generate the cached filename 468 Torque::Path cachedPath(path); 469 cachedPath.setExtension("cached.dts"); 470 471 // Check if a cached DTS newer than this file is available 472 FileTime cachedModifyTime; 473 if (Platform::getFileTimes(cachedPath.getFullPath(), NULL, &cachedModifyTime)) 474 { 475 bool forceLoad = Con::getBoolVariable("$assimp::forceLoad", false); 476 477 FileTime daeModifyTime; 478 if (!Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime) || 479 (!forceLoad && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0) )) 480 { 481 // Original file not found, or cached DTS is newer 482 return true; 483 } 484 } 485 486 return false; 487} 488 489void AssimpShapeLoader::assimpLogCallback(const char* message, char* user) 490{ 491 Con::printf("[Assimp log message] %s", StringUnit::getUnit(message, 0, "\n")); 492} 493 494bool AssimpShapeLoader::ignoreNode(const String& name) 495{ 496 // Do not add AssimpFbx dummy nodes to the TSShape. See: Assimp::FBX::ImportSettings::preservePivots 497 // https://github.com/assimp/assimp/blob/master/code/FBXImportSettings.h#L116-L135 498 if (name.find("_$AssimpFbx$_") != String::NPos) 499 return true; 500 501 if (FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().alwaysImport, name, false)) 502 return false; 503 504 return FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().neverImport, name, false); 505} 506 507bool AssimpShapeLoader::ignoreMesh(const String& name) 508{ 509 if (FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().alwaysImportMesh, name, false)) 510 return false; 511 else 512 return FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().neverImportMesh, name, false); 513} 514 515void AssimpShapeLoader::detectDetails() 516{ 517 // Set LOD option 518 bool singleDetail = true; 519 switch (ColladaUtils::getOptions().lodType) 520 { 521 case ColladaUtils::ImportOptions::DetectDTS: 522 // Check for a baseXX->startXX hierarchy at the top-level, if we find 523 // one, use trailing numbers for LOD, otherwise use a single size 524 for (S32 iNode = 0; singleDetail && (iNode < mScene->mRootNode->mNumChildren); iNode++) { 525 aiNode* node = mScene->mRootNode->mChildren[iNode]; 526 if (node && dStrStartsWith(node->mName.C_Str(), "base")) { 527 for (S32 iChild = 0; iChild < node->mNumChildren; iChild++) { 528 aiNode* child = node->mChildren[iChild]; 529 if (child && dStrStartsWith(child->mName.C_Str(), "start")) { 530 singleDetail = false; 531 break; 532 } 533 } 534 } 535 } 536 break; 537 538 case ColladaUtils::ImportOptions::SingleSize: 539 singleDetail = true; 540 break; 541 542 case ColladaUtils::ImportOptions::TrailingNumber: 543 singleDetail = false; 544 break; 545 546 default: 547 break; 548 } 549 550 AssimpAppMesh::fixDetailSize(singleDetail, ColladaUtils::getOptions().singleDetailSize); 551} 552 553void AssimpShapeLoader::extractTexture(U32 index, aiTexture* pTex) 554{ // Cache an embedded texture to disk 555 updateProgress(Load_EnumerateScene, "Extracting Textures...", mScene->mNumTextures, index); 556 Con::printf("[Assimp] Extracting Texture %s, W: %d, H: %d, %d of %d, format hint: (%s)", pTex->mFilename.C_Str(), 557 pTex->mWidth, pTex->mHeight, index, mScene->mNumTextures, pTex->achFormatHint); 558 559 // Create the texture filename 560 String cleanFile = AppMaterial::cleanString(TSShapeLoader::getShapePath().getFileName()); 561 String texName = String::ToString("%s_cachedTex%d", cleanFile.c_str(), index); 562 Torque::Path texPath = shapePath; 563 texPath.setFileName(texName); 564 565 if (pTex->mHeight == 0) 566 { // Compressed format, write the data directly to disc 567 texPath.setExtension(pTex->achFormatHint); 568 FileStream *outputStream; 569 if ((outputStream = FileStream::createAndOpen(texPath.getFullPath(), Torque::FS::File::Write)) != NULL) 570 { 571 outputStream->setPosition(0); 572 outputStream->write(pTex->mWidth, pTex->pcData); 573 outputStream->close(); 574 delete outputStream; 575 } 576 } 577 else 578 { // Embedded pixel data, fill a bitmap and save it. 579 GFXTexHandle shapeTex; 580 shapeTex.set(pTex->mWidth, pTex->mHeight, GFXFormatR8G8B8A8_SRGB, &GFXDynamicTextureSRGBProfile, 581 String::ToString("AssimpShapeLoader (%s:%i)", __FILE__, __LINE__), 1, 0); 582 GFXLockedRect *rect = shapeTex.lock(); 583 584 for (U32 y = 0; y < pTex->mHeight; ++y) 585 { 586 for (U32 x = 0; x < pTex->mWidth; ++x) 587 { 588 U32 targetIndex = (y * rect->pitch) + (x * 4); 589 U32 sourceIndex = ((y * pTex->mWidth) + x) * 4; 590 rect->bits[targetIndex] = pTex->pcData[sourceIndex].r; 591 rect->bits[targetIndex + 1] = pTex->pcData[sourceIndex].g; 592 rect->bits[targetIndex + 2] = pTex->pcData[sourceIndex].b; 593 rect->bits[targetIndex + 3] = pTex->pcData[sourceIndex].a; 594 } 595 } 596 shapeTex.unlock(); 597 598 texPath.setExtension("png"); 599 shapeTex->dumpToDisk("PNG", texPath.getFullPath()); 600 } 601} 602 603void AssimpShapeLoader::addNodeToTree(S32 parentItem, aiNode* node, GuiTreeViewCtrl* tree, U32& nodeCount) 604{ 605 // Add this node 606 S32 nodeItem = parentItem; 607 String nodeName = node->mName.C_Str(); 608 if (!ignoreNode(nodeName)) 609 { 610 if (nodeName.isEmpty()) 611 nodeName = "null"; 612 nodeItem = tree->insertItem(parentItem, nodeName.c_str(), String::ToString("%i", node->mNumChildren)); 613 nodeCount++; 614 } 615 616 // Add any child nodes 617 for (U32 n = 0; n < node->mNumChildren; ++n) 618 addNodeToTree(nodeItem, node->mChildren[n], tree, nodeCount); 619} 620 621void AssimpShapeLoader::addMetaDataToTree(const aiMetadata* metaData, GuiTreeViewCtrl* tree) 622{ 623 S32 metaItem = tree->insertItem(0, "MetaData", String::ToString("%i", metaData->mNumProperties)); 624 625 aiString valString; 626 aiVector3D valVec; 627 628 for (U32 n = 0; n < metaData->mNumProperties; ++n) 629 { 630 String keyStr = metaData->mKeys[n].C_Str(); 631 keyStr += ": "; 632 switch (metaData->mValues[n].mType) 633 { 634 case AI_BOOL: 635 keyStr += ((bool)metaData->mValues[n].mData) ? "true" : "false"; 636 break; 637 case AI_INT32: 638 keyStr += String::ToString(*((S32*)(metaData->mValues[n].mData))); 639 break; 640 case AI_UINT64: 641 keyStr += String::ToString("%I64u", *((U64*)metaData->mValues[n].mData)); 642 break; 643 case AI_FLOAT: 644 keyStr += String::ToString(*((F32*)metaData->mValues[n].mData)); 645 break; 646 case AI_DOUBLE: 647 keyStr += String::ToString(*((F64*)metaData->mValues[n].mData)); 648 break; 649 case AI_AISTRING: 650 metaData->Get<aiString>(metaData->mKeys[n], valString); 651 keyStr += valString.C_Str(); 652 break; 653 case AI_AIVECTOR3D: 654 metaData->Get<aiVector3D>(metaData->mKeys[n], valVec); 655 keyStr += String::ToString("%f, %f, %f", valVec.x, valVec.y, valVec.z); 656 break; 657 default: 658 break; 659 } 660 661 tree->insertItem(metaItem, keyStr.c_str(), String::ToString("%i", n)); 662 } 663} 664 665bool AssimpShapeLoader::getMetabool(const char* key, bool& boolVal) 666{ 667 if (!mScene || !mScene->mMetaData) 668 return false; 669 670 String keyStr = key; 671 for (U32 n = 0; n < mScene->mMetaData->mNumProperties; ++n) 672 { 673 if (keyStr.equal(mScene->mMetaData->mKeys[n].C_Str(), String::NoCase)) 674 { 675 if (mScene->mMetaData->mValues[n].mType == AI_BOOL) 676 { 677 boolVal = (bool)mScene->mMetaData->mValues[n].mData; 678 return true; 679 } 680 } 681 } 682 return false; 683} 684 685bool AssimpShapeLoader::getMetaInt(const char* key, S32& intVal) 686{ 687 if (!mScene || !mScene->mMetaData) 688 return false; 689 690 String keyStr = key; 691 for (U32 n = 0; n < mScene->mMetaData->mNumProperties; ++n) 692 { 693 if (keyStr.equal(mScene->mMetaData->mKeys[n].C_Str(), String::NoCase)) 694 { 695 if (mScene->mMetaData->mValues[n].mType == AI_INT32) 696 { 697 intVal = *((S32*)(mScene->mMetaData->mValues[n].mData)); 698 return true; 699 } 700 } 701 } 702 return false; 703} 704 705bool AssimpShapeLoader::getMetaFloat(const char* key, F32& floatVal) 706{ 707 if (!mScene || !mScene->mMetaData) 708 return false; 709 710 String keyStr = key; 711 for (U32 n = 0; n < mScene->mMetaData->mNumProperties; ++n) 712 { 713 if (keyStr.equal(mScene->mMetaData->mKeys[n].C_Str(), String::NoCase)) 714 { 715 if (mScene->mMetaData->mValues[n].mType == AI_FLOAT) 716 { 717 floatVal = *((F32*)mScene->mMetaData->mValues[n].mData); 718 return true; 719 } 720 } 721 } 722 return false; 723} 724 725bool AssimpShapeLoader::getMetaDouble(const char* key, F64& doubleVal) 726{ 727 if (!mScene || !mScene->mMetaData) 728 return false; 729 730 String keyStr = key; 731 for (U32 n = 0; n < mScene->mMetaData->mNumProperties; ++n) 732 { 733 if (keyStr.equal(mScene->mMetaData->mKeys[n].C_Str(), String::NoCase)) 734 { 735 if (mScene->mMetaData->mValues[n].mType == AI_DOUBLE) 736 { 737 doubleVal = *((F64*)mScene->mMetaData->mValues[n].mData); 738 return true; 739 } 740 } 741 } 742 return false; 743} 744 745bool AssimpShapeLoader::getMetaString(const char* key, String& stringVal) 746{ 747 if (!mScene || !mScene->mMetaData) 748 return false; 749 750 String keyStr = key; 751 for (U32 n = 0; n < mScene->mMetaData->mNumProperties; ++n) 752 { 753 if (keyStr.equal(mScene->mMetaData->mKeys[n].C_Str(), String::NoCase)) 754 { 755 if (mScene->mMetaData->mValues[n].mType == AI_AISTRING) 756 { 757 aiString valString; 758 mScene->mMetaData->Get<aiString>(mScene->mMetaData->mKeys[n], valString); 759 stringVal = valString.C_Str(); 760 return true; 761 } 762 } 763 } 764 return false; 765} 766//----------------------------------------------------------------------------- 767/// This function is invoked by the resource manager based on file extension. 768TSShape* assimpLoadShape(const Torque::Path &path) 769{ 770 // TODO: add .cached.dts generation. 771 // Generate the cached filename 772 Torque::Path cachedPath(path); 773 cachedPath.setExtension("cached.dts"); 774 775 // Check if an up-to-date cached DTS version of this file exists, and 776 // if so, use that instead. 777 if (AssimpShapeLoader::canLoadCachedDTS(path)) 778 { 779 FileStream cachedStream; 780 cachedStream.open(cachedPath.getFullPath(), Torque::FS::File::Read); 781 if (cachedStream.getStatus() == Stream::Ok) 782 { 783 TSShape *shape = new TSShape; 784 bool readSuccess = shape->read(&cachedStream); 785 cachedStream.close(); 786 787 if (readSuccess) 788 { 789 #ifdef TORQUE_DEBUG 790 Con::printf("Loaded cached shape from %s", cachedPath.getFullPath().c_str()); 791 #endif 792 return shape; 793 } 794 else 795 delete shape; 796 } 797 798 Con::warnf("Failed to load cached shape from %s", cachedPath.getFullPath().c_str()); 799 } 800 801 if (!Torque::FS::IsFile(path)) 802 { 803 // File does not exist, bail. 804 return NULL; 805 } 806 807 // Allow TSShapeConstructor object to override properties 808 ColladaUtils::getOptions().reset(); 809 TSShapeConstructor* tscon = TSShapeConstructor::findShapeConstructor(path.getFullPath()); 810 if (tscon) 811 { 812 ColladaUtils::getOptions() = tscon->mOptions; 813 } 814 815 AssimpShapeLoader loader; 816 TSShape* tss = loader.generateShape(path); 817 if (tss) 818 { 819 TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import complete"); 820 Con::printf("[ASSIMP] Shape created successfully."); 821 822 // Cache the model to a DTS file for faster loading next time. 823 FileStream dtsStream; 824 if (dtsStream.open(cachedPath.getFullPath(), Torque::FS::File::Write)) 825 { 826 Con::printf("Writing cached shape to %s", cachedPath.getFullPath().c_str()); 827 tss->write(&dtsStream); 828 } 829 830 loader.updateMaterialsScript(path); 831 } 832 loader.releaseImport(); 833 return tss; 834} 835 836DefineEngineFunction(GetShapeInfo, bool, (const char* shapePath, const char* ctrl, bool loadCachedDts), ("", "", true), 837 "(string shapePath, GuiTreeViewCtrl ctrl) Collect scene information from " 838 "a shape file and store it in a GuiTreeView control. This function is " 839 "used by the assimp import gui to show a preview of the scene contents " 840 "prior to import, and is probably not much use for anything else.\n" 841 "@param shapePath shape filename\n" 842 "@param ctrl GuiTreeView control to add elements to\n" 843 "@return true if successful, false otherwise\n" 844 "@ingroup Editors\n" 845 "@internal") 846{ 847 GuiTreeViewCtrl* tree; 848 if (!Sim::findObject(ctrl, tree)) 849 { 850 Con::errorf("enumColladaScene::Could not find GuiTreeViewCtrl '%s'", ctrl); 851 return false; 852 } 853 854 // Check if a cached DTS is available => no need to import the source file 855 // if we can load the DTS instead 856 Torque::Path path(shapePath); 857 if (loadCachedDts && AssimpShapeLoader::canLoadCachedDTS(path)) 858 return false; 859 860 AssimpShapeLoader loader; 861 return loader.fillGuiTreeView(shapePath, tree); 862} 863