colladaAppMesh.cpp
Engine/source/ts/collada/colladaAppMesh.cpp
Classes:
class
Namespaces:
namespace
Public Defines
define
GET_VIS(node) (dynamic_cast< *>(node)->nodeExt->visibility.getValue())
define
IS_VIS_ANIMATED(node) (dynamic_cast< *>(node)->nodeExt->visibility.isAnimated(appSeq->getStart(), appSeq->getEnd()))
Public Functions
daeElement *
findInputSource(const daeElement * input)
Detailed Description
Public Defines
GET_VIS(node) (dynamic_cast< *>(node)->nodeExt->visibility.getValue())
IS_VIS_ANIMATED(node) (dynamic_cast< *>(node)->nodeExt->visibility.isAnimated(appSeq->getStart(), appSeq->getEnd()))
Public Typedefs
typedef Map< VertTuple, S32 > VertTupleMap
Public Functions
findInputSource(const daeElement * input)
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#include "platform/platform.h" 25 26// Make GCC happy. Needs to have seen this before processing the 27// hash table template. 28struct VertTuple; 29namespace DictHash 30{ 31 inline U32 hash( const VertTuple& data ); 32} 33 34#include "ts/collada/colladaExtensions.h" 35#include "ts/collada/colladaAppMesh.h" 36#include "ts/collada/colladaAppNode.h" 37#include "ts/collada/colladaAppMaterial.h" 38 39#include "core/util/tDictionary.h" 40#include "core/stringTable.h" 41 42using namespace ColladaUtils; 43 44bool ColladaAppMesh::fixedSizeEnabled = false; 45S32 ColladaAppMesh::fixedSize = 2; 46 47//----------------------------------------------------------------------------- 48// Define a VertTuple dictionary to allow fast tuple lookups 49namespace DictHash 50{ 51 inline U32 hash(const VertTuple& data) 52 { 53 return (U32)data.vertex; 54 } 55} 56 57typedef Map<VertTuple, S32> VertTupleMap; 58 59//----------------------------------------------------------------------------- 60// Find a source with matching ID. Cannot use the DOM .getElement method since 61// some lame Collada exporters generate <source>s with non-unique IDs. 62daeElement* findInputSource(const daeElement* input) 63{ 64 // Try using the DOM .getElement method => the resolved element's parent 65 // should be the input's grandparent 66 daeElement* parent = ((daeElement*)input)->getParentElement(); 67 daeElement* grandparent = parent ? parent->getParentElement() : 0; 68 if (!grandparent) 69 return NULL; 70 71 const domURIFragmentType* uri = 0; 72 if (input->getElementType() == COLLADA_TYPE::INPUTLOCAL) 73 uri = &daeSafeCast<domInputLocal>((daeElement*)input)->getSource(); 74 else if (input->getElementType() == COLLADA_TYPE::INPUTLOCALOFFSET) 75 uri = &daeSafeCast<domInputLocalOffset>((daeElement*)input)->getSource(); 76 if (!uri) 77 return NULL; 78 79 daeElement* element = uri->getElement(); 80 if (element && element->getParentElement() == grandparent) 81 return element; 82 else 83 { 84 // Probably a non-unique ID => search for the matching element manually 85 86 // Skip the leading '#' on source IDs 87 const char* id = uri->originalStr().c_str(); 88 if (id && (id[0] == '#')) 89 id++; 90 91 for (S32 iChild = 0; iChild < grandparent->getChildren().getCount(); iChild++) 92 { 93 element = grandparent->getChildren()[iChild]; 94 if ((element->getElementType() != COLLADA_TYPE::SOURCE) && 95 (element->getElementType() != COLLADA_TYPE::VERTICES)) 96 continue; 97 98 if (dStrEqual(id, element->getAttribute("id").c_str())) 99 return element; 100 } 101 } 102 return NULL; 103} 104 105//----------------------------------------------------------------------------- 106// Collada scatters the data required for geometry all over the place; this class 107// helps to group it all together. 108class MeshStreams 109{ 110public: 111 // The sources we want to read from the mesh stream. Can be any order, but 112 // sources of the same type (eg. UVs and UV2s) must be sequential (to allow 113 // ordering by set index) 114 enum eSourceType { 115 Points, 116 Normals, 117 Colors, 118 UVs, 119 UV2s, 120 Joints, 121 Weights, 122 InvBindMatrices, 123 NumStreams 124 }; 125 126 const char* SourceTypeToSemantic(eSourceType type) 127 { 128 switch ( type ) 129 { 130 case Points: return "POSITION"; 131 case Normals: return "NORMAL"; 132 case Colors: return "COLOR"; 133 case UVs: 134 case UV2s: return "TEXCOORD"; 135 case Joints: return "JOINT"; 136 case Weights: return "WEIGHT"; 137 case InvBindMatrices: return "INV_BIND_MATRIX"; 138 default: return ""; 139 } 140 } 141 142private: 143 /// Classify a single input 144 template<class T> 145 static void selectInput(T input, T sortedInputs[], S32 start, S32 end=-1) 146 { 147 if (end == -1) 148 end = start; 149 150 // Get the set for this input 151 const domInputLocalOffset* localOffset = daeSafeCast<domInputLocalOffset>(input); 152 domUint newSet = localOffset ? localOffset->getSet() : 0; 153 154 // Add the input to the right place in the list (somewhere between start and end) 155 for (S32 i = start; i <= end; i++) { 156 localOffset = daeSafeCast<domInputLocalOffset>(sortedInputs[i]); 157 domUint set = localOffset ? localOffset->getSet() : 0xFFFFFFFF; 158 if (newSet < set) { 159 for (S32 j = i + 1; j <= end; j++) 160 sortedInputs[j] = sortedInputs[j-1]; 161 sortedInputs[i] = input; 162 return; 163 } 164 } 165 } 166 167 /// Attempt to initialise a _SourceReader 168 template<class T> 169 bool initSourceReader(T input, eSourceType type, _SourceReader& reader, const char* params[]) 170 { 171 if (!input) 172 return false; 173 174 // Try to get the source element 175 const domSource* source = 0; 176 daeElement *element = findInputSource(input); 177 if (element->getElementType() == COLLADA_TYPE::SOURCE) 178 source = daeSafeCast<domSource>(element); 179 else if (element->getElementType() == COLLADA_TYPE::VERTICES) { 180 const domVertices* vertices = daeSafeCast<domVertices>(element); 181 // Search for the input with the desired semantic 182 const char* semantic = SourceTypeToSemantic( type ); 183 for (S32 iInput = 0; iInput < vertices->getInput_array().getCount(); iInput++) 184 { 185 domInputLocal* vInput = vertices->getInput_array().get(iInput); 186 if (dStrEqual(vInput->getSemantic(), semantic)) 187 { 188 source = daeSafeCast<domSource>(findInputSource(vInput)); 189 break; 190 } 191 } 192 } 193 if (!source) 194 return false; 195 196 return reader.initFromSource(source, params); 197 } 198 199public: 200 201 _SourceReader points; 202 _SourceReader normals; 203 _SourceReader colors; 204 _SourceReader uvs; 205 _SourceReader uv2s; 206 207 _SourceReader joints; 208 _SourceReader weights; 209 _SourceReader invBindMatrices; 210 211 /// Clear the mesh streams 212 void reset() 213 { 214 points.reset(); 215 normals.reset(); 216 colors.reset(); 217 uvs.reset(); 218 uv2s.reset(); 219 joints.reset(); 220 weights.reset(); 221 invBindMatrices.reset(); 222 } 223 224 /// Classify a set of inputs by type and set number (needs to be a template 225 /// because Collada has two forms of input arrays that may be accessed in 226 /// an identical fashion, but the classes are unrelated. Sigh. 227 template<class T> 228 static void classifyInputs(const daeTArray<T>& inputs, T sortedInputs[], U32 *maxOffset=0) 229 { 230 if (maxOffset) 231 *maxOffset = 0; 232 233 // Clear output array 234 for (S32 i = 0; i < NumStreams; i++) 235 sortedInputs[i] = 0; 236 237 // Separate inputs by type, and sort by set (ie. lowest TEXCOORD set becomes UV, 238 // next TEXCOORD set becomes UV2 etc) 239 for (S32 iInput = 0; iInput < inputs.getCount(); iInput++) { 240 241 const T& input = inputs[iInput]; 242 const daeString semantic = input->getSemantic(); 243 244 if (dStrEqual(semantic, "VERTEX")) 245 { 246 domVertices* vertices = daeSafeCast<domVertices>(findInputSource(input)); 247 248 // The <vertices> element may contain multiple inputs (eg. POSITION, NORMAL etc) 249 domInputLocalRef verticesInputs[NumStreams]; 250 classifyInputs(vertices->getInput_array(), verticesInputs); 251 for (S32 iStream = 0; iStream < NumStreams; iStream++) 252 { 253 if (verticesInputs[iStream] != 0) 254 sortedInputs[iStream] = input; 255 } 256 } 257 else if (dStrEqual(semantic, "POSITION")) selectInput(input, sortedInputs, Points); 258 else if (dStrEqual(semantic, "NORMAL")) selectInput(input, sortedInputs, Normals); 259 else if (dStrEqual(semantic, "COLOR")) selectInput(input, sortedInputs, Colors); 260 else if (dStrEqual(semantic, "TEXCOORD")) selectInput(input, sortedInputs, UVs, UV2s); 261 else if (dStrEqual(semantic, "JOINT")) selectInput(input, sortedInputs, Joints); 262 else if (dStrEqual(semantic, "WEIGHT")) selectInput(input, sortedInputs, Weights); 263 else if (dStrEqual(semantic, "INV_BIND_MATRIX")) selectInput(input, sortedInputs, InvBindMatrices); 264 265 if (maxOffset) 266 { 267 const domInputLocalOffset* localOffset = daeSafeCast<domInputLocalOffset>(input); 268 domUint offset = localOffset ? localOffset->getOffset() : 0; 269 if (offset > (*maxOffset)) 270 *maxOffset = offset; 271 } 272 } 273 } 274 275 /// Read a set of inputs into the named sources. There may be multiple 'sets' 276 /// of COLOR or TEXCOORD (uvs) streams, but we are only interested in the 277 /// first COLOR set (ie. smallest set value), and the first 2 TEXCOORDS sets. 278 template<class T> 279 bool readInputs(const daeTArray<T>& inputs) 280 { 281 // Sort inputs by type and set to find the ones we are interested in 282 T sortedInputs[NumStreams]; 283 classifyInputs(inputs, sortedInputs); 284 285 // Attempt to initialise the SourceReaders 286 const char* vertex_params[] = { "X", "Y", "Z", "" }; 287 initSourceReader(sortedInputs[Points], Points, points, vertex_params); 288 289 const char* normal_params[] = { "X", "Y", "Z", "" }; 290 initSourceReader(sortedInputs[Normals], Normals, normals, normal_params); 291 292 const char* color_params[] = { "R", "G", "B", "A", "" }; 293 initSourceReader(sortedInputs[Colors], Colors, colors, color_params); 294 295 const char* uv_params[] = { "S", "T", "" }; 296 const char* uv_params2[] = { "U", "V", "" }; // some files use the nonstandard U,V or X,Y param names 297 const char* uv_params3[] = { "X", "Y", "" }; 298 if (!initSourceReader(sortedInputs[UVs], UVs, uvs, uv_params)) 299 if (!initSourceReader(sortedInputs[UVs], UVs, uvs, uv_params2)) 300 initSourceReader(sortedInputs[UVs], UVs, uvs, uv_params3); 301 if (!initSourceReader(sortedInputs[UV2s], UV2s, uv2s, uv_params)) 302 if (!initSourceReader(sortedInputs[UV2s], UV2s, uv2s, uv_params2)) 303 initSourceReader(sortedInputs[UV2s], UV2s, uv2s, uv_params3); 304 305 const char* joint_params[] = { "JOINT", "" }; 306 initSourceReader(sortedInputs[Joints], Joints, joints, joint_params); 307 308 const char* weight_params[] = { "WEIGHT", "" }; 309 initSourceReader(sortedInputs[Weights], Weights, weights, weight_params); 310 311 const char* matrix_params[] = { "TRANSFORM", "" }; 312 initSourceReader(sortedInputs[InvBindMatrices], InvBindMatrices, invBindMatrices, matrix_params); 313 314 return true; 315 } 316}; 317 318//------------------------------------------------------------------------------ 319 320ColladaAppMesh::ColladaAppMesh(const domInstance_geometry* instance, ColladaAppNode* node) 321 : appNode(node),instanceGeom(instance), instanceCtrl(0), geomExt(0) 322{ 323 flags = 0; 324 numFrames = 0; 325 numMatFrames = 0; 326} 327 328ColladaAppMesh::ColladaAppMesh(const domInstance_controller* instance, ColladaAppNode* node) 329 : appNode(node),instanceGeom(0), instanceCtrl(instance), geomExt(0) 330{ 331 flags = 0; 332 numFrames = 0; 333 numMatFrames = 0; 334} 335 336const char* ColladaAppMesh::getName(bool allowFixed) 337{ 338 // Some exporters add a 'PIVOT' or unnamed node between the mesh and the 339 // actual object node. Detect this and return the object node name instead 340 // of the pivot node. 341 const char* nodeName = appNode->getName(); 342 if ( dStrEqual(nodeName, "null") || dStrEndsWith(nodeName, "PIVOT") ) 343 nodeName = appNode->getParentName(); 344 345 // If all geometry is being fixed to the same size, append the size 346 // to the name 347 return allowFixed && fixedSizeEnabled ? avar("%s %d", nodeName, fixedSize) : nodeName; 348} 349 350MatrixF ColladaAppMesh::getMeshTransform(F32 time) 351{ 352 return appNode->getNodeTransform(time); 353} 354 355bool ColladaAppMesh::animatesVis(const AppSequence* appSeq) 356{ 357 #define IS_VIS_ANIMATED(node) \ 358 (dynamic_cast<const ColladaAppNode*>(node)->nodeExt->visibility.isAnimated(appSeq->getStart(), appSeq->getEnd())) 359 360 // Check if the node visibility is animated within the sequence interval 361 return IS_VIS_ANIMATED(appNode) || (appNode->appParent ? IS_VIS_ANIMATED(appNode->appParent) : false); 362} 363 364bool ColladaAppMesh::animatesMatFrame(const AppSequence* appSeq) 365{ 366 // Texture coordinates may be animated in two ways: 367 // - by animating the MAYA profile texture transform (diffuse texture) 368 // - by animating the morph weights for morph targets with different UVs 369 370 // Check if the MAYA profile texture transform is animated 371 for (S32 iMat = 0; iMat < appMaterials.size(); iMat++) { 372 ColladaAppMaterial* appMat = static_cast<ColladaAppMaterial*>(appMaterials[iMat]); 373 if (appMat->effectExt && 374 appMat->effectExt->animatesTextureTransform(appSeq->getStart(), appSeq->getEnd())) 375 return true; 376 } 377 378 // Check that the morph weights are animated within the sequence interval, 379 // and that the morph targets have different UVs to the base geometry. 380 bool animated = false; 381 bool differentUVs = false; 382 if (const domMorph* morph = getMorph()) { 383 for (S32 iInput = 0; iInput < morph->getTargets()->getInput_array().getCount(); iInput++) { 384 const domInputLocal* input = morph->getTargets()->getInput_array()[iInput]; 385 if (dStrEqual(input->getSemantic(), "MORPH_TARGET")) { 386 // @todo: Check if morph targets have different UVs to base geometry 387 differentUVs = false; 388 } 389 if (dStrEqual(input->getSemantic(), "MORPH_WEIGHT")) { 390 const domSource* source = daeSafeCast<domSource>(findInputSource(input)); 391 AnimatedFloatList weights(source ? source->getFloat_array() : 0); 392 animated = weights.isAnimated(appSeq->getStart(), appSeq->getEnd()); 393 } 394 } 395 } 396 397 return (animated && differentUVs); 398} 399 400bool ColladaAppMesh::animatesFrame(const AppSequence* appSeq) 401{ 402 // Collada <morph>s ALWAYS contain vert positions, so just need to check if 403 // the morph weights are animated within the sequence interval 404 bool animated = false; 405 if (const domMorph* morph = getMorph()) { 406 for (S32 iInput = 0; iInput < morph->getTargets()->getInput_array().getCount(); iInput++) { 407 const domInputLocal* input = morph->getTargets()->getInput_array()[iInput]; 408 if (dStrEqual(input->getSemantic(), "MORPH_WEIGHT")) { 409 const domSource* source = daeSafeCast<domSource>(findInputSource(input)); 410 AnimatedFloatList weights(source ? source->getFloat_array() : 0); 411 animated = weights.isAnimated(appSeq->getStart(), appSeq->getEnd()); 412 break; 413 } 414 } 415 } 416 return animated; 417} 418 419F32 ColladaAppMesh::getVisValue(F32 t) 420{ 421 #define GET_VIS(node) \ 422 (dynamic_cast<const ColladaAppNode*>(node)->nodeExt->visibility.getValue(t)) 423 424 // Get the visibility of the mesh's node at time, 't' 425 return GET_VIS(appNode) * (appNode->appParent ? GET_VIS(appNode->appParent) : 1.0f); 426} 427 428S32 ColladaAppMesh::addMaterial(const char* symbol) 429{ 430 if (!symbol) 431 return TSDrawPrimitive::NoMaterial; 432 433 // Lookup the symbol in the materials already bound to this geometry/controller 434 // instance 435 Map<StringTableEntry,U32>::Iterator itr = boundMaterials.find(symbol); 436 if (itr != boundMaterials.end()) 437 return itr->value; 438 439 // Find the Collada material that this symbol maps to 440 U32 matIndex = TSDrawPrimitive::NoMaterial; 441 const domBind_material* binds = instanceGeom ? instanceGeom->getBind_material() : 442 instanceCtrl->getBind_material(); 443 if (binds) { 444 const domInstance_material_Array& matArray = binds->getTechnique_common()->getInstance_material_array(); 445 for (S32 iBind = 0; iBind < matArray.getCount(); iBind++) { 446 if (dStrEqual(matArray[iBind]->getSymbol(), symbol)) { 447 448 // Find the index of the bound material in the shape global list 449 const domMaterial* mat = daeSafeCast<domMaterial>(matArray[iBind]->getTarget().getElement()); 450 for (matIndex = 0; matIndex < appMaterials.size(); matIndex++) { 451 if (static_cast<ColladaAppMaterial*>(appMaterials[matIndex])->mat == mat) 452 break; 453 } 454 455 // Check if this material needs to be added to the shape global list 456 if (matIndex == appMaterials.size()) { 457 if (mat) 458 appMaterials.push_back(new ColladaAppMaterial(mat)); 459 else 460 appMaterials.push_back(new ColladaAppMaterial(symbol)); 461 } 462 463 break; 464 } 465 } 466 } 467 else 468 { 469 // No Collada material is present for this symbol, so just create an empty one 470 appMaterials.push_back(new ColladaAppMaterial(symbol)); 471 } 472 473 // Add this symbol to the bound list for the mesh 474 boundMaterials.insert(StringTable->insert(symbol), matIndex); 475 return matIndex; 476} 477 478void ColladaAppMesh::getPrimitives(const domGeometry* geometry) 479{ 480 // Only do this once 481 if (primitives.size()) 482 return; 483 484 // Read the <geometry> extension 485 if (!geomExt) 486 geomExt = new ColladaExtension_geometry(geometry); 487 488 // Get the supported primitive elements for this geometry, and warn 489 // about unsupported elements 490 Vector<BasePrimitive*> meshPrims; 491 const daeElementRefArray& contents = geometry->getMesh()->getContents(); 492 for (S32 iElem = 0; iElem < contents.getCount(); iElem++) { 493 494 if (BasePrimitive::isPrimitive(contents[iElem])) { 495 if (BasePrimitive::isSupportedPrimitive(contents[iElem])) 496 meshPrims.push_back(BasePrimitive::get(contents[iElem])); 497 else { 498 daeErrorHandler::get()->handleWarning(avar("Collada <%s> element " 499 "in %s is not supported.", contents[iElem]->getElementName(), 500 _GetNameOrId(geometry))); 501 } 502 } 503 } 504 505 MeshStreams streams; 506 VertTupleMap tupleMap; 507 508 // Create Torque primitives 509 for (S32 iPrim = 0; iPrim < meshPrims.size(); iPrim++) { 510 511 // Primitive element must have at least 1 triangle 512 const domListOfUInts* pTriData = meshPrims[iPrim]->getTriangleData(); 513 if (!pTriData) 514 continue; 515 516 U32 numTriangles = pTriData->getCount() / meshPrims[iPrim]->getStride() / 3; 517 if (!numTriangles) 518 continue; 519 520 // Create TSMesh primitive 521 primitives.increment(); 522 TSDrawPrimitive& primitive = primitives.last(); 523 primitive.start = indices.size(); 524 primitive.matIndex = (TSDrawPrimitive::Triangles | TSDrawPrimitive::Indexed) | 525 addMaterial(meshPrims[iPrim]->getMaterial()); 526 527 // Get the AppMaterial associated with this primitive 528 ColladaAppMaterial* appMat = 0; 529 if (!(primitive.matIndex & TSDrawPrimitive::NoMaterial)) 530 appMat = static_cast<ColladaAppMaterial*>(appMaterials[primitive.matIndex & TSDrawPrimitive::MaterialMask]); 531 532 // Force the material to be double-sided if this geometry is double-sided. 533 if (geomExt->double_sided && appMat && appMat->effectExt) 534 appMat->effectExt->double_sided = true; 535 536 // Pre-allocate triangle indices 537 primitive.numElements = numTriangles * 3; 538 indices.setSize(indices.size() + primitive.numElements); 539 U32* dstIndex = indices.end() - primitive.numElements; 540 541 // Determine the offset for each element type in the stream, and also the 542 // maximum input offset, which will be the number of indices per vertex we 543 // need to skip. 544 domInputLocalOffsetRef sortedInputs[MeshStreams::NumStreams]; 545 MeshStreams::classifyInputs(meshPrims[iPrim]->getInputs(), sortedInputs); 546 547 S32 offsets[MeshStreams::NumStreams]; 548 for (S32 i = 0; i < MeshStreams::NumStreams; i++) 549 offsets[i] = sortedInputs[i] ? sortedInputs[i]->getOffset() : -1; 550 551 // Loop through indices 552 const domUint* pSrcData = &(pTriData->get(0)); 553 554 for (U32 iTri = 0; iTri < numTriangles; iTri++) { 555 556 // If the next triangle could cause us to index across a 16-bit 557 // boundary, split this primitive and clear the tuple map to 558 // ensure primitives only index verts within a 16-bit range. 559 if (vertTuples.size() && 560 (((vertTuples.size()-1) ^ (vertTuples.size()+2)) & 0x10000)) 561 { 562 // Pad vertTuples up to the next 16-bit boundary 563 while (vertTuples.size() & 0xFFFF) 564 vertTuples.push_back(VertTuple(vertTuples.last())); 565 566 // Split the primitive at the current triangle 567 S32 indicesRemaining = (numTriangles - iTri) * 3; 568 if (iTri > 0) 569 { 570 daeErrorHandler::get()->handleWarning(avar("Splitting primitive " 571 "in %s: too many verts for 16-bit indices.", _GetNameOrId(geometry))); 572 573 primitives.last().numElements -= indicesRemaining; 574 primitives.push_back(TSDrawPrimitive(primitives.last())); 575 } 576 577 primitives.last().numElements = indicesRemaining; 578 primitives.last().start = indices.size() - indicesRemaining; 579 580 tupleMap.clear(); 581 } 582 583 streams.reset(); 584 streams.readInputs(meshPrims[iPrim]->getInputs()); 585 586 for (U32 v = 0; v < 3; v++) { 587 // Collect vert tuples into a single array so we can easily grab 588 // vertex data later. 589 VertTuple tuple; 590 tuple.prim = iPrim; 591 tuple.vertex = offsets[MeshStreams::Points] >= 0 ? pSrcData[offsets[MeshStreams::Points]] : -1; 592 tuple.normal = offsets[MeshStreams::Normals] >= 0 ? pSrcData[offsets[MeshStreams::Normals]] : -1; 593 tuple.color = offsets[MeshStreams::Colors] >= 0 ? pSrcData[offsets[MeshStreams::Colors]] : -1; 594 tuple.uv = offsets[MeshStreams::UVs] >= 0 ? pSrcData[offsets[MeshStreams::UVs]] : -1; 595 tuple.uv2 = offsets[MeshStreams::UV2s] >= 0 ? pSrcData[offsets[MeshStreams::UV2s]] : -1; 596 597 tuple.dataVertex = tuple.vertex > -1 ? streams.points.getPoint3FValue(tuple.vertex) : Point3F::Max; 598 tuple.dataNormal = tuple.normal > -1 ? streams.normals.getPoint3FValue(tuple.normal) : Point3F::Max; 599 tuple.dataColor = tuple.color > -1 ? streams.colors.getColorIValue(tuple.color) : ColorI(0,0,0); 600 tuple.dataUV = tuple.uv > -1 ? streams.uvs.getPoint2FValue(tuple.uv) : Point2F::Max; 601 tuple.dataUV2 = tuple.uv2 > -1 ? streams.uv2s.getPoint2FValue(tuple.uv2) : Point2F::Max; 602 603 VertTupleMap::Iterator itr = tupleMap.find(tuple); 604 if (itr == tupleMap.end()) 605 { 606 itr = tupleMap.insert(tuple, vertTuples.size()); 607 vertTuples.push_back(tuple); 608 } 609 610 // Collada uses CCW for front face and Torque uses the opposite, so 611 // for normal (non-inverted) meshes, the indices are flipped. 612 if (appNode->invertMeshes) 613 dstIndex[v] = itr->value; 614 else 615 dstIndex[2 - v] = itr->value; 616 617 pSrcData += meshPrims[iPrim]->getStride(); 618 } 619 dstIndex += 3; 620 } 621 } 622 623 for (S32 iPrim = 0; iPrim < meshPrims.size(); iPrim++) 624 delete meshPrims[iPrim]; 625} 626 627void ColladaAppMesh::getVertexData(const domGeometry* geometry, F32 time, const MatrixF& objOffset, 628 Vector<Point3F>& v_points, 629 Vector<Point3F>& v_norms, 630 Vector<ColorI>& v_colors, 631 Vector<Point2F>& v_uvs, 632 Vector<Point2F>& v_uv2s, 633 bool appendValues) 634{ 635 if (!primitives.size()) 636 return; 637 638 MeshStreams streams; 639 S32 lastPrimitive = -1; 640 ColladaAppMaterial* appMat = 0; 641 642 // Get the supported primitive elements for this geometry 643 Vector<BasePrimitive*> meshPrims; 644 const daeElementRefArray& contents = geometry->getMesh()->getContents(); 645 for (S32 iElem = 0; iElem < contents.getCount(); iElem++) { 646 if (BasePrimitive::isSupportedPrimitive(contents[iElem])) 647 meshPrims.push_back(BasePrimitive::get(contents[iElem])); 648 } 649 650 // If appending values, pre-allocate the arrays 651 if (appendValues) { 652 v_points.setSize(v_points.size() + vertTuples.size()); 653 v_uvs.setSize(v_uvs.size() + vertTuples.size()); 654 } 655 656 // Get pointers to arrays 657 Point3F* points_array = &v_points[v_points.size() - vertTuples.size()]; 658 Point2F* uvs_array = &v_uvs[v_uvs.size() - vertTuples.size()]; 659 Point3F* norms_array = NULL; 660 ColorI* colors_array = NULL; 661 Point2F* uv2s_array = NULL; 662 663 for (S32 iVert = 0; iVert < vertTuples.size(); iVert++) { 664 665 const VertTuple& tuple = vertTuples[iVert]; 666 667 // Change primitives? 668 if (tuple.prim != lastPrimitive) { 669 if (meshPrims.size() <= tuple.prim) { 670 daeErrorHandler::get()->handleError(avar("Failed to get vertex data " 671 "for %s. Primitives do not match base geometry.", geometry->getID())); 672 break; 673 } 674 675 // Update vertex/normal/UV streams and get the new material index 676 streams.reset(); 677 streams.readInputs(meshPrims[tuple.prim]->getInputs()); 678 S32 matIndex = addMaterial(meshPrims[tuple.prim]->getMaterial()); 679 if (matIndex != TSDrawPrimitive::NoMaterial) 680 appMat = static_cast<ColladaAppMaterial*>(appMaterials[matIndex]); 681 else 682 appMat = 0; 683 684 lastPrimitive = tuple.prim; 685 } 686 687 // If we are NOT appending values, only set the value if it actually exists 688 // in the mesh data stream. 689 690 if (appendValues || ((tuple.vertex >= 0) && (tuple.vertex < streams.points.size()))) { 691 points_array[iVert] = streams.points.getPoint3FValue(tuple.vertex); 692 693 // Flip verts for inverted meshes 694 if (appNode->invertMeshes) 695 points_array[iVert].z = -points_array[iVert].z; 696 697 objOffset.mulP(points_array[iVert]); 698 } 699 700 if (appendValues || ((tuple.uv >= 0) && (tuple.uv < streams.uvs.size()))) { 701 uvs_array[iVert] = streams.uvs.getPoint2FValue(tuple.uv); 702 if (appMat && appMat->effectExt) 703 appMat->effectExt->applyTextureTransform(uvs_array[iVert], time); 704 uvs_array[iVert].y = 1.0f - uvs_array[iVert].y; // Collada texcoords are upside down compared to TGE 705 } 706 707 // The rest is non-required data... if it doesn't exist then don't append it. 708 709 if ( (tuple.normal >= 0) && (tuple.normal < streams.normals.size()) ) { 710 if ( !norms_array && iVert == 0 ) 711 { 712 v_norms.setSize(v_norms.size() + vertTuples.size()); 713 norms_array = &v_norms[v_norms.size() - vertTuples.size()]; 714 } 715 716 if ( norms_array ) { 717 norms_array[iVert] = streams.normals.getPoint3FValue(tuple.normal); 718 719 // Flip normals for inverted meshes 720 if (appNode->invertMeshes) 721 norms_array[iVert].z = -norms_array[iVert].z; 722 } 723 } 724 725 if ( (tuple.color >= 0) && (tuple.color < streams.colors.size())) 726 { 727 if ( !colors_array && iVert == 0 ) 728 { 729 v_colors.setSize(v_colors.size() + vertTuples.size()); 730 colors_array = &v_colors[v_colors.size() - vertTuples.size()]; 731 } 732 733 if ( colors_array ) 734 colors_array[iVert] = streams.colors.getColorIValue(tuple.color); 735 } 736 737 if ( (tuple.uv2 >= 0) && (tuple.uv2 < streams.uv2s.size()) ) 738 { 739 if ( !uv2s_array && iVert == 0 ) 740 { 741 v_uv2s.setSize(v_uv2s.size() + vertTuples.size()); 742 uv2s_array = &v_uv2s[v_uv2s.size() - vertTuples.size()]; 743 } 744 745 if ( uv2s_array ) 746 { 747 uv2s_array[iVert] = streams.uv2s.getPoint2FValue(tuple.uv2); 748 if (appMat && appMat->effectExt) 749 appMat->effectExt->applyTextureTransform(uv2s_array[iVert], time); 750 uv2s_array[iVert].y = 1.0f - uv2s_array[iVert].y; // Collada texcoords are upside down compared to TGE 751 } 752 } 753 } 754 755 for (S32 iPrim = 0; iPrim < meshPrims.size(); iPrim++) 756 delete meshPrims[iPrim]; 757} 758 759void ColladaAppMesh::getMorphVertexData(const domMorph* morph, F32 time, const MatrixF& objOffset, 760 Vector<Point3F>& v_points, 761 Vector<Point3F>& v_norms, 762 Vector<ColorI>& v_colors, 763 Vector<Point2F>& v_uvs, 764 Vector<Point2F>& v_uv2s) 765{ 766 // @todo: Could the base geometry (or any target geometry) also be a morph? 767 768 // Get the target geometries and weights (could be animated) 769 Vector<const domGeometry*> targetGeoms; 770 domListOfFloats targetWeights; 771 772 for (S32 iInput = 0; iInput < morph->getTargets()->getInput_array().getCount(); iInput++) { 773 const domInputLocal* input = morph->getTargets()->getInput_array()[iInput]; 774 const domSource* source = daeSafeCast<domSource>(findInputSource(input)); 775 776 if (dStrEqual(input->getSemantic(), "MORPH_TARGET")) { 777 // Get the morph targets 778 _SourceReader srcTargets; 779 srcTargets.initFromSource(source); 780 781 for (S32 iTarget = 0; iTarget < srcTargets.size(); iTarget++) { 782 // Lookup the element and add to the targets list 783 daeIDRef idref(srcTargets.getStringValue(iTarget)); 784 idref.setContainer(morph->getDocument()->getDomRoot()); 785 targetGeoms.push_back(daeSafeCast<domGeometry>(idref.getElement())); 786 } 787 } 788 else if (dStrEqual(input->getSemantic(), "MORPH_WEIGHT")) { 789 // Get the (possibly animated) morph weight 790 targetWeights = AnimatedFloatList(source->getFloat_array()).getValue(time); 791 } 792 } 793 794 // Check that we have a weight for each target 795 if (targetGeoms.size() != targetWeights.getCount()) 796 { 797 domController* ctrl = daeSafeCast<domController>(const_cast<domMorph*>(morph)->getParent()); 798 Con::warnf("Mismatched morph targets and weights in %s.", _GetNameOrId(ctrl)); 799 800 // Set unused targets to zero weighting (unused weights are ignored) 801 while (targetGeoms.size() > targetWeights.getCount()) 802 targetWeights.append(0.0f); 803 } 804 805 // Get the base geometry and vertex data 806 const domGeometry* baseGeometry = daeSafeCast<domGeometry>(morph->getSource().getElement()); 807 if (!baseGeometry) 808 return; 809 810 getPrimitives(baseGeometry); 811 getVertexData(baseGeometry, time, objOffset, v_points, v_norms, v_colors, v_uvs, v_uv2s, true); 812 813 // Get pointers to the arrays of base geometry data 814 Point3F* points_array = &v_points[v_points.size() - vertTuples.size()]; 815 Point3F* norms_array = &v_norms[v_norms.size() - vertTuples.size()]; 816 Point2F* uvs_array = &v_uvs[v_uvs.size() - vertTuples.size()]; 817 ColorI* colors_array = v_colors.size() ? &v_colors[v_colors.size() - vertTuples.size()] : 0; 818 Point2F* uv2s_array = v_uv2s.size() ? &v_uv2s[v_uv2s.size() - vertTuples.size()] : 0; 819 820 // Normalize base vertex data? 821 if (morph->getMethod() == MORPHMETHODTYPE_NORMALIZED) { 822 823 F32 weightSum = 0.0f; 824 for (S32 iWeight = 0; iWeight < targetWeights.getCount(); iWeight++) { 825 weightSum += targetWeights[iWeight]; 826 } 827 828 // Result = Base*(1.0-w1-w2 ... -wN) + w1*Target1 + w2*Target2 ... + wN*TargetN 829 weightSum = mClampF(1.0f - weightSum, 0.0f, 1.0f); 830 831 for (S32 iVert = 0; iVert < vertTuples.size(); iVert++) { 832 points_array[iVert] *= weightSum; 833 norms_array[iVert] *= weightSum; 834 uvs_array[iVert] *= weightSum; 835 } 836 837 if (uv2s_array) { 838 for (S32 iVert = 0; iVert < vertTuples.size(); iVert++) 839 uv2s_array[iVert] *= weightSum; 840 } 841 } 842 843 // Interpolate using the target geometry and weights 844 for (S32 iTarget = 0; iTarget < targetGeoms.size(); iTarget++) { 845 846 // Ignore empty weights 847 if (targetWeights[iTarget] == 0.0f) 848 continue; 849 850 // Get target geometry data into temporary arrays 851 Vector<Point3F> targetPoints; 852 Vector<Point3F> targetNorms; 853 Vector<Point2F> targetUvs; 854 Vector<ColorI> targetColors; 855 Vector<Point2F> targetUv2s; 856 857 // Copy base geometry into target geometry (will be used if target does 858 // not define normals or uvs) 859 targetPoints.set(points_array, vertTuples.size()); 860 targetNorms.set(norms_array, vertTuples.size()); 861 targetUvs.set(uvs_array, vertTuples.size()); 862 if (colors_array) 863 targetColors.set(colors_array, vertTuples.size()); 864 if (uv2s_array) 865 targetUv2s.set(uv2s_array, vertTuples.size()); 866 867 getVertexData(targetGeoms[iTarget], time, objOffset, targetPoints, targetNorms, targetColors, targetUvs, targetUv2s, false); 868 869 // Combine with base geometry 870 for (S32 iVert = 0; iVert < vertTuples.size(); iVert++) { 871 points_array[iVert] += targetPoints[iVert] * targetWeights[iTarget]; 872 norms_array[iVert] += targetNorms[iVert] * targetWeights[iTarget]; 873 uvs_array[iVert] += targetUvs[iVert] * targetWeights[iTarget]; 874 } 875 876 if (uv2s_array) { 877 for (S32 iVert = 0; iVert < vertTuples.size(); iVert++) 878 uv2s_array[iVert] += targetUv2s[iVert] * targetWeights[iTarget]; 879 } 880 if (colors_array) { 881 for (S32 iVert = 0; iVert < vertTuples.size(); iVert++) 882 { 883 LinearColorF tCol = colors_array[iVert]; 884 tCol += LinearColorF(targetColors[iVert]) * (F32)targetWeights[iTarget]; 885 colors_array[iVert] = tCol.toColorI(); 886 } 887 } 888 } 889} 890 891void ColladaAppMesh::lockMesh(F32 t, const MatrixF& objOffset) 892{ 893 // Find the geometry element for this mesh. Could be one of 3 things: 894 // 1) a simple static mesh (Collada <geometry> element) 895 // 2) a simple morph (some combination of static meshes) 896 // 3) a skin (skin geometry could also be a morph!) 897 daeElement* geometry = 0; 898 899 if (instanceGeom) { 900 // Simple, static mesh 901 geometry = instanceGeom->getUrl().getElement(); 902 } 903 else if (instanceCtrl) { 904 const domController* ctrl = daeSafeCast<domController>(instanceCtrl->getUrl().getElement()); 905 if (!ctrl) { 906 daeErrorHandler::get()->handleWarning(avar("Failed to find <controller> " 907 "element for %s", getName())); 908 return; 909 } 910 else if (ctrl->getMorph()) { 911 // Morph controller 912 geometry = ctrl->getMorph(); 913 } 914 else { 915 // Skinned mesh: source geometry could be static geometry or a morph controller. 916 geometry = ctrl->getSkin()->getSource().getElement(); 917 if (geometry && geometry->getElementType() == COLLADA_TYPE::CONTROLLER) 918 geometry = daeSafeCast<domController>(geometry)->getMorph(); 919 } 920 } 921 922 if (!geometry) { 923 daeErrorHandler::get()->handleWarning(avar("Failed to find source geometry " 924 "for %s", getName())); 925 return; 926 } 927 928 // Now get the vertex data at the specified time 929 if (geometry->getElementType() == COLLADA_TYPE::GEOMETRY) { 930 getPrimitives(daeSafeCast<domGeometry>(geometry)); 931 getVertexData(daeSafeCast<domGeometry>(geometry), t, objOffset, points, normals, colors, uvs, uv2s, true); 932 } 933 else if (geometry->getElementType() == COLLADA_TYPE::MORPH) { 934 getMorphVertexData(daeSafeCast<domMorph>(geometry), t, objOffset, points, normals, colors, uvs, uv2s); 935 } 936 else { 937 daeErrorHandler::get()->handleWarning(avar("Unsupported geometry type " 938 "'<%s>' for %s", geometry->getElementName(), getName())); 939 } 940} 941 942void ColladaAppMesh::lookupSkinData() 943{ 944 // Only lookup skin data once 945 if (!isSkin() || weight.size()) 946 return; 947 948 // Get the skin and vertex weight data 949 const domSkin* skin = daeSafeCast<domController>(instanceCtrl->getUrl().getElement())->getSkin(); 950 const domSkin::domVertex_weights& weightIndices = *(skin->getVertex_weights()); 951 const domListOfInts& weights_v = weightIndices.getV()->getValue(); 952 const domListOfUInts& weights_vcount = weightIndices.getVcount()->getValue(); 953 954 MeshStreams streams; 955 streams.readInputs(skin->getJoints()->getInput_array()); 956 streams.readInputs(weightIndices.getInput_array()); 957 958 MatrixF invObjOffset(objectOffset); 959 invObjOffset.inverse(); 960 961 // Get the bind shape matrix 962 MatrixF bindShapeMatrix(true); 963 if (skin->getBind_shape_matrix()) 964 bindShapeMatrix = vecToMatrixF<domMatrix>(skin->getBind_shape_matrix()->getValue()); 965 bindShapeMatrix.mul(invObjOffset); 966 967 // Determine the offset into the vindices array for each vertex (since each 968 // vertex may have multiple [bone, weight] pairs in the array) 969 Vector<U32> vindicesOffset; 970 const domInt* vindices = (domInt*)weights_v.getRaw(0); 971 for (S32 iWeight = 0; iWeight < weights_vcount.getCount(); iWeight++) { 972 // Store the offset into the vindices array for this vertex 973 vindicesOffset.push_back(vindices - (domInt*)weights_v.getRaw(0)); 974 vindices += (weights_vcount[iWeight]*2); // 2 indices [bone, weight] per vert 975 } 976 977 // Set vertex weights 978 bool tooManyWeightsWarning = false; 979 for (S32 iVert = 0; iVert < vertsPerFrame; iVert++) { 980 const domUint* vcount = (domUint*)weights_vcount.getRaw(0); 981 vindices = (domInt*)weights_v.getRaw(0); 982 vindices += vindicesOffset[vertTuples[iVert].vertex]; 983 984 S32 nonZeroWeightCount = 0; 985 986 for (S32 iWeight = 0; iWeight < vcount[vertTuples[iVert].vertex]; iWeight++) { 987 988 S32 bIndex = vindices[iWeight*2]; 989 F32 bWeight = streams.weights.getFloatValue( vindices[iWeight*2 + 1] ); 990 991 // Ignore empty weights 992 if ( bIndex < 0 || bWeight == 0 ) 993 continue; 994 995 // Limit the number of weights per bone (keep the N largest influences) 996 if ( nonZeroWeightCount >= TSSkinMesh::BatchData::maxBonePerVert ) 997 { 998 if (vcount[vertTuples[iVert].vertex] > TSSkinMesh::BatchData::maxBonePerVert) 999 { 1000 if (!tooManyWeightsWarning) 1001 { 1002 tooManyWeightsWarning = true; 1003 daeErrorHandler::get()->handleWarning(avar("At least one vertex has " 1004 "too many bone weights. Limiting to the largest %d influences.", 1005 TSSkinMesh::BatchData::maxBonePerVert)); 1006 } 1007 } 1008 1009 // Too many weights => find and replace the smallest one 1010 S32 minIndex = weight.size() - TSSkinMesh::BatchData::maxBonePerVert; 1011 F32 minWeight = weight[minIndex]; 1012 for (S32 i = minIndex + 1; i < weight.size(); i++) 1013 { 1014 if (weight[i] < minWeight) 1015 { 1016 minWeight = weight[i]; 1017 minIndex = i; 1018 } 1019 } 1020 1021 boneIndex[minIndex] = bIndex; 1022 weight[minIndex] = bWeight; 1023 } 1024 else 1025 { 1026 vertexIndex.push_back( iVert ); 1027 boneIndex.push_back( bIndex ); 1028 weight.push_back( bWeight ); 1029 nonZeroWeightCount++; 1030 } 1031 } 1032 } 1033 1034 // Normalize vertex weights (force weights for each vert to sum to 1) 1035 S32 iWeight = 0; 1036 while (iWeight < weight.size()) { 1037 // Find the last weight with the same vertex number, and sum all weights for 1038 // that vertex 1039 F32 invTotalWeight = 0; 1040 S32 iLast; 1041 for (iLast = iWeight; iLast < weight.size(); iLast++) { 1042 if (vertexIndex[iLast] != vertexIndex[iWeight]) 1043 break; 1044 invTotalWeight += weight[iLast]; 1045 } 1046 1047 // Then normalize the vertex weights 1048 invTotalWeight = 1.0f / invTotalWeight; 1049 for (; iWeight < iLast; iWeight++) 1050 weight[iWeight] *= invTotalWeight; 1051 } 1052 1053 // Add dummy AppNodes to allow Collada joints to be mapped to 3space nodes 1054 bones.setSize(streams.joints.size()); 1055 initialTransforms.setSize(streams.joints.size()); 1056 for (S32 iJoint = 0; iJoint < streams.joints.size(); iJoint++) 1057 { 1058 const char* jointName = streams.joints.getStringValue(iJoint); 1059 1060 // Lookup the joint element 1061 const domNode* joint = 0; 1062 if (instanceCtrl->getSkeleton_array().getCount()) { 1063 // Search for the node using the <skeleton> as the base element 1064 for (S32 iSkel = 0; iSkel < instanceCtrl->getSkeleton_array().getCount(); iSkel++) { 1065 xsAnyURI skeleton = instanceCtrl->getSkeleton_array()[iSkel]->getValue(); 1066 daeSIDResolver resolver(skeleton.getElement(), jointName); 1067 joint = daeSafeCast<domNode>(resolver.getElement()); 1068 if (joint) 1069 break; 1070 } 1071 } 1072 else { 1073 // Search for the node from the root level 1074 daeSIDResolver resolver(skin->getDocument()->getDomRoot(), jointName); 1075 joint = daeSafeCast<domNode>(resolver.getElement()); 1076 } 1077 1078 if (!joint) { 1079 daeErrorHandler::get()->handleWarning(avar("Failed to find bone '%s', " 1080 "defaulting to instance_controller parent node '%s'", jointName, appNode->getName())); 1081 joint = appNode->getDomNode(); 1082 } 1083 bones[iJoint] = new ColladaAppNode(joint); 1084 1085 initialTransforms[iJoint] = objectOffset; 1086 1087 // Bone scaling is generally ignored during import, since 3space only 1088 // stores default node transform and rotation. Compensate for this by 1089 // removing the scaling from the inverse bind transform as well 1090 MatrixF invBind = streams.invBindMatrices.getMatrixFValue(iJoint); 1091 if (!ColladaUtils::getOptions().ignoreNodeScale) 1092 { 1093 Point3F invScale = invBind.getScale(); 1094 invScale.x = invScale.x ? (1.0f / invScale.x) : 0; 1095 invScale.y = invScale.y ? (1.0f / invScale.y) : 0; 1096 invScale.z = invScale.z ? (1.0f / invScale.z) : 0; 1097 initialTransforms[iJoint].scale(invScale); 1098 } 1099 1100 // Inverted node coordinate spaces (negative scale factor) are corrected 1101 // in ColladaAppNode::getNodeTransform, so need to apply the same operation 1102 // here to match 1103 if (m_matF_determinant(invBind) < 0.0f) 1104 initialTransforms[iJoint].scale(Point3F(1, 1, -1)); 1105 1106 initialTransforms[iJoint].mul(invBind); 1107 initialTransforms[iJoint].mul(bindShapeMatrix); 1108 } 1109} 1110