Torque3D Documentation / _generateds / colladaShapeLoader.cpp

colladaShapeLoader.cpp

Engine/source/ts/collada/colladaShapeLoader.cpp

More...

Classes:

Public Functions

copySketchupTexture(const Torque::Path & path, String & textureFilename)

Copy a texture from a KMZ to a cache. Note that the texture filename is modified.

Find the file extension for an extensionless texture.

This function is invoked by the resource manager based on file extension.

updateMaterialsScript(const Torque::Path & path, bool copyTextures)

Add collada materials to materials.tscript.

Detailed Description

Public Variables

 MODULE_END 
 MODULE_INIT 
DAE sDAE 
myErrorHandler sErrorHandler 
FileTime sLastModTime 
Torque::Path sLastPath 

Public Functions

copySketchupTexture(const Torque::Path & path, String & textureFilename)

Copy a texture from a KMZ to a cache. Note that the texture filename is modified.

findTextureExtension(const Torque::Path & texPath)

Find the file extension for an extensionless texture.

loadColladaShape(const Torque::Path & path)

This function is invoked by the resource manager based on file extension.

updateMaterialsScript(const Torque::Path & path, bool copyTextures)

Add collada materials to materials.tscript.

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