//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "console/consoleTypes.h" #include "core/resourceManager.h" #include "ts/tsShape.h" #include "ts/tsShapeInstance.h" #include "ts/tsLastDetail.h" #include "ts/tsMaterialList.h" #include "core/stream/fileStream.h" #include "core/volume.h" //----------------------------------------------------------------------------- S32 TSShape::addName(const String& name) { // Check for empty names if (name.isEmpty()) return -1; // Return the index of the new name (add if it is unique) S32 index = findName(name); if (index >= 0) return index; names.push_back(StringTable->insert(name)); return names.size()-1; } void TSShape::updateSmallestVisibleDL() { // Update smallest visible detail mSmallestVisibleDL = -1; mSmallestVisibleSize = F32_MAX; F32 maxSize = 0.0f; for (S32 i = 0; i < details.size(); i++) { maxSize = getMax( maxSize, details[i].size ); if ((details[i].size >= 0) && (details[i].size < mSmallestVisibleSize)) { mSmallestVisibleDL = i; mSmallestVisibleSize = details[i].size; } } // Initialize the detail level lod lookup table. mDetailLevelLookup.setSize( (U32)( maxSize * 2.0f ) + 2 ); for ( U32 l=0; l < mDetailLevelLookup.size(); l++ ) { F32 pixelSize = (F32)l; S32 dl = -1; for ( U32 d=0; d < details.size(); d++ ) { // Break when we get to hidden detail // levels like collision shapes. if ( details[d].size < 0 ) break; if ( pixelSize > details[d].size ) { dl = d; break; } if ( d + 1 >= details.size() || details[d+1].size < 0 ) { // We've run out of details and haven't found anything? // Let's just grab this one. dl = d; break; } } // Calculate the intra detail level. F32 intraDL = 0; if ( dl > -1 ) { F32 curSize = details[dl].size; F32 nextSize = dl == 0 ? 2.0f * curSize : details[dl - 1].size; intraDL = mClampF( nextSize - curSize > 0.01f ? (pixelSize - curSize) / (nextSize - curSize) : 1.0f, 0, 1 ); } mDetailLevelLookup[l].set( dl, intraDL ); } // Test for using the legacy screen error // lod method here instead of runtime. // // See setDetailFromDistance(). // mUseDetailFromScreenError = mSmallestVisibleDL >= 0 && details.first().maxError >= 0; } S32 TSShape::addDetail(const String& dname, S32 size, S32 subShapeNum) { S32 nameIndex = addName(avar("%s%d", dname.c_str(), size)); // Check if this detail size has already been added S32 index; for (index = 0; index < details.size(); index++) { if ((details[index].size == size) && (details[index].subShapeNum == subShapeNum) && (details[index].nameIndex == nameIndex)) return index; if (details[index].size < size) break; } // Create a new detail level at the right index, so array // remains sorted by detail size (from largest to smallest) details.insert(index); TSShape::Detail &detail = details[index]; // Clear the detail to ensure no garbage values // are left in any vars we don't set. dMemset( &detail, 0, sizeof( Detail ) ); // Setup the detail. detail.nameIndex = nameIndex; detail.size = size; detail.subShapeNum = subShapeNum; detail.objectDetailNum = 0; detail.averageError = -1; detail.maxError = -1; detail.polyCount = 0; // Resize alpha vectors alphaIn.increment(); alphaOut.increment(); // Fixup objectDetailNum in other detail levels for (S32 i = index+1; i < details.size(); i++) { if ((details[i].subShapeNum >= 0) && ((subShapeNum == -1) || (details[i].subShapeNum == subShapeNum))) details[i].objectDetailNum++; } // Update smallest visible detail updateSmallestVisibleDL(); return index; } S32 TSShape::addImposter(const String& cachePath, S32 size, S32 numEquatorSteps, S32 numPolarSteps, S32 dl, S32 dim, bool includePoles, F32 polarAngle) { // Check if the desired size is already in use bool isNewDetail = false; S32 detIndex = findDetailBySize( size ); if ( detIndex >= 0 ) { // Size is in use. If the detail is already an imposter, we can just change // the settings, otherwise quit if ( details[detIndex].subShapeNum >= 0 ) { Con::errorf( "TSShape::addImposter: A non-billboard detail already " "exists at size %d", size ); return -1; } } else { // Size is not in use. If an imposter already exists, change its size, otherwise // create a new detail for ( detIndex = 0; detIndex < details.size(); ++detIndex ) { if ( details[detIndex].subShapeNum < 0 ) { // Change the imposter detail size setDetailSize( details[detIndex].size, size ); break; } } if ( detIndex == details.size() ) { isNewDetail = true; detIndex = addDetail( "bbDetail", size, -1 ); } } // Now set the billboard properties. Detail &detail = details[detIndex]; // In prior to DTS version 26 we would pack the autobillboard // into this single 32bit value. This was prone to overflows // of parameters caused random bugs. // // Set the old autobillboard properties var to zero. detail.objectDetailNum = 0; // We now use the new vars. detail.bbEquatorSteps = numEquatorSteps; detail.bbPolarSteps = numPolarSteps; detail.bbPolarAngle = polarAngle; detail.bbDetailLevel = dl; detail.bbDimension = dim; detail.bbIncludePoles = includePoles; // Rebuild billboard details or force an update of the modified detail if ( isNewDetail ) { // Add NULL meshes for this detail for ( S32 iObj = 0; iObj < objects.size(); ++iObj ) { if ( detIndex < objects[iObj].numMeshes ) { objects[iObj].numMeshes++; meshes.insert( objects[iObj].startMeshIndex + detIndex, NULL ); for (S32 j = iObj + 1; j < objects.size(); ++j ) objects[j].startMeshIndex++; } } // Could be dedicated server. if ( GFXDevice::devicePresent() ) setupBillboardDetails( cachePath ); while ( detailCollisionAccelerators.size() < details.size() ) detailCollisionAccelerators.push_back( NULL ); } else { if ( billboardDetails.size() && GFXDevice::devicePresent() ) { delete billboardDetails[detIndex]; billboardDetails[detIndex] = new TSLastDetail( this, cachePath, detail.bbEquatorSteps, detail.bbPolarSteps, detail.bbPolarAngle, detail.bbIncludePoles, detail.bbDetailLevel, detail.bbDimension ); billboardDetails[detIndex]->update( true ); } } return detIndex; } bool TSShape::removeImposter() { // Find the imposter detail level S32 detIndex; for ( detIndex = 0; detIndex < details.size(); ++detIndex ) { if ( details[detIndex].subShapeNum < 0 ) break; } if ( detIndex == details.size() ) { Con::errorf( "TSShape::removeImposter: No imposter detail level found in shape" ); return false; } // Remove the detail level details.erase( detIndex ); if ( detIndex < billboardDetails.size() ) { // Delete old textures TSLastDetail* bb = billboardDetails[detIndex]; bb->deleteImposterCacheTextures(); delete billboardDetails[detIndex]; } billboardDetails.clear(); detailCollisionAccelerators.erase( detIndex ); // Remove the (NULL) meshes from each object for ( S32 iObj = 0; iObj < objects.size(); ++iObj ) { if ( detIndex < objects[iObj].numMeshes ) { objects[iObj].numMeshes--; meshes.erase( objects[iObj].startMeshIndex + detIndex ); for (S32 j = iObj + 1; j < objects.size(); ++j ) objects[j].startMeshIndex--; } } // Update smallest visible size updateSmallestVisibleDL(); return true; } //----------------------------------------------------------------------------- /// Get the index of the element in the group with a given name template S32 findByName(Vector& group, S32 nameIndex) { for (S32 i = 0; i < group.size(); i++) if (group[i].nameIndex == nameIndex) return i; return -1; } /// Adjust the nameIndex for elements in the group template void adjustForNameRemoval(Vector& group, S32 nameIndex) { for (S32 i = 0; i < group.size(); i++) if (group[i].nameIndex > nameIndex) group[i].nameIndex--; } bool TSShape::removeName(const String& name) { // Check if the name is still in use S32 nameIndex = findName(name); if ((findByName(nodes, nameIndex) >= 0) || (findByName(objects, nameIndex) >= 0) || (findByName(sequences, nameIndex) >= 0) || (findByName(details, nameIndex) >= 0)) return false; // Remove the name, then update nameIndex for affected elements names.erase(nameIndex); adjustForNameRemoval(nodes, nameIndex); adjustForNameRemoval(objects, nameIndex); adjustForNameRemoval(sequences, nameIndex); adjustForNameRemoval(details, nameIndex); return true; } //----------------------------------------------------------------------------- template bool doRename(TSShape* shape, Vector& group, const String& oldName, const String& newName) { // Find the element in the group with the oldName S32 index = findByName(group, shape->findName(oldName)); if (index < 0) { Con::errorf("TSShape::rename: Could not find '%s'", oldName.c_str()); return false; } // Ignore trivial renames if (oldName.equal(newName, String::NoCase)) return true; // Check that this name is not already in use if (findByName(group, shape->findName(newName)) >= 0) { Con::errorf("TSShape::rename: '%s' is already in use", newName.c_str()); return false; } // Do the rename (the old name will be removed if it is no longer in use) group[index].nameIndex = shape->addName(newName); shape->removeName(oldName); return true; } bool TSShape::renameNode(const String& oldName, const String& newName) { return doRename(this, nodes, oldName, newName); } bool TSShape::renameObject(const String& oldName, const String& newName) { return doRename(this, objects, oldName, newName); } bool TSShape::renameDetail(const String& oldName, const String& newName) { return doRename(this, details, oldName, newName); } bool TSShape::renameSequence(const String& oldName, const String& newName) { return doRename(this, sequences, oldName, newName); } //----------------------------------------------------------------------------- bool TSShape::addNode(const String& name, const String& parentName, const Point3F& pos, const QuatF& rot) { // Check that adding this node would not exceed the maximum count if (nodes.size() >= MAX_TS_SET_SIZE) { Con::errorf("TSShape::addNode: Cannot add node, shape already has maximum (%d) nodes", MAX_TS_SET_SIZE); return false; } // Check that there is not already a node with this name if (findNode(name) >= 0) { Con::errorf("TSShape::addNode: %s already exists!", name.c_str()); return false; } // Find the parent node (OK for name to be empty => node is at root level) S32 parentIndex = -1; if (String::compare(parentName, "")) { parentIndex = findNode(parentName); if (parentIndex < 0) { Con::errorf("TSShape::addNode: Could not find parent node '%s'", parentName.c_str()); return false; } } // Need to make everything editable since node indexes etc will change makeEditable(); // Insert node at the end of the subshape S32 subShapeIndex = (parentIndex >= 0) ? getSubShapeForNode(parentIndex) : 0; S32 nodeIndex = subShapeNumNodes[subShapeIndex]; // Adjust subshape node indices subShapeNumNodes[subShapeIndex]++; for (S32 i = subShapeIndex + 1; i < subShapeFirstNode.size(); i++) subShapeFirstNode[i]++; // Update animation sequences for (S32 iSeq = 0; iSeq < sequences.size(); iSeq++) { // Update animation matters arrays (new node is not animated) TSShape::Sequence& seq = sequences[iSeq]; seq.translationMatters.insert(nodeIndex, false); seq.rotationMatters.insert(nodeIndex, false); seq.scaleMatters.insert(nodeIndex, false); } // Insert the new node TSShape::Node node; node.nameIndex = addName(name); node.parentIndex = parentIndex; node.firstChild = -1; node.firstObject = -1; node.nextSibling = -1; nodes.insert(nodeIndex, node); // Insert node default translation and rotation Quat16 rot16; rot16.set(rot); defaultTranslations.insert(nodeIndex, pos); defaultRotations.insert(nodeIndex, rot16); // Fixup node indices for (S32 i = 0; i < nodes.size(); i++) { if (nodes[i].parentIndex >= nodeIndex) nodes[i].parentIndex++; } for (S32 i = 0; i < objects.size(); i++) { if (objects[i].nodeIndex >= nodeIndex) objects[i].nodeIndex++; } for (S32 i = 0; i < meshes.size(); i++) { if (meshes[i] && (meshes[i]->getMeshType() == TSMesh::SkinMeshType)) { TSSkinMesh* skin = dynamic_cast(meshes[i]); for (S32 j = 0; j < skin->batchData.nodeIndex.size(); j++) { if (skin->batchData.nodeIndex[j] >= nodeIndex) skin->batchData.nodeIndex[j]++; } } } initObjects(); return true; } /// Erase animation keyframes (translation, rotation etc) template S32 eraseStates(Vector& vec, const TSIntegerSet& matters, S32 base, S32 numKeyframes, S32 index=-1) { S32 dest, count; if (index == -1) { // Erase for all nodes/objects dest = base; count = numKeyframes * matters.count(); } else { // Erase for the indexed node/object only dest = base + matters.count(index)*numKeyframes; count = numKeyframes; } // Erase the values if (count) { if ((dest + count) < vec.size()) dCopyArray(&vec[dest], &vec[dest + count], vec.size() - (dest + count)); vec.decrement(count); } return count; } bool TSShape::removeNode(const String& name) { // Find the node to be removed S32 nodeIndex = findNode(name); if (nodeIndex < 0) { Con::errorf("TSShape::removeNode: Could not find node '%s'", name.c_str()); return false; } S32 nodeParentIndex = nodes[nodeIndex].parentIndex; // Warn if there are objects attached to this node Vector nodeObjects; getNodeObjects(nodeIndex, nodeObjects); if (nodeObjects.size()) { Con::warnf("TSShape::removeNode: Node '%s' has %d objects attached, these " "will be reassigned to the node's parent ('%s')", name.c_str(), nodeObjects.size(), ((nodeParentIndex >= 0) ? getName(nodes[nodeParentIndex].nameIndex).c_str() : "null")); } // Need to make everything editable since node indexes etc will change makeEditable(); // Update animation sequences for (S32 iSeq = 0; iSeq < sequences.size(); iSeq++) { TSShape::Sequence& seq = sequences[iSeq]; // Remove animated node transforms if (seq.translationMatters.test(nodeIndex)) eraseStates(nodeTranslations, seq.translationMatters, seq.baseTranslation, seq.numKeyframes, nodeIndex); if (seq.rotationMatters.test(nodeIndex)) eraseStates(nodeRotations, seq.rotationMatters, seq.baseRotation, seq.numKeyframes, nodeIndex); if (seq.scaleMatters.test(nodeIndex)) { if (seq.flags & TSShape::ArbitraryScale) { eraseStates(nodeArbitraryScaleRots, seq.scaleMatters, seq.baseScale, seq.numKeyframes, nodeIndex); eraseStates(nodeArbitraryScaleFactors, seq.scaleMatters, seq.baseScale, seq.numKeyframes, nodeIndex); } else if (seq.flags & TSShape::AlignedScale) eraseStates(nodeAlignedScales, seq.scaleMatters, seq.baseScale, seq.numKeyframes, nodeIndex); else eraseStates(nodeUniformScales, seq.scaleMatters, seq.baseScale, seq.numKeyframes, nodeIndex); } seq.translationMatters.erase(nodeIndex); seq.rotationMatters.erase(nodeIndex); seq.scaleMatters.erase(nodeIndex); } // Remove the node nodes.erase(nodeIndex); defaultTranslations.erase(nodeIndex); defaultRotations.erase(nodeIndex); // Adjust subshape node indices S32 subShapeIndex = getSubShapeForNode(nodeIndex); subShapeNumNodes[subShapeIndex]--; for (S32 i = subShapeIndex + 1; i < subShapeFirstNode.size(); i++) subShapeFirstNode[i]--; // Fixup node parent indices for (S32 i = 0; i < nodes.size(); i++) { if (nodes[i].parentIndex == nodeIndex) nodes[i].parentIndex = -1; else if (nodes[i].parentIndex > nodeIndex) nodes[i].parentIndex--; } if (nodeParentIndex > nodeIndex) nodeParentIndex--; // Fixup object node indices, and re-assign attached objects to node's parent for (S32 i = 0; i < objects.size(); i++) { if (objects[i].nodeIndex == nodeIndex) objects[i].nodeIndex = nodeParentIndex; if (objects[i].nodeIndex > nodeIndex) objects[i].nodeIndex--; } // Fixup skin weight node indices, and re-assign weights for deleted node to its parent for (S32 i = 0; i < meshes.size(); i++) { if (meshes[i] && (meshes[i]->getMeshType() == TSMesh::SkinMeshType)) { TSSkinMesh* skin = dynamic_cast(meshes[i]); for (S32 j = 0; j < skin->batchData.nodeIndex.size(); j++) { if (skin->batchData.nodeIndex[j] == nodeIndex) skin->batchData.nodeIndex[j] = nodeParentIndex; if (skin->batchData.nodeIndex[j] > nodeIndex) skin->batchData.nodeIndex[j]--; } } } // Remove the sequence name if it is no longer in use removeName(name); initObjects(); return true; } //----------------------------------------------------------------------------- bool TSShape::setNodeTransform(const String& name, const Point3F& pos, const QuatF& rot) { // Find the node to be transformed S32 nodeIndex = findNode(name); if (nodeIndex < 0) { Con::errorf("TSShape::setNodeTransform: Could not find node '%s'", name.c_str()); return false; } // Update initial node position and rotation defaultTranslations[nodeIndex] = pos; defaultRotations[nodeIndex].set(rot); return true; } //----------------------------------------------------------------------------- S32 TSShape::addObject(const String& objName, S32 subShapeIndex) { S32 objIndex = subShapeNumObjects[subShapeIndex]; // Add object to subshape subShapeNumObjects[subShapeIndex]++; for (S32 i = subShapeIndex + 1; i < subShapeFirstObject.size(); i++) subShapeFirstObject[i]++; TSShape::Object obj; obj.nameIndex = addName(objName); obj.nodeIndex = 0; obj.numMeshes = 0; obj.startMeshIndex = (objIndex == 0) ? 0 : objects[objIndex-1].startMeshIndex + objects[objIndex-1].numMeshes; obj.firstDecal = 0; obj.nextSibling = 0; objects.insert(objIndex, obj); // Add default object state TSShape::ObjectState state; state.frameIndex = 0; state.matFrameIndex = 0; state.vis = 1.0f; objectStates.insert(objIndex, state); // Fixup sequences for (S32 i = 0; i < sequences.size(); i++) sequences[i].baseObjectState++; return objIndex; } void TSShape::addMeshToObject(S32 objIndex, S32 meshIndex, TSMesh* mesh) { TSShape::Object& obj = objects[objIndex]; // Pad with NULLs if required S32 oldNumMeshes = obj.numMeshes; if (mesh) { for (S32 i = obj.numMeshes; i < meshIndex; i++) { meshes.insert(obj.startMeshIndex + i, NULL); obj.numMeshes++; } } // Insert the new mesh meshes.insert(obj.startMeshIndex + meshIndex, mesh); obj.numMeshes++; // Skinned meshes are not attached to any node if (mesh && (mesh->getMeshType() == TSMesh::SkinMeshType)) obj.nodeIndex = -1; // Fixup mesh indices for other objects for (S32 i = 0; i < objects.size(); i++) { if ((i != objIndex) && (objects[i].startMeshIndex >= obj.startMeshIndex)) objects[i].startMeshIndex += (obj.numMeshes - oldNumMeshes); } } void TSShape::removeMeshFromObject(S32 objIndex, S32 meshIndex) { TSShape::Object& obj = objects[objIndex]; // Remove the mesh, but do not destroy it (this must be done by the caller) meshes[obj.startMeshIndex + meshIndex] = NULL; // Check if there are any objects remaining that have a valid mesh at this // detail size bool removeDetail = true; for (S32 i = 0; i < objects.size(); i++) { if ((meshIndex < objects[i].numMeshes) && meshes[objects[i].startMeshIndex + meshIndex]) { removeDetail = false; break; } } // Remove detail level if possible if (removeDetail) { for (S32 i = 0; i < objects.size(); i++) { if (meshIndex < objects[i].numMeshes) { U32 idxToRemove = objects[i].startMeshIndex + meshIndex; meshes.erase(idxToRemove); objects[i].numMeshes--; // Clear invalid parent for (U32 k = 0; k < meshes.size(); k++) { if (meshes[k] == NULL) continue; if (meshes[k]->mParentMesh == idxToRemove) { meshes[k]->mParentMesh = -1; } else if (meshes[k]->mParentMesh > idxToRemove) { meshes[k]->mParentMesh--; } } for (S32 j = 0; j < objects.size(); j++) { if (objects[j].startMeshIndex > objects[i].startMeshIndex) objects[j].startMeshIndex--; } } } Vector validDetails; getSubShapeDetails(getSubShapeForObject(objIndex), validDetails); for (S32 i = 0; i < validDetails.size(); i++) { TSShape::Detail& detail = details[validDetails[i]]; if (detail.objectDetailNum > meshIndex) detail.objectDetailNum--; } details.erase(validDetails[meshIndex]); } // Remove trailing NULL meshes from the object S32 oldNumMeshes = obj.numMeshes; while (obj.numMeshes && !meshes[obj.startMeshIndex + obj.numMeshes - 1]) { U32 idxToRemove = obj.startMeshIndex + obj.numMeshes - 1; meshes.erase(idxToRemove); // Clear invalid parent for (U32 k = 0; k < meshes.size(); k++) { if (meshes[k] == NULL) continue; if (meshes[k]->mParentMesh == idxToRemove) { meshes[k]->mParentMesh = -1; } else if (meshes[k]->mParentMesh > idxToRemove) { meshes[k]->mParentMesh--; } } obj.numMeshes--; } // Fixup mesh indices for other objects for (S32 i = 0; i < objects.size(); i++) { if (objects[i].startMeshIndex > obj.startMeshIndex) objects[i].startMeshIndex -= (oldNumMeshes - obj.numMeshes); } } bool TSShape::setObjectNode(const String& objName, const String& nodeName) { // Find the object and node S32 objIndex = findObject(objName); if (objIndex < 0) { Con::errorf("TSShape::setObjectNode: Could not find object '%s'", objName.c_str()); return false; } S32 nodeIndex; if (nodeName.isEmpty()) nodeIndex = -1; else { nodeIndex = findNode(nodeName); if (nodeIndex < 0) { Con::errorf("TSShape::setObjectNode: Could not find node '%s'", nodeName.c_str()); return false; } } objects[objIndex].nodeIndex = nodeIndex; return true; } bool TSShape::removeObject(const String& name) { // Find the object S32 objIndex = findObject(name); if (objIndex < 0) { Con::errorf("TSShape::removeObject: Could not find object '%s'", name.c_str()); return false; } // Need to make everything editable since node indexes etc will change makeEditable(); // Destroy all meshes in the object TSShape::Object& obj = objects[objIndex]; while ( obj.numMeshes ) { destructInPlace(meshes[obj.startMeshIndex + obj.numMeshes - 1]); removeMeshFromObject(objIndex, obj.numMeshes - 1); } // Remove the object from the shape objects.erase(objIndex); S32 subShapeIndex = getSubShapeForObject(objIndex); subShapeNumObjects[subShapeIndex]--; for (S32 i = subShapeIndex + 1; i < subShapeFirstObject.size(); i++) subShapeFirstObject[i]--; // Remove the object from all sequences for (S32 i = 0; i < sequences.size(); i++) { TSShape::Sequence& seq = sequences[i]; TSIntegerSet objMatters(seq.frameMatters); objMatters.overlap(seq.matFrameMatters); objMatters.overlap(seq.visMatters); if (objMatters.test(objIndex)) eraseStates(objectStates, objMatters, seq.baseObjectState, seq.numKeyframes, objIndex); seq.frameMatters.erase(objIndex); seq.matFrameMatters.erase(objIndex); seq.visMatters.erase(objIndex); } // Remove the object name if it is no longer in use removeName(name); // Update smallest visible detail updateSmallestVisibleDL(); initObjects(); return true; } //----------------------------------------------------------------------------- // Helper to copy a TSMesh ready for adding to this TSShape (with the right vertex format etc) TSMesh* TSShape::copyMesh( const TSMesh* srcMesh ) const { TSMesh * mesh = 0; if ( srcMesh && ( srcMesh->getMeshType() == TSMesh::SkinMeshType ) ) { TSSkinMesh* skin = new TSSkinMesh; // Copy skin elements const TSSkinMesh *srcSkin = dynamic_cast(srcMesh); skin->weight = srcSkin->weight; skin->vertexIndex = srcSkin->vertexIndex; skin->boneIndex = srcSkin->boneIndex; skin->batchData.nodeIndex = srcSkin->batchData.nodeIndex; skin->batchData.initialTransforms = srcSkin->batchData.initialTransforms; skin->batchData.initialVerts = srcSkin->batchData.initialVerts; skin->batchData.initialNorms = srcSkin->batchData.initialNorms; mesh = static_cast(skin); } else { mesh = new TSMesh; } if ( !srcMesh ) return mesh; // return an empty mesh // Copy mesh elements mesh->mIndices = srcMesh->mIndices; mesh->mPrimitives = srcMesh->mPrimitives; mesh->numFrames = srcMesh->numFrames; mesh->numMatFrames = srcMesh->numMatFrames; mesh->vertsPerFrame = srcMesh->vertsPerFrame; mesh->setFlags(srcMesh->getFlags()); mesh->mNumVerts = srcMesh->mNumVerts; // Copy vertex data in an *unpacked* form mesh->copySourceVertexDataFrom(srcMesh); mesh->createTangents(mesh->mVerts, mesh->mNorms); mesh->mEncodedNorms.set(NULL, 0); mesh->computeBounds(); return mesh; } bool TSShape::addMesh(TSMesh* mesh, const String& meshName) { // Ensure mesh is in editable state mesh->makeEditable(); // Need to make everything editable since node indexes etc will change makeEditable(); // Determine the object name and detail size from the mesh name S32 detailSize = 999; String objName(String::GetTrailingNumber(meshName, detailSize)); // Find the destination object (create one if it does not exist) S32 objIndex = findObject(objName); if (objIndex < 0) objIndex = addObject(objName, 0); AssertFatal(objIndex >= 0 && objIndex < objects.size(), "Invalid object index!"); // Determine the subshape this object belongs to S32 subShapeIndex = getSubShapeForObject(objIndex); AssertFatal(subShapeIndex < subShapeFirstObject.size(), "Could not find subshape for object!"); // Get the existing detail levels for the subshape Vector validDetails; getSubShapeDetails(subShapeIndex, validDetails); // Determine where to add the new mesh, and whether this is a new detail S32 detIndex; bool newDetail = true; for (detIndex = 0; detIndex < validDetails.size(); detIndex++) { const TSShape::Detail& det = details[validDetails[detIndex]]; if (detailSize >= det.size) { newDetail = (det.size != detailSize); break; } } // Insert the new detail level if required if (newDetail) { // Determine a name for the detail level const char* detailName; if (dStrStartsWith(objName, "Col")) detailName = "collision"; else if (dStrStartsWith(objName, "loscol")) detailName = "los"; else detailName = "detail"; S32 index = addDetail(detailName, detailSize, subShapeIndex); details[index].objectDetailNum = detIndex; } // Adding a new mesh or detail level is a bit tricky, since each // object potentially stores a different number of meshes, including // NULL meshes for higher detail levels where required. // For example, the following table shows 3 objects. Note how NULLs // must be inserted for detail levels higher than the first valid // mesh, but details after the the last valid mesh are left empty. // // Detail | Object1 | Object2 | Object3 // ---------+-----------+-----------+--------- // 128 | 128 | NULL | NULL // 64 | | NULL | 64 // 32 | | 32 | NULL // 2 | | | 2 // Add meshes as required for each object for (S32 i = 0; i < subShapeNumObjects[subShapeIndex]; i++) { S32 index = subShapeFirstObject[subShapeIndex] + i; const TSShape::Object& obj = objects[index]; if (index == objIndex) { // The target object: replace the existing mesh (if any) or add a new one // if required. if (!newDetail && (detIndex < obj.numMeshes)) { if ( meshes[obj.startMeshIndex + detIndex] ) destructInPlace(meshes[obj.startMeshIndex + detIndex]); meshes[obj.startMeshIndex + detIndex] = mesh; } else addMeshToObject(index, detIndex, mesh); } else { // Other objects: add a NULL mesh only if inserting before a valid mesh if (newDetail && (detIndex < obj.numMeshes)) addMeshToObject(index, detIndex, NULL); } } initObjects(); return true; } bool TSShape::addMesh(TSShape* srcShape, const String& srcMeshName, const String& meshName) { // Find the mesh in the source shape TSMesh* srcMesh = srcShape->findMesh(srcMeshName); if (!srcMesh) { Con::errorf("TSShape::addMesh: Could not find mesh '%s' in shape", srcMeshName.c_str()); return false; } // Copy the source mesh TSMesh *mesh = copyMesh( srcMesh ); if (srcMesh->getMeshType() == TSMesh::SkinMeshType) { TSSkinMesh *srcSkin = dynamic_cast(srcMesh); // Check that the source skin is compatible with our skeleton Vector nodeMap(srcShape->nodes.size()); for (S32 i = 0; i < srcShape->nodes.size(); i++) nodeMap.push_back( findNode( srcShape->getName(srcShape->nodes[i].nameIndex) ) ); for (S32 i = 0; i < srcSkin->boneIndex.size(); i++) { S32 srcNode = srcSkin->boneIndex[i]; if (nodeMap[srcNode] == -1) { const char* name = srcShape->getName(srcShape->nodes[srcNode].nameIndex).c_str(); Con::errorf("TSShape::addMesh: Skin is weighted to node (%s) that " "does not exist in this shape", name); return false; } } TSSkinMesh *skin = dynamic_cast(mesh); // Remap node indices skin->batchData.nodeIndex = srcSkin->batchData.nodeIndex; for (S32 i = 0; i < skin->batchData.nodeIndex.size(); i++) skin->batchData.nodeIndex[i] = nodeMap[skin->batchData.nodeIndex[i]]; } // Add the copied mesh to the shape if (!addMesh(mesh, meshName)) { delete mesh; return false; } // Copy materials used by the source mesh (only if from a different shape) if (srcShape != this) { for (S32 i = 0; i < mesh->mPrimitives.size(); i++) { if (!(mesh->mPrimitives[i].matIndex & TSDrawPrimitive::NoMaterial)) { S32 drawType = (mesh->mPrimitives[i].matIndex & (~TSDrawPrimitive::MaterialMask)); S32 srcMatIndex = mesh->mPrimitives[i].matIndex & TSDrawPrimitive::MaterialMask; const String& matName = srcShape->materialList->getMaterialName(srcMatIndex); // Add the material if it does not already exist S32 destMatIndex = materialList->getMaterialNameList().find_next(matName); if (destMatIndex < 0) { destMatIndex = materialList->size(); materialList->push_back(matName, srcShape->materialList->getFlags(srcMatIndex)); } mesh->mPrimitives[i].matIndex = drawType | destMatIndex; } } } return true; } bool TSShape::setMeshSize(const String& meshName, S32 size) { S32 objIndex, meshIndex; if (!findMeshIndex(meshName, objIndex, meshIndex) || !meshes[objects[objIndex].startMeshIndex + meshIndex]) { Con::errorf("TSShape::setMeshSize: Could not find mesh '%s'", meshName.c_str()); return false; } // Need to make everything editable since node indexes etc will change makeEditable(); // Remove the mesh from the object, but don't destroy it TSShape::Object& obj = objects[objIndex]; TSMesh* mesh = meshes[obj.startMeshIndex + meshIndex]; removeMeshFromObject(objIndex, meshIndex); // Add the mesh back at the new position addMesh(mesh, avar("%s %d", getName(obj.nameIndex).c_str(), size)); // Update smallest visible detail updateSmallestVisibleDL(); initObjects(); return true; } bool TSShape::removeMesh(const String& meshName) { S32 objIndex, meshIndex; if (!findMeshIndex(meshName, objIndex, meshIndex) || !meshes[objects[objIndex].startMeshIndex + meshIndex]) { Con::errorf("TSShape::removeMesh: Could not find mesh '%s'", meshName.c_str()); return false; } // Need to make everything editable since node indexes etc will change makeEditable(); // Destroy and remove the mesh TSShape::Object& obj = objects[objIndex]; destructInPlace(meshes[obj.startMeshIndex + meshIndex]); removeMeshFromObject(objIndex, meshIndex); // Remove the object if there are no meshes left if (!obj.numMeshes) removeObject(getName(obj.nameIndex)); // Update smallest visible detail updateSmallestVisibleDL(); initObjects(); return true; } //----------------------------------------------------------------------------- // Helper function for dealing with some of the Vectors used in a TSShape. 'meshes' // for example contains a TSMesh* per-object, per-detail-level, with NULLs for // undefined details for each object. Trailing NULLs are not added to the Vector, // so you end up with a different number of pointers for each object, depending // on which detail levels it defines. This makes it tricky to move meshes around // since you have to know how many elements belong to each object. // To simplify things, this function pads the Vector up to a certain length (so // all objects can appear to have the same number of meshes), the moves a single // element to a new index, then trims trailing NULLs again. template static void _PadMoveAndTrim(Vector& vec, S32 offset, S32 count, S32 padLength, S32 oldIndex, S32 newIndex) { // Pad the array with NULLs for ( S32 i = count; i < padLength; ++i ) vec.insert( offset + count, NULL ); // Move the element from the old to the new index T* tmp = vec[offset + oldIndex]; vec.erase( offset + oldIndex ); vec.insert( offset + newIndex, tmp ); // Trim trailing NULLs from the vector for ( S32 i = padLength - 1; i >= 0; --i ) { if ( vec[offset + i] ) break; else vec.erase( offset + i ); } } S32 TSShape::setDetailSize(S32 oldSize, S32 newSize) { S32 oldIndex = findDetailBySize( oldSize ); if ( oldIndex < 0 ) { Con::errorf( "TSShape::setDetailSize: Cannot find detail with size %d", oldSize ); return -1; } // Remove this detail from the list TSShape::Detail tmpDetail = details[oldIndex]; tmpDetail.size = newSize; details.erase(oldIndex); // Determine the new position for the detail (details are sorted by size) S32 newIndex = 0; for ( newIndex = 0; newIndex < details.size(); ++newIndex ) { if ( newSize > details[newIndex].size ) break; } // Add the detail at its new position details.insert( newIndex, tmpDetail ); // Rename the detail so its trailing size value is correct { S32 tmp; String oldName( getName( tmpDetail.nameIndex ) ); String newName( String::GetTrailingNumber( oldName, tmp ) ); newName += String::ToString( "%d", newSize ); renameDetail(oldName, newName); } if ( newIndex != oldIndex ) { // Fixup details for ( S32 iDet = 0; iDet < details.size(); iDet++ ) { if ( details[iDet].subShapeNum < 0 ) { if ( details[iDet].bbDetailLevel == oldIndex ) details[iDet].bbDetailLevel = newIndex; } else { details[iDet].objectDetailNum = iDet; } } // Fixup Billboard details _PadMoveAndTrim( billboardDetails, 0, billboardDetails.size(), details.size(), oldIndex, newIndex ); // Now move the mesh for each object in the subshape (adding and removing // NULLs as appropriate) for ( S32 iObj = 0; iObj < objects.size(); ++iObj ) { TSShape::Object& obj = objects[iObj]; S32 oldMeshCount = meshes.size(); _PadMoveAndTrim( meshes, obj.startMeshIndex, obj.numMeshes, details.size(), oldIndex, newIndex ); obj.numMeshes += ( meshes.size() - oldMeshCount ); // Fixup startMeshIndex for remaining objects for ( S32 j = iObj + 1; j < objects.size(); ++j ) objects[j].startMeshIndex += ( meshes.size() - oldMeshCount ); } } // Update smallest visible detail updateSmallestVisibleDL(); // Nothing major, just reint object lists initObjects(); return newIndex; } bool TSShape::removeDetail( S32 size ) { S32 dl = findDetailBySize( size ); if ( ( dl < 0 ) || ( dl >= details.size() ) ) { Con::errorf( "TSShape::removeDetail: Invalid detail index (%d)", dl ); return false; } // Need to make everything editable since node indexes etc will change makeEditable(); // Destroy and remove each mesh in the detail level for ( S32 objIndex = objects.size()-1; objIndex >= 0; objIndex-- ) { TSShape::Object& obj = objects[objIndex]; if ( dl < obj.numMeshes ) { if ( meshes[obj.startMeshIndex + dl] ) destructInPlace( meshes[obj.startMeshIndex + dl] ); removeMeshFromObject(objIndex, dl); // Remove the object if there are no meshes left if (!obj.numMeshes) removeObject( getName( obj.nameIndex ) ); } } // Destroy billboard detail level if ( dl < billboardDetails.size() ) { if ( billboardDetails[dl] ) { // Delete old textures billboardDetails[dl]->deleteImposterCacheTextures(); delete billboardDetails[dl]; } billboardDetails.erase( dl ); } // Update smallest visible detail updateSmallestVisibleDL(); initObjects(); return true; } //----------------------------------------------------------------------------- bool TSShape::addSequence(const Torque::Path& path, const String& fromSeq, const String& name, S32 startFrame, S32 endFrame, bool padRotKeys, bool padTransKeys) { String oldName(fromSeq); if (path.getExtension().equal("dsq", String::NoCase)) { S32 oldSeqCount = sequences.size(); // DSQ source file char filenameBuf[1024]; Con::expandScriptFilename(filenameBuf, sizeof(filenameBuf), path.getFullPath().c_str()); FileStream *f; if((f = FileStream::createAndOpen( filenameBuf, Torque::FS::File::Read )) == NULL) { Con::errorf("TSShape::addSequence: Could not load DSQ file '%s'", filenameBuf); return false; } if (!importSequences(f, filenameBuf) || (f->getStatus() != Stream::Ok)) { delete f; Con::errorf("TSShape::addSequence: Load sequence file '%s' failed", filenameBuf); return false; } delete f; // Rename the new sequence if required (avoid rename if name is not // unique (this will be fixed up later, and we don't need 2 errors about it!) if (oldName.isEmpty()) oldName = getName(sequences.last().nameIndex); if (!oldName.equal(name)) { if (findSequence(name) == -1) { // Use a dummy intermediate name since we might be renaming from an // existing name (and we want to rename the right sequence!) sequences.last().nameIndex = addName("__dummy__"); renameSequence("__dummy__", name); } } // Check that sequences have unique names bool lastSequenceRejected = false; for (S32 i = sequences.size()-1; i >= oldSeqCount; i--) { S32 nameIndex = (i == sequences.size()-1) ? findName(name) : sequences[i].nameIndex; S32 seqIndex = findSequence(nameIndex); if ((seqIndex != -1) && (seqIndex != i)) { Con::errorf("TSShape::addSequence: Failed to add sequence '%s' " "(name already exists)", getName(nameIndex).c_str()); sequences[i].nameIndex = addName("__dummy__"); removeSequence("__dummy__"); if (i == sequences.size()) lastSequenceRejected = true; } } // @todo:Need to remove keyframes if start!=0 and end!=-1 TSShape::Sequence& seq = sequences.last(); // Store information about how this sequence was created seq.sourceData.from = String::ToString("%s\t%s", filenameBuf, name.c_str()); seq.sourceData.total = seq.numKeyframes; seq.sourceData.start = ((startFrame < 0) || (startFrame >= seq.numKeyframes)) ? 0 : startFrame; seq.sourceData.end = ((endFrame < 0) || (endFrame >= seq.numKeyframes)) ? seq.numKeyframes-1 : endFrame; return (sequences.size() != oldSeqCount); } /* Check that sequence to be added does not already exist */ if (findSequence(name) != -1) { Con::errorf("TSShape::addSequence: Cannot add sequence '%s' (name already exists)", name.c_str()); return false; } Resource hSrcShape; TSShape* srcShape = this; // Assume we are copying an existing sequence if (path.getExtension().equal("dts", String::NoCase) || path.getExtension().equal("dae", String::NoCase)) { // DTS or DAE source file char filenameBuf[1024]; Con::expandScriptFilename(filenameBuf, sizeof(filenameBuf), path.getFullPath().c_str()); hSrcShape = ResourceManager::get().load(filenameBuf); if (!bool(hSrcShape)) { Con::errorf("TSShape::addSequence: Could not load source shape '%s'", path.getFullPath().c_str()); return false; } srcShape = const_cast((const TSShape*)hSrcShape); if (!srcShape->sequences.size()) { Con::errorf("TSShape::addSequence: Source shape '%s' does not contain any sequences", path.getFullPath().c_str()); return false; } // If no sequence name is specified, just use the first one if (oldName.isEmpty()) oldName = srcShape->getName(srcShape->sequences[0].nameIndex); } else { // Source is an existing sequence oldName = path.getFullPath(); } // Find the sequence S32 seqIndex = srcShape->findSequence(oldName); if (seqIndex < 0) { Con::errorf("TSShape::addSequence (%s): Could not find sequence named '%s'", path.getFullPath().c_str(), oldName.c_str()); return false; } // Check keyframe range const TSShape::Sequence* srcSeq = &srcShape->sequences[seqIndex]; if ((startFrame < 0) || (startFrame >= srcSeq->numKeyframes)) { Con::warnf("TSShape::addSequence (%s): Start keyframe (%d) out of range (0-%d) for sequence '%s'", path.getFullPath().c_str(), startFrame, srcSeq->numKeyframes-1, oldName.c_str()); startFrame = 0; } if (endFrame < 0) endFrame = srcSeq->numKeyframes - 1; else if (endFrame >= srcSeq->numKeyframes) { Con::warnf("TSShape::addSequence (%s): End keyframe (%d) out of range (0-%d) for sequence '%s'", path.getFullPath().c_str(), endFrame, srcSeq->numKeyframes-1, oldName.c_str()); endFrame = srcSeq->numKeyframes - 1; } // Create array to map source nodes to our nodes Vector nodeMap(srcShape->nodes.size()); for (S32 i = 0; i < srcShape->nodes.size(); i++) nodeMap.push_back(findNode(srcShape->getName(srcShape->nodes[i].nameIndex))); // Create array to map source objects to our objects Vector objectMap(srcShape->objects.size()); for (S32 i = 0; i < srcShape->objects.size(); i++) objectMap.push_back(findObject(srcShape->getName(srcShape->objects[i].nameIndex))); // Copy the source sequence (need to do it this ugly way instead of just // using push_back since srcSeq pointer may change if copying a sequence // from inside the shape itself sequences.increment(); TSShape::Sequence& seq = sequences.last(); srcSeq = &srcShape->sequences[seqIndex]; // update pointer as it may have changed! seq = *srcSeq; seq.nameIndex = addName(name); seq.numKeyframes = endFrame - startFrame + 1; if (seq.duration > 0) seq.duration *= ((F32)seq.numKeyframes / srcSeq->numKeyframes); // Add object states // Note: only visibility animation is supported seq.frameMatters.clearAll(); seq.matFrameMatters.clearAll(); seq.visMatters.clearAll(); for (S32 i = 0; i < objectMap.size(); i++) { if (objectMap[i] < 0) continue; if (srcSeq->visMatters.test(i)) { // Check if visibility is animated within the frames to be copied const F32 defaultVis = srcShape->objectStates[i].vis; S32 objNum = srcSeq->visMatters.count(i); for (S32 iFrame = startFrame; iFrame <= endFrame; iFrame++) { if (srcShape->getObjectState(*srcSeq, iFrame, objNum).vis != defaultVis) { seq.visMatters.set(objectMap[i]); break; } } } } TSIntegerSet srcObjectStateSet(srcSeq->frameMatters); srcObjectStateSet.overlap(srcSeq->matFrameMatters); srcObjectStateSet.overlap(srcSeq->visMatters); TSIntegerSet objectStateSet(seq.frameMatters); objectStateSet.overlap(seq.matFrameMatters); objectStateSet.overlap(seq.visMatters); seq.baseObjectState = objectStates.size(); objectStates.increment(objectStateSet.count()*seq.numKeyframes); for (S32 i = 0; i < objectMap.size(); i++) { if (objectMap[i] < 0) continue; // Note: only visibility animation is supported if (objectStateSet.test(objectMap[i])) { S32 src = srcSeq->baseObjectState + srcSeq->numKeyframes * srcObjectStateSet.count(i) + startFrame; S32 dest = seq.baseObjectState + seq.numKeyframes * objectStateSet.count(objectMap[i]); dCopyArray(&objectStates[dest], &srcShape->objectStates[src], seq.numKeyframes); } } // Add ground frames F32 ratio = (F32)seq.numKeyframes / srcSeq->numKeyframes; S32 groundBase = srcSeq->firstGroundFrame + startFrame*ratio; seq.numGroundFrames *= ratio; seq.firstGroundFrame = groundTranslations.size(); groundTranslations.reserve(mMin(groundTranslations.size() + seq.numGroundFrames, srcShape->groundTranslations.size())); groundRotations.reserve(mMin(groundRotations.size() + seq.numGroundFrames, srcShape->groundRotations.size())); for (S32 i = 0; i < seq.numGroundFrames; i++) { S32 offset = groundBase + i; if (offset >= srcShape->groundTranslations.size()) { Con::errorf("%s groundTranslations out of bounds! [%i/%i] ", path.getFullPath().c_str(), groundBase + i, srcShape->groundTranslations.size()); offset = srcShape->groundTranslations.size() - 1; } const Point3F pointValueToCopy = srcShape->groundTranslations[offset]; groundTranslations.push_back(pointValueToCopy); S32 offset2 = groundBase + i; if (offset2 >= srcShape->groundRotations.size()) { Con::errorf("%s groundRotations out of bounds! [%i/%i] ", path.getFullPath().c_str(), groundBase + i, srcShape->groundRotations.size()); offset2 = srcShape->groundRotations.size() - 1; } const Quat16 quatValueToCopy = srcShape->groundRotations[offset2]; groundRotations.push_back(quatValueToCopy); } // Add triggers seq.numTriggers = 0; seq.firstTrigger = triggers.size(); F32 seqStartPos = (F32)startFrame / seq.numKeyframes; F32 seqEndPos = (F32)endFrame / seq.numKeyframes; for (S32 i = 0; i < srcSeq->numTriggers; i++) { const TSShape::Trigger& srcTrig = srcShape->triggers[srcSeq->firstTrigger + i]; if ((srcTrig.pos >= seqStartPos) && (srcTrig.pos <= seqEndPos)) { triggers.push_back(srcTrig); triggers.last().pos -= seqStartPos; seq.numTriggers++; } } // Fixup node matters arrays seq.translationMatters.clearAll(); seq.rotationMatters.clearAll(); seq.scaleMatters.clearAll(); for (S32 i = 0; i < nodeMap.size(); i++) { if (nodeMap[i] < 0) continue; if (srcSeq->translationMatters.test(i)) { // Check if node position is animated within the frames to be copied const Point3F& defaultTrans = srcShape->defaultTranslations[i]; S32 tranNum = srcSeq->translationMatters.count(i); for (S32 iFrame = startFrame; iFrame <= endFrame; iFrame++) { if (srcShape->getTranslation(*srcSeq, iFrame, tranNum) != defaultTrans) { seq.translationMatters.set(nodeMap[i]); break; } } } if (srcSeq->rotationMatters.test(i)) { // Check if node rotation is animated within the frames to be copied const QuatF defaultRot = srcShape->defaultRotations[i].getQuatF(); S32 rotNum = srcSeq->rotationMatters.count(i); for (S32 iFrame = startFrame; iFrame <= endFrame; iFrame++) { QuatF temp; if (srcShape->getRotation(*srcSeq, iFrame, rotNum, &temp) != defaultRot) { seq.rotationMatters.set(nodeMap[i]); break; } } } if (srcSeq->scaleMatters.test(i)) { S32 scaleNum = srcSeq->scaleMatters.count(i); // Check if node scale is animated within the frames to be copied if (srcSeq->animatesArbitraryScale()) { TSScale defaultScale; defaultScale.identity(); for (S32 iFrame = startFrame; iFrame <= endFrame; iFrame++) { TSScale temp; if (!(srcShape->getArbitraryScale(*srcSeq, iFrame, scaleNum, &temp) == defaultScale)) { seq.scaleMatters.set(nodeMap[i]); break; } } } else if (srcSeq->animatesAlignedScale()) { const Point3F defaultScale(Point3F::One); for (S32 iFrame = startFrame; iFrame <= endFrame; iFrame++) { if (srcShape->getAlignedScale(*srcSeq, iFrame, scaleNum) != defaultScale) { seq.scaleMatters.set(nodeMap[i]); break; } } } else if (srcSeq->animatesUniformScale()) { const F32 defaultScale = 1.0f; for (S32 iFrame = startFrame; iFrame <= endFrame; iFrame++) { if (srcShape->getUniformScale(*srcSeq, iFrame, scaleNum) != defaultScale) { seq.scaleMatters.set(nodeMap[i]); break; } } } } } // Resize the node transform arrays seq.baseTranslation = nodeTranslations.size(); nodeTranslations.increment(seq.translationMatters.count()*seq.numKeyframes); seq.baseRotation = nodeRotations.size(); nodeRotations.increment(seq.rotationMatters.count()*seq.numKeyframes); if (seq.flags & TSShape::ArbitraryScale) { S32 scaleCount = seq.scaleMatters.count(); seq.baseScale = nodeArbitraryScaleRots.size(); nodeArbitraryScaleRots.increment(scaleCount*seq.numKeyframes); nodeArbitraryScaleFactors.increment(scaleCount*seq.numKeyframes); } else if (seq.flags & TSShape::AlignedScale) { seq.baseScale = nodeAlignedScales.size(); nodeAlignedScales.increment(seq.scaleMatters.count()*seq.numKeyframes); } else { seq.baseScale = nodeUniformScales.size(); nodeUniformScales.increment(seq.scaleMatters.count()*seq.numKeyframes); } // Add node transforms (remap from source node indices to our node indices). As // well as copying animated node translations and rotations, also handle when the // default translation and rotation are different between the source and // destination shapes. for (S32 i = 0; i < nodeMap.size(); i++) { if (nodeMap[i] < 0) continue; if (seq.translationMatters.test(nodeMap[i])) { S32 src = srcSeq->baseTranslation + srcSeq->numKeyframes * srcSeq->translationMatters.count(i) + startFrame; S32 dest = seq.baseTranslation + seq.numKeyframes * seq.translationMatters.count(nodeMap[i]); dCopyArray(&nodeTranslations[dest], &srcShape->nodeTranslations[src], seq.numKeyframes); } else if (padTransKeys && (defaultTranslations[nodeMap[i]] != srcShape->defaultTranslations[i])) { seq.translationMatters.set(nodeMap[i]); S32 dest = seq.baseTranslation + seq.numKeyframes * seq.translationMatters.count(nodeMap[i]); for (S32 j = 0; j < seq.numKeyframes; j++) nodeTranslations.insert(dest, srcShape->defaultTranslations[i]); } if (seq.rotationMatters.test(nodeMap[i])) { S32 src = srcSeq->baseRotation + srcSeq->numKeyframes * srcSeq->rotationMatters.count(i) + startFrame; S32 dest = seq.baseRotation + seq.numKeyframes * seq.rotationMatters.count(nodeMap[i]); dCopyArray(&nodeRotations[dest], &srcShape->nodeRotations[src], seq.numKeyframes); } else if (padRotKeys && (defaultRotations[nodeMap[i]] != srcShape->defaultRotations[i])) { seq.rotationMatters.set(nodeMap[i]); S32 dest = seq.baseRotation + seq.numKeyframes * seq.rotationMatters.count(nodeMap[i]); for (S32 j = 0; j < seq.numKeyframes; j++) nodeRotations.insert(dest, srcShape->defaultRotations[i]); } if (seq.scaleMatters.test(nodeMap[i])) { S32 src = srcSeq->baseScale + srcSeq->numKeyframes * srcSeq->scaleMatters.count(i)+ startFrame; S32 dest = seq.baseScale + seq.numKeyframes * seq.scaleMatters.count(nodeMap[i]); if (seq.flags & TSShape::ArbitraryScale) { dCopyArray(&nodeArbitraryScaleRots[dest], &srcShape->nodeArbitraryScaleRots[src], seq.numKeyframes); dCopyArray(&nodeArbitraryScaleFactors[dest], &srcShape->nodeArbitraryScaleFactors[src], seq.numKeyframes); } else if (seq.flags & TSShape::AlignedScale) dCopyArray(&nodeAlignedScales[dest], &srcShape->nodeAlignedScales[src], seq.numKeyframes); else dCopyArray(&nodeUniformScales[dest], &srcShape->nodeUniformScales[src], seq.numKeyframes); } } // Set shape flags (only the most significant scale type) U32 curVal = mFlags & AnyScale; mFlags &= ~(AnyScale); mFlags |= getMax(curVal, seq.flags & AnyScale); // take the larger value (can only convert upwards) // Set sequence flags seq.dirtyFlags = 0; if (seq.rotationMatters.testAll() || seq.translationMatters.testAll() || seq.scaleMatters.testAll()) seq.dirtyFlags |= TSShapeInstance::TransformDirty; if (seq.visMatters.testAll()) seq.dirtyFlags |= TSShapeInstance::VisDirty; if (seq.frameMatters.testAll()) seq.dirtyFlags |= TSShapeInstance::FrameDirty; if (seq.matFrameMatters.testAll()) seq.dirtyFlags |= TSShapeInstance::MatFrameDirty; // Store information about how this sequence was created seq.sourceData.from = String::ToString("%s\t%s", path.getFullPath().c_str(), oldName.c_str()); seq.sourceData.total = srcSeq->numKeyframes; seq.sourceData.start = startFrame; seq.sourceData.end = endFrame; return true; } bool TSShape::removeSequence(const String& name) { // Find the sequence to be removed S32 seqIndex = findSequence(name); if (seqIndex < 0) { Con::errorf("TSShape::removeSequence: Could not find sequence '%s'", name.c_str()); return false; } TSShape::Sequence& seq = sequences[seqIndex]; // Remove the node transforms for this sequence S32 transCount = eraseStates(nodeTranslations, seq.translationMatters, seq.baseTranslation, seq.numKeyframes); S32 rotCount = eraseStates(nodeRotations, seq.rotationMatters, seq.baseRotation, seq.numKeyframes); S32 scaleCount = 0; if (seq.flags & TSShape::ArbitraryScale) { scaleCount = eraseStates(nodeArbitraryScaleRots, seq.scaleMatters, seq.baseScale, seq.numKeyframes); eraseStates(nodeArbitraryScaleFactors, seq.scaleMatters, seq.baseScale, seq.numKeyframes); } else if (seq.flags & TSShape::AlignedScale) scaleCount = eraseStates(nodeAlignedScales, seq.scaleMatters, seq.baseScale, seq.numKeyframes); else scaleCount = eraseStates(nodeUniformScales, seq.scaleMatters, seq.baseScale, seq.numKeyframes); // Remove the object states for this sequence TSIntegerSet objMatters(seq.frameMatters); objMatters.overlap(seq.matFrameMatters); objMatters.overlap(seq.visMatters); S32 objCount = eraseStates(objectStates, objMatters, seq.baseObjectState, seq.numKeyframes); // Remove groundframes and triggers TSIntegerSet dummy; eraseStates(groundTranslations, dummy, seq.firstGroundFrame, seq.numGroundFrames, 0); eraseStates(groundRotations, dummy, seq.firstGroundFrame, seq.numGroundFrames, 0); eraseStates(triggers, dummy, seq.firstTrigger, seq.numTriggers, 0); // Fixup the base indices of the other sequences for (S32 i = seqIndex + 1; i < sequences.size(); i++) { sequences[i].baseTranslation -= transCount; sequences[i].baseRotation -= rotCount; sequences[i].baseScale -= scaleCount; sequences[i].baseObjectState -= objCount; sequences[i].firstGroundFrame -= seq.numGroundFrames; sequences[i].firstTrigger -= seq.numTriggers; } // Remove the sequence itself sequences.erase(seqIndex); // Remove the sequence name if it is no longer in use removeName(name); return true; } //----------------------------------------------------------------------------- bool TSShape::addTrigger(const String& seqName, S32 keyframe, S32 state) { // Find the sequence S32 seqIndex = findSequence(seqName); if (seqIndex < 0) { Con::errorf("TSShape::addTrigger: Could not find sequence '%s'", seqName.c_str()); return false; } TSShape::Sequence& seq = sequences[seqIndex]; if (keyframe >= seq.numKeyframes) { Con::errorf("TSShape::addTrigger: Keyframe out of range (0-%d for sequence '%s')", seq.numKeyframes-1, seqName.c_str()); return false; } // Encode the trigger state if (state < 0) state = 1 << (-state-1); else if (state > 0) state = (1 << (state-1)) | TSShape::Trigger::StateOn; // Fixup seq.firstTrigger if this sequence does not have any triggers yet if (seq.numTriggers == 0) { seq.firstTrigger = 0; for (S32 i = 0; i < seqIndex; i++) seq.firstTrigger += sequences[i].numTriggers; } // Find where to insert the trigger (sorted by keyframe) S32 trigIndex; for (trigIndex = seq.firstTrigger; trigIndex < (seq.firstTrigger + seq.numTriggers); trigIndex++) { const TSShape::Trigger& trig = triggers[trigIndex]; if ((S32)(trig.pos * seq.numKeyframes) > keyframe) break; } // Create the new trigger TSShape::Trigger trig; trig.pos = (F32)keyframe / getMax(1, seq.numKeyframes-1); trig.state = state; triggers.insert(trigIndex, trig); seq.numTriggers++; // set invert for other triggers if needed if ((trig.state & TSShape::Trigger::StateOn) == 0) { U32 offTrigger = (trig.state & TSShape::Trigger::StateMask); for (S32 i = 0; i < seq.numTriggers; i++) { if (triggers[seq.firstTrigger + i].state & offTrigger) triggers[seq.firstTrigger + i].state |= TSShape::Trigger::InvertOnReverse; } } // fixup firstTrigger index for other sequences for (S32 i = seqIndex + 1; i < sequences.size(); i++) { if (sequences[i].numTriggers > 0) sequences[i].firstTrigger++; } // set MakePath flag so triggers will be animated seq.flags |= TSShape::MakePath; return true; } bool TSShape::removeTrigger(const String& seqName, S32 keyframe, S32 state) { // Find the sequence S32 seqIndex = findSequence(seqName); if (seqIndex < 0) { Con::errorf("TSShape::removeTrigger: Could not find sequence '%s'", seqName.c_str()); return false; } TSShape::Sequence& seq = sequences[seqIndex]; if (keyframe >= seq.numKeyframes) { Con::errorf("TSShape::removeTrigger: Keyframe out of range (0-%d for sequence '%s')", seq.numKeyframes-1, seqName.c_str()); return false; } // Encode the trigger state if (state < 0) state = 1 << (-state-1); else if (state > 0) state = (1 << (state-1)) | TSShape::Trigger::StateOn; // Find and remove the trigger for (S32 trigIndex = seq.firstTrigger; trigIndex < (seq.firstTrigger + seq.numTriggers); trigIndex++) { TSShape::Trigger& trig = triggers[trigIndex]; S32 cmpFrame = (S32)(trig.pos * (seq.numKeyframes-1) + 0.5f); S32 cmpState = trig.state & (~TSShape::Trigger::InvertOnReverse); if ((cmpFrame == keyframe) && (cmpState == state)) { triggers.erase(trigIndex); seq.numTriggers--; // Fix up firstTrigger for other sequences for (S32 i = seqIndex + 1; i < sequences.size(); i++) { if (sequences[i].numTriggers > 0) sequences[i].firstTrigger--; } // Clear MakePath flag if no more triggers if ( seq.numTriggers == 0 ) seq.flags &= (~TSShape::MakePath); return true; } } Con::errorf("TSShape::removeTrigger: Could not find trigger (%d, %d) for sequence '%s'", keyframe, state, seqName.c_str()); return false; } void TSShape::getNodeKeyframe(S32 nodeIndex, const TSShape::Sequence& seq, S32 keyframe, MatrixF* mat) const { // Get the node rotation and translation QuatF rot; if (seq.rotationMatters.test(nodeIndex)) { S32 index = seq.rotationMatters.count(nodeIndex) * seq.numKeyframes + keyframe; nodeRotations[seq.baseRotation + index].getQuatF(&rot); } else defaultRotations[nodeIndex].getQuatF(&rot); Point3F trans; if (seq.translationMatters.test(nodeIndex)) { S32 index = seq.translationMatters.count(nodeIndex) * seq.numKeyframes + keyframe; trans = nodeTranslations[seq.baseTranslation + index]; } else trans = defaultTranslations[nodeIndex]; // Set the keyframe matrix rot.setMatrix(mat); mat->setPosition(trans); } bool TSShape::setSequenceBlend(const String& seqName, bool blend, const String& blendRefSeqName, S32 blendRefFrame) { // Find the target sequence S32 seqIndex = findSequence(seqName); if (seqIndex < 0) { Con::errorf("TSShape::setSequenceBlend: Could not find sequence named '%s'", seqName.c_str()); return false; } TSShape::Sequence& seq = sequences[seqIndex]; // Ignore if blend flag is already correct if (seq.isBlend() == blend) return true; // Find the sequence containing the reference frame S32 blendRefSeqIndex = findSequence(blendRefSeqName); if (blendRefSeqIndex < 0) { Con::errorf("TSShape::setSequenceBlend: Could not find reference sequence named '%s'", blendRefSeqName.c_str()); return false; } TSShape::Sequence& blendRefSeq = sequences[blendRefSeqIndex]; if ((blendRefFrame < 0) || (blendRefFrame >= blendRefSeq.numKeyframes)) { Con::errorf("TSShape::setSequenceBlend: Reference frame out of range (0-%d)", blendRefSeq.numKeyframes-1); return false; } // Set the new flag if (blend) seq.flags |= TSShape::Blend; else seq.flags &= (~TSShape::Blend); // For each animated node in the target sequence, need to add or subtract the // reference keyframe from each frame TSIntegerSet nodeMatters(seq.rotationMatters); nodeMatters.overlap(seq.translationMatters); S32 end = nodeMatters.end(); for (S32 nodeIndex = nodeMatters.start(); nodeIndex < end; nodeMatters.next(nodeIndex)) { MatrixF refMat; getNodeKeyframe(nodeIndex, blendRefSeq, blendRefFrame, &refMat); // Add or subtract the reference? if (blend) refMat.inverse(); bool updateRot(false), updateTrans(false); S32 rotOffset(0), transOffset(0); if (seq.rotationMatters.test(nodeIndex)) { updateRot = true; rotOffset = seq.baseRotation + seq.rotationMatters.count(nodeIndex) * seq.numKeyframes; } if (seq.translationMatters.test(nodeIndex)) { updateTrans = true; transOffset = seq.baseTranslation + seq.translationMatters.count(nodeIndex) * seq.numKeyframes; } for (S32 frame = 0; frame < seq.numKeyframes; frame++) { MatrixF oldMat; getNodeKeyframe(nodeIndex, seq, frame, &oldMat); MatrixF newMat; newMat.mul(refMat, oldMat); if (updateRot) nodeRotations[rotOffset + frame].set(QuatF(newMat)); if (updateTrans) nodeTranslations[transOffset + frame] = newMat.getPosition(); } } // Update sequence blend information seq.sourceData.blendSeq = blendRefSeqName; seq.sourceData.blendFrame = blendRefFrame; return true; } bool TSShape::setSequenceGroundSpeed(const String& seqName, const Point3F& trans, const Point3F& rot) { // Find the sequence S32 seqIndex = findSequence(seqName); if (seqIndex < 0) { Con::errorf("setSequenceGroundSpeed: Could not find sequence named '%s'", seqName.c_str()); return false; } TSShape::Sequence& seq = sequences[seqIndex]; // Determine how many ground-frames to generate (FPS=10, at least 1 frame) const F32 groundFrameRate = 10.0f; S32 numFrames = getMax(1, (S32)(seq.duration * groundFrameRate)); // Allocate space for the frames (add/delete frames as required) S32 frameAdjust = numFrames - seq.numGroundFrames; for (S32 i = 0; i < mAbs(frameAdjust); i++) { if (frameAdjust > 0) { groundTranslations.insert(seq.firstGroundFrame); groundRotations.insert(seq.firstGroundFrame); } else { groundTranslations.erase(seq.firstGroundFrame); groundRotations.erase(seq.firstGroundFrame); } } // Fixup ground frame indices seq.numGroundFrames += frameAdjust; for (S32 i = seqIndex + 1; i < sequences.size(); i++) sequences[i].firstGroundFrame += frameAdjust; // Generate the ground-frames Point3F adjTrans = trans; Point3F adjRot = rot; if (seq.numGroundFrames > 0) { adjTrans /= seq.numGroundFrames; adjRot /= seq.numGroundFrames; } QuatF rotSpeed(adjRot); QuatF groundRot(rotSpeed); for (S32 i = 0; i < seq.numGroundFrames; i++) { groundTranslations[seq.firstGroundFrame + i] = adjTrans * (i + 1); groundRotations[seq.firstGroundFrame + i].set(groundRot); groundRot *= rotSpeed; } // set MakePath flag so ground frames will be animated seq.flags |= TSShape::MakePath; return true; } void TSShape::makeEditable() { mNeedReinit = true; if (mShapeVertexData.base == NULL) return; for (U32 i = 0; i < meshes.size(); i++) { if (meshes[i]) { meshes[i]->makeEditable(); } } mShapeVertexData.set(NULL, 0); } bool TSShape::needsReinit() { return mVertexSize == 0 || mShapeVertexData.base == NULL || mNeedReinit; }