colladaShapeLoader.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  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/collada/colladaShapeLoader.h"
  31. #include "ts/collada/colladaUtils.h"
  32. #include "ts/collada/colladaAppNode.h"
  33. #include "ts/collada/colladaAppMesh.h"
  34. #include "ts/collada/colladaAppMaterial.h"
  35. #include "ts/collada/colladaAppSequence.h"
  36. #include "core/util/tVector.h"
  37. #include "core/strings/findMatch.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. //
  48. static DAE sDAE; // Collada model database (holds the last loaded file)
  49. static Torque::Path sLastPath; // Path of the last loaded Collada file
  50. static FileTime sLastModTime; // Modification time of the last loaded Collada file
  51. //-----------------------------------------------------------------------------
  52. // Custom warning/error message handler
  53. class myErrorHandler : public daeErrorHandler
  54. {
  55. void handleError( daeString msg )
  56. {
  57. Con::errorf("Error: %s", msg);
  58. }
  59. void handleWarning( daeString msg )
  60. {
  61. Con::errorf("Warning: %s", msg);
  62. }
  63. } sErrorHandler;
  64. //-----------------------------------------------------------------------------
  65. ColladaShapeLoader::ColladaShapeLoader(domCOLLADA* _root)
  66. : root(_root)
  67. {
  68. // Extract the global scale and up_axis from the top level <asset> element,
  69. F32 unit = 1.0f;
  70. domUpAxisType upAxis = UPAXISTYPE_Z_UP;
  71. if (root->getAsset()) {
  72. if (root->getAsset()->getUnit())
  73. unit = root->getAsset()->getUnit()->getMeter();
  74. if (root->getAsset()->getUp_axis())
  75. upAxis = root->getAsset()->getUp_axis()->getValue();
  76. }
  77. // Set import options (if they are not set to override)
  78. if (ColladaUtils::getOptions().unit <= 0.0f)
  79. ColladaUtils::getOptions().unit = unit;
  80. if (ColladaUtils::getOptions().upAxis == UPAXISTYPE_COUNT)
  81. ColladaUtils::getOptions().upAxis = upAxis;
  82. }
  83. ColladaShapeLoader::~ColladaShapeLoader()
  84. {
  85. // Delete all of the animation channels
  86. for (S32 iAnim = 0; iAnim < animations.size(); iAnim++) {
  87. for (S32 iChannel = 0; iChannel < animations[iAnim]->size(); iChannel++)
  88. delete (*animations[iAnim])[iChannel];
  89. delete animations[iAnim];
  90. }
  91. animations.clear();
  92. }
  93. void ColladaShapeLoader::processAnimation(const domAnimation* anim, F32& maxEndTime, F32& minFrameTime)
  94. {
  95. const char* sRGBANames[] = { ".R", ".G", ".B", ".A", "" };
  96. const char* sXYZNames[] = { ".X", ".Y", ".Z", "" };
  97. const char* sXYZANames[] = { ".X", ".Y", ".Z", ".ANGLE" };
  98. const char* sLOOKATNames[] = { ".POSITIONX", ".POSITIONY", ".POSITIONZ", ".TARGETX", ".TARGETY", ".TARGETZ", ".UPX", ".UPY", ".UPZ", "" };
  99. const char* sSKEWNames[] = { ".ROTATEX", ".ROTATEY", ".ROTATEZ", ".AROUNDX", ".AROUNDY", ".AROUNDZ", ".ANGLE", "" };
  100. const char* sNullNames[] = { "" };
  101. for (S32 iChannel = 0; iChannel < anim->getChannel_array().getCount(); iChannel++) {
  102. // Get the animation elements: <channel>, <sampler>
  103. domChannel* channel = anim->getChannel_array()[iChannel];
  104. domSampler* sampler = daeSafeCast<domSampler>(channel->getSource().getElement());
  105. if (!sampler)
  106. continue;
  107. // Find the animation channel target
  108. daeSIDResolver resolver(channel, channel->getTarget());
  109. daeElement* target = resolver.getElement();
  110. if (!target) {
  111. daeErrorHandler::get()->handleWarning(avar("Failed to resolve animation "
  112. "target: %s", channel->getTarget()));
  113. continue;
  114. }
  115. /*
  116. // If the target is a <source>, point it at the array instead
  117. // @todo:Only support targeting float arrays for now...
  118. if (target->getElementType() == COLLADA_TYPE::SOURCE)
  119. {
  120. domSource* source = daeSafeCast<domSource>(target);
  121. if (source->getFloat_array())
  122. target = source->getFloat_array();
  123. }
  124. */
  125. // Get the target's animation channels (create them if not already)
  126. if (!AnimData::getAnimChannels(target)) {
  127. animations.push_back(new AnimChannels(target));
  128. }
  129. AnimChannels* targetChannels = AnimData::getAnimChannels(target);
  130. // Add a new animation channel to the target
  131. targetChannels->push_back(new AnimData());
  132. channel->setUserData(targetChannels->last());
  133. AnimData& data = *targetChannels->last();
  134. for (S32 iInput = 0; iInput < sampler->getInput_array().getCount(); iInput++) {
  135. const domInputLocal* input = sampler->getInput_array()[iInput];
  136. const domSource* source = daeSafeCast<domSource>(input->getSource().getElement());
  137. if (!source)
  138. continue;
  139. // @todo:don't care about the input param names for now. Could
  140. // validate against the target type....
  141. if (dStrEqual(input->getSemantic(), "INPUT")) {
  142. data.mInput.initFromSource(source);
  143. // Adjust the maximum sequence end time
  144. maxEndTime = getMax(maxEndTime, data.mInput.getFloatValue((S32)data.mInput.size()-1));
  145. // Detect the frame rate (minimum time between keyframes)
  146. for (S32 iFrame = 1; iFrame < data.mInput.size(); iFrame++)
  147. {
  148. F32 delta = data.mInput.getFloatValue( iFrame ) - data.mInput.getFloatValue( iFrame-1 );
  149. if ( delta < 0 )
  150. {
  151. daeErrorHandler::get()->handleError(avar("<animation> INPUT '%s' "
  152. "has non-monotonic keys. Animation is unlikely to be imported correctly.", source->getID()));
  153. break;
  154. }
  155. minFrameTime = getMin( minFrameTime, delta );
  156. }
  157. }
  158. else if (dStrEqual(input->getSemantic(), "OUTPUT"))
  159. data.mOutput.initFromSource(source);
  160. else if (dStrEqual(input->getSemantic(), "IN_TANGENT"))
  161. data.mInTangent.initFromSource(source);
  162. else if (dStrEqual(input->getSemantic(), "OUT_TANGENT"))
  163. data.mOutTangent.initFromSource(source);
  164. else if (dStrEqual(input->getSemantic(), "INTERPOLATION"))
  165. data.mInterpolation.initFromSource(source);
  166. }
  167. // Set initial value for visibility targets that were added automatically (in colladaUtils.cpp
  168. if (dStrEqual(target->getElementName(), "visibility"))
  169. {
  170. domAny* visTarget = daeSafeCast<domAny>(target);
  171. if (visTarget && dStrEqual(visTarget->getValue(), ""))
  172. visTarget->setValue(avar("%g", data.mOutput.getFloatValue(0)));
  173. }
  174. // Ignore empty animations
  175. if (data.mInput.size() == 0) {
  176. channel->setUserData(0);
  177. delete targetChannels->last();
  178. targetChannels->pop_back();
  179. continue;
  180. }
  181. // Determine the number and offset the elements of the target value
  182. // targeted by this animation
  183. switch (target->getElementType()) {
  184. case COLLADA_TYPE::COLOR: data.parseTargetString(channel->getTarget(), 4, sRGBANames); break;
  185. case COLLADA_TYPE::TRANSLATE: data.parseTargetString(channel->getTarget(), 3, sXYZNames); break;
  186. case COLLADA_TYPE::ROTATE: data.parseTargetString(channel->getTarget(), 4, sXYZANames); break;
  187. case COLLADA_TYPE::SCALE: data.parseTargetString(channel->getTarget(), 3, sXYZNames); break;
  188. case COLLADA_TYPE::LOOKAT: data.parseTargetString(channel->getTarget(), 3, sLOOKATNames); break;
  189. case COLLADA_TYPE::SKEW: data.parseTargetString(channel->getTarget(), 3, sSKEWNames); break;
  190. case COLLADA_TYPE::MATRIX: data.parseTargetString(channel->getTarget(), 16, sNullNames); break;
  191. case COLLADA_TYPE::FLOAT_ARRAY: data.parseTargetString(channel->getTarget(), daeSafeCast<domFloat_array>(target)->getCount(), sNullNames); break;
  192. default: data.parseTargetString(channel->getTarget(), 1, sNullNames); break;
  193. }
  194. }
  195. // Process child animations
  196. for (S32 iAnim = 0; iAnim < anim->getAnimation_array().getCount(); iAnim++)
  197. processAnimation(anim->getAnimation_array()[iAnim], maxEndTime, minFrameTime);
  198. }
  199. void ColladaShapeLoader::enumerateScene()
  200. {
  201. // Get animation clips
  202. Vector<const domAnimation_clip*> animationClips;
  203. for (S32 iClipLib = 0; iClipLib < root->getLibrary_animation_clips_array().getCount(); iClipLib++) {
  204. const domLibrary_animation_clips* libraryClips = root->getLibrary_animation_clips_array()[iClipLib];
  205. for (S32 iClip = 0; iClip < libraryClips->getAnimation_clip_array().getCount(); iClip++)
  206. appSequences.push_back(new ColladaAppSequence(libraryClips->getAnimation_clip_array()[iClip]));
  207. }
  208. // Process all animations => this attaches animation channels to the targeted
  209. // Collada elements, and determines the length of the sequence if it is not
  210. // already specified in the Collada <animation_clip> element
  211. for (S32 iSeq = 0; iSeq < appSequences.size(); iSeq++) {
  212. ColladaAppSequence* appSeq = dynamic_cast<ColladaAppSequence*>(appSequences[iSeq]);
  213. F32 maxEndTime = 0;
  214. F32 minFrameTime = 1000.0f;
  215. for (S32 iAnim = 0; iAnim < appSeq->getClip()->getInstance_animation_array().getCount(); iAnim++) {
  216. domAnimation* anim = daeSafeCast<domAnimation>(appSeq->getClip()->getInstance_animation_array()[iAnim]->getUrl().getElement());
  217. if (anim)
  218. processAnimation(anim, maxEndTime, minFrameTime);
  219. }
  220. if (appSeq->getEnd() == 0)
  221. appSeq->setEnd(maxEndTime);
  222. // Collada animations can be stored as sampled frames or true keyframes. For
  223. // sampled frames, use the same frame rate as the DAE file. For true keyframes,
  224. // resample at a fixed frame rate.
  225. appSeq->fps = mClamp(1.0f / minFrameTime + 0.5f, TSShapeLoader::MinFrameRate, TSShapeLoader::MaxFrameRate);
  226. }
  227. // First grab all of the top-level nodes
  228. Vector<domNode*> sceneNodes;
  229. for (S32 iSceneLib = 0; iSceneLib < root->getLibrary_visual_scenes_array().getCount(); iSceneLib++) {
  230. const domLibrary_visual_scenes* libScenes = root->getLibrary_visual_scenes_array()[iSceneLib];
  231. for (S32 iScene = 0; iScene < libScenes->getVisual_scene_array().getCount(); iScene++) {
  232. const domVisual_scene* visualScene = libScenes->getVisual_scene_array()[iScene];
  233. for (S32 iNode = 0; iNode < visualScene->getNode_array().getCount(); iNode++)
  234. sceneNodes.push_back(visualScene->getNode_array()[iNode]);
  235. }
  236. }
  237. // Set LOD option
  238. bool singleDetail = true;
  239. switch (ColladaUtils::getOptions().lodType)
  240. {
  241. case ColladaUtils::ImportOptions::DetectDTS:
  242. // Check for a baseXX->startXX hierarchy at the top-level, if we find
  243. // one, use trailing numbers for LOD, otherwise use a single size
  244. for (S32 iNode = 0; singleDetail && (iNode < sceneNodes.size()); iNode++) {
  245. domNode* node = sceneNodes[iNode];
  246. if (dStrStartsWith(_GetNameOrId(node), "base")) {
  247. for (S32 iChild = 0; iChild < node->getNode_array().getCount(); iChild++) {
  248. domNode* child = node->getNode_array()[iChild];
  249. if (dStrStartsWith(_GetNameOrId(child), "start")) {
  250. singleDetail = false;
  251. break;
  252. }
  253. }
  254. }
  255. }
  256. break;
  257. case ColladaUtils::ImportOptions::SingleSize:
  258. singleDetail = true;
  259. break;
  260. case ColladaUtils::ImportOptions::TrailingNumber:
  261. singleDetail = false;
  262. break;
  263. default:
  264. break;
  265. }
  266. ColladaAppMesh::fixDetailSize( singleDetail, ColladaUtils::getOptions().singleDetailSize );
  267. // Process the top level nodes
  268. for (S32 iNode = 0; iNode < sceneNodes.size(); iNode++) {
  269. ColladaAppNode* node = new ColladaAppNode(sceneNodes[iNode], 0);
  270. if (!processNode(node))
  271. delete node;
  272. }
  273. // Make sure that the scene has a bounds node (for getting the root scene transform)
  274. if (!boundsNode)
  275. {
  276. domVisual_scene* visualScene = root->getLibrary_visual_scenes_array()[0]->getVisual_scene_array()[0];
  277. domNode* dombounds = daeSafeCast<domNode>( visualScene->createAndPlace( "node" ) );
  278. dombounds->setName( "bounds" );
  279. ColladaAppNode *appBounds = new ColladaAppNode(dombounds, 0);
  280. if (!processNode(appBounds))
  281. delete appBounds;
  282. }
  283. }
  284. bool ColladaShapeLoader::ignoreNode(const String& name)
  285. {
  286. if (FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().alwaysImport, name, false))
  287. return false;
  288. else
  289. return FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().neverImport, name, false);
  290. }
  291. bool ColladaShapeLoader::ignoreMesh(const String& name)
  292. {
  293. if (FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().alwaysImportMesh, name, false))
  294. return false;
  295. else
  296. return FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().neverImportMesh, name, false);
  297. }
  298. void ColladaShapeLoader::computeBounds(Box3F& bounds)
  299. {
  300. TSShapeLoader::computeBounds(bounds);
  301. // Check if the model origin needs adjusting
  302. if ( bounds.isValidBox() &&
  303. (ColladaUtils::getOptions().adjustCenter ||
  304. ColladaUtils::getOptions().adjustFloor) )
  305. {
  306. // Compute shape offset
  307. Point3F shapeOffset = Point3F::Zero;
  308. if ( ColladaUtils::getOptions().adjustCenter )
  309. {
  310. bounds.getCenter( &shapeOffset );
  311. shapeOffset = -shapeOffset;
  312. }
  313. if ( ColladaUtils::getOptions().adjustFloor )
  314. shapeOffset.z = -bounds.minExtents.z;
  315. // Adjust bounds
  316. bounds.minExtents += shapeOffset;
  317. bounds.maxExtents += shapeOffset;
  318. // Now adjust all positions for root level nodes (nodes with no parent)
  319. for (S32 iNode = 0; iNode < shape->nodes.size(); iNode++)
  320. {
  321. if ( !appNodes[iNode]->isParentRoot() )
  322. continue;
  323. // Adjust default translation
  324. shape->defaultTranslations[iNode] += shapeOffset;
  325. // Adjust animated translations
  326. for (S32 iSeq = 0; iSeq < shape->sequences.size(); iSeq++)
  327. {
  328. const TSShape::Sequence& seq = shape->sequences[iSeq];
  329. if ( seq.translationMatters.test(iNode) )
  330. {
  331. for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
  332. {
  333. S32 index = seq.baseTranslation + seq.translationMatters.count(iNode)*seq.numKeyframes + iFrame;
  334. shape->nodeTranslations[index] += shapeOffset;
  335. }
  336. }
  337. }
  338. }
  339. }
  340. }
  341. //-----------------------------------------------------------------------------
  342. /// Find the file extension for an extensionless texture
  343. String findTextureExtension(const Torque::Path &texPath)
  344. {
  345. Torque::Path path(texPath);
  346. for(S32 i = 0;i < GBitmap::sRegistrations.size();++i)
  347. {
  348. GBitmap::Registration &reg = GBitmap::sRegistrations[i];
  349. for(S32 j = 0;j < reg.extensions.size();++j)
  350. {
  351. path.setExtension(reg.extensions[j]);
  352. if (Torque::FS::IsFile(path))
  353. return path.getExtension();
  354. }
  355. }
  356. return String();
  357. }
  358. //-----------------------------------------------------------------------------
  359. /// Copy a texture from a KMZ to a cache. Note that the texture filename is modified
  360. void copySketchupTexture(const Torque::Path &path, String &textureFilename)
  361. {
  362. if (textureFilename.isEmpty())
  363. return;
  364. Torque::Path texturePath(textureFilename);
  365. texturePath.setExtension(findTextureExtension(texturePath));
  366. String cachedTexFilename = String::ToString("%s_%s.cached",
  367. TSShapeLoader::getShapePath().getFileName().c_str(), texturePath.getFileName().c_str());
  368. Torque::Path cachedTexPath;
  369. cachedTexPath.setRoot(path.getRoot());
  370. cachedTexPath.setPath(path.getPath());
  371. cachedTexPath.setFileName(cachedTexFilename);
  372. cachedTexPath.setExtension(texturePath.getExtension());
  373. FileStream *source;
  374. FileStream *dest;
  375. if ((source = FileStream::createAndOpen(texturePath.getFullPath(), Torque::FS::File::Read)) == NULL)
  376. return;
  377. if ((dest = FileStream::createAndOpen(cachedTexPath.getFullPath(), Torque::FS::File::Write)) == NULL)
  378. {
  379. delete source;
  380. return;
  381. }
  382. dest->copyFrom(source);
  383. delete dest;
  384. delete source;
  385. // Update the filename in the material
  386. cachedTexPath.setExtension("");
  387. textureFilename = cachedTexPath.getFullPath();
  388. }
  389. //-----------------------------------------------------------------------------
  390. /// Add collada materials to materials.cs
  391. void updateMaterialsScript(const Torque::Path &path, bool copyTextures = false)
  392. {
  393. #ifdef DAE2DTS_TOOL
  394. if (!ColladaUtils::getOptions().forceUpdateMaterials)
  395. return;
  396. #endif
  397. Torque::Path scriptPath(path);
  398. scriptPath.setFileName("materials");
  399. scriptPath.setExtension("cs");
  400. // First see what materials we need to update
  401. PersistenceManager persistMgr;
  402. for ( U32 iMat = 0; iMat < AppMesh::mAppMaterials.size(); iMat++ )
  403. {
  404. ColladaAppMaterial *mat = dynamic_cast<ColladaAppMaterial*>( AppMesh::mAppMaterials[iMat] );
  405. if ( mat )
  406. {
  407. Material *mappedMat;
  408. if ( Sim::findObject( MATMGR->getMapEntry( mat->getName() ), mappedMat ) )
  409. {
  410. // Only update existing materials if forced to
  411. if ( ColladaUtils::getOptions().forceUpdateMaterials )
  412. persistMgr.setDirty( mappedMat );
  413. }
  414. else
  415. {
  416. // Create a new material definition
  417. persistMgr.setDirty( mat->createMaterial( scriptPath ), scriptPath.getFullPath() );
  418. }
  419. }
  420. }
  421. if ( persistMgr.getDirtyList().empty() )
  422. return;
  423. // If importing a sketchup file, the paths will point inside the KMZ so we need to cache them.
  424. if (copyTextures)
  425. {
  426. for (S32 iMat = 0; iMat < persistMgr.getDirtyList().size(); iMat++)
  427. {
  428. Material *mat = dynamic_cast<Material*>( persistMgr.getDirtyList()[iMat].getObject() );
  429. copySketchupTexture(path, mat->mDiffuseMapFilename[0]);
  430. copySketchupTexture(path, mat->mNormalMapFilename[0]);
  431. copySketchupTexture(path, mat->mSpecularMapFilename[0]);
  432. }
  433. }
  434. persistMgr.saveDirty();
  435. }
  436. //-----------------------------------------------------------------------------
  437. /// Check if an up-to-date cached DTS is available for this DAE file
  438. bool ColladaShapeLoader::canLoadCachedDTS(const Torque::Path& path)
  439. {
  440. // Generate the cached filename
  441. Torque::Path cachedPath(path);
  442. cachedPath.setExtension("cached.dts");
  443. // Check if a cached DTS newer than this file is available
  444. FileTime cachedModifyTime;
  445. if (Platform::getFileTimes(cachedPath.getFullPath(), NULL, &cachedModifyTime))
  446. {
  447. bool forceLoadDAE = Con::getBoolVariable("$collada::forceLoadDAE", false);
  448. FileTime daeModifyTime;
  449. if (!Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime) ||
  450. (!forceLoadDAE && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0) ))
  451. {
  452. // DAE not found, or cached DTS is newer
  453. return true;
  454. }
  455. }
  456. return false;
  457. }
  458. bool ColladaShapeLoader::checkAndMountSketchup(const Torque::Path& path, String& mountPoint, Torque::Path& daePath)
  459. {
  460. bool isSketchup = path.getExtension().equal("kmz", String::NoCase);
  461. if (isSketchup)
  462. {
  463. // Mount the zip so files can be found (it will be unmounted before we return)
  464. mountPoint = String("sketchup_") + path.getFileName();
  465. String zipPath = path.getFullPath();
  466. if (!Torque::FS::Mount(mountPoint, new Torque::ZipFileSystem(zipPath)))
  467. return false;
  468. Vector<String> daeFiles;
  469. Torque::Path findPath;
  470. findPath.setRoot(mountPoint);
  471. S32 results = Torque::FS::FindByPattern(findPath, "*.dae", true, daeFiles);
  472. if (results == 0 || daeFiles.size() == 0)
  473. {
  474. Torque::FS::Unmount(mountPoint);
  475. return false;
  476. }
  477. daePath = daeFiles[0];
  478. }
  479. else
  480. {
  481. daePath = path;
  482. }
  483. return isSketchup;
  484. }
  485. //-----------------------------------------------------------------------------
  486. /// Get the root collada DOM element for the given DAE file
  487. domCOLLADA* ColladaShapeLoader::getDomCOLLADA(const Torque::Path& path)
  488. {
  489. daeErrorHandler::setErrorHandler(&sErrorHandler);
  490. TSShapeLoader::updateProgress(TSShapeLoader::Load_ReadFile, path.getFullFileName().c_str());
  491. // Check if we can use the last loaded file
  492. FileTime daeModifyTime;
  493. if (Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime))
  494. {
  495. if ((path == sLastPath) && (Platform::compareFileTimes(sLastModTime, daeModifyTime) >= 0))
  496. return sDAE.getRoot(path.getFullPath().c_str());
  497. }
  498. sDAE.clear();
  499. sDAE.setBaseURI("");
  500. TSShapeLoader::updateProgress(TSShapeLoader::Load_ParseFile, "Parsing XML...");
  501. domCOLLADA* root = readColladaFile(path.getFullPath());
  502. if (!root)
  503. {
  504. TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import failed");
  505. sDAE.clear();
  506. return NULL;
  507. }
  508. sLastPath = path;
  509. sLastModTime = daeModifyTime;
  510. return root;
  511. }
  512. domCOLLADA* ColladaShapeLoader::readColladaFile(const String& path)
  513. {
  514. // Check if this file is already loaded into the database
  515. domCOLLADA* root = sDAE.getRoot(path.c_str());
  516. if (root)
  517. return root;
  518. // Load the Collada file into memory
  519. FileObject fo;
  520. if (!fo.readMemory(path))
  521. {
  522. daeErrorHandler::get()->handleError(avar("Could not read %s into memory", path.c_str()));
  523. return NULL;
  524. }
  525. root = sDAE.openFromMemory(path.c_str(), (const char*)fo.buffer());
  526. if (!root || !root->getLibrary_visual_scenes_array().getCount()) {
  527. daeErrorHandler::get()->handleError(avar("Could not parse %s", path.c_str()));
  528. return NULL;
  529. }
  530. // Fixup issues in the model
  531. ColladaUtils::applyConditioners(root);
  532. // Recursively load external DAE references
  533. TSShapeLoader::updateProgress(TSShapeLoader::Load_ExternalRefs, "Loading external references...");
  534. for (S32 iRef = 0; iRef < root->getDocument()->getReferencedDocuments().getCount(); iRef++) {
  535. String refPath = (daeString)root->getDocument()->getReferencedDocuments()[iRef];
  536. if (refPath.endsWith(".dae") && !readColladaFile(refPath))
  537. daeErrorHandler::get()->handleError(avar("Failed to load external reference: %s", refPath.c_str()));
  538. }
  539. return root;
  540. }
  541. //-----------------------------------------------------------------------------
  542. /// This function is invoked by the resource manager based on file extension.
  543. TSShape* loadColladaShape(const Torque::Path &path)
  544. {
  545. #ifndef DAE2DTS_TOOL
  546. // Generate the cached filename
  547. Torque::Path cachedPath(path);
  548. cachedPath.setExtension("cached.dts");
  549. // Check if an up-to-date cached DTS version of this file exists, and
  550. // if so, use that instead.
  551. if (ColladaShapeLoader::canLoadCachedDTS(path))
  552. {
  553. FileStream cachedStream;
  554. cachedStream.open(cachedPath.getFullPath(), Torque::FS::File::Read);
  555. if (cachedStream.getStatus() == Stream::Ok)
  556. {
  557. TSShape *shape = new TSShape;
  558. bool readSuccess = shape->read(&cachedStream);
  559. cachedStream.close();
  560. if (readSuccess)
  561. {
  562. #ifdef TORQUE_DEBUG
  563. Con::printf("Loaded cached Collada shape from %s", cachedPath.getFullPath().c_str());
  564. #endif
  565. return shape;
  566. }
  567. else
  568. delete shape;
  569. }
  570. Con::warnf("Failed to load cached COLLADA shape from %s", cachedPath.getFullPath().c_str());
  571. }
  572. #endif // DAE2DTS_TOOL
  573. if (!Torque::FS::IsFile(path))
  574. {
  575. // DAE file does not exist, bail.
  576. return NULL;
  577. }
  578. #ifdef DAE2DTS_TOOL
  579. ColladaUtils::ImportOptions cmdLineOptions = ColladaUtils::getOptions();
  580. #endif
  581. // Allow TSShapeConstructor object to override properties
  582. ColladaUtils::getOptions().reset();
  583. TSShapeConstructor* tscon = TSShapeConstructor::findShapeConstructor(path.getFullPath());
  584. if (tscon)
  585. {
  586. ColladaUtils::getOptions() = tscon->mOptions;
  587. #ifdef DAE2DTS_TOOL
  588. // Command line overrides certain options
  589. ColladaUtils::getOptions().forceUpdateMaterials = cmdLineOptions.forceUpdateMaterials;
  590. ColladaUtils::getOptions().useDiffuseNames = cmdLineOptions.useDiffuseNames;
  591. #endif
  592. }
  593. // Check if this is a Sketchup file (.kmz) and if so, mount the zip filesystem
  594. // and get the path to the DAE file.
  595. String mountPoint;
  596. Torque::Path daePath;
  597. bool isSketchup = ColladaShapeLoader::checkAndMountSketchup(path, mountPoint, daePath);
  598. // Load Collada model and convert to 3space
  599. TSShape* tss = 0;
  600. domCOLLADA* root = ColladaShapeLoader::getDomCOLLADA(daePath);
  601. if (root)
  602. {
  603. ColladaShapeLoader loader(root);
  604. tss = loader.generateShape(daePath);
  605. if (tss)
  606. {
  607. #ifndef DAE2DTS_TOOL
  608. // Cache the Collada model to a DTS file for faster loading next time.
  609. FileStream dtsStream;
  610. if (dtsStream.open(cachedPath.getFullPath(), Torque::FS::File::Write))
  611. {
  612. Con::printf("Writing cached COLLADA shape to %s", cachedPath.getFullPath().c_str());
  613. tss->write(&dtsStream);
  614. }
  615. #endif // DAE2DTS_TOOL
  616. // Add collada materials to materials.cs
  617. updateMaterialsScript(path, isSketchup);
  618. }
  619. }
  620. // Close progress dialog
  621. TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import complete");
  622. if (isSketchup)
  623. {
  624. // Unmount the zip if we mounted it
  625. Torque::FS::Unmount(mountPoint);
  626. }
  627. return tss;
  628. }