colladaShapeLoader.cpp
Engine/source/ts/collada/colladaShapeLoader.cpp
Classes:
class
Public Variables
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.
TSShape *
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.
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 ® = 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