assimpShapeLoader.cpp 35 KB

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