Torque3D Documentation / _generateds / assimpShapeLoader.cpp

assimpShapeLoader.cpp

Engine/source/ts/assimp/assimpShapeLoader.cpp

More...

Public Variables

Public Functions

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