colladaShapeLoader.cpp 28 KB

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