mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-06-01 02:26:38 +00:00
final setup for assimp Added assimp importers and exporters, removed defaulting to all Added compression to tshape added dts version to tshape and cmake Update assimpShapeLoader.cpp quick fix fix previewing dsq ground work automatically export dsq files for animations Groundwork Adds the same sort of model for registering loaders and exporters as is set out on gbitmap Added a bit more safety around the assimp matrix fix to convert incoming models to torques coordinate system.
2197 lines
70 KiB
C++
2197 lines
70 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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"
|
|
#include "assets/assetManager.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<class T> S32 findByName(Vector<T>& 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<class T> void adjustForNameRemoval(Vector<T>& 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<class T> bool doRename(TSShape* shape, Vector<T>& 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<TSSkinMesh*>(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<class T> S32 eraseStates(Vector<T>& 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<S32> 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<TSSkinMesh*>(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<S32> 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<const TSSkinMesh*>(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<TSMesh*>(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<S32> 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<TSSkinMesh*>(srcMesh);
|
|
|
|
// Check that the source skin is compatible with our skeleton
|
|
Vector<S32> 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<TSSkinMesh*>(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<class T>
|
|
static void _PadMoveAndTrim(Vector<T*>& 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::isShapeFileType(Torque::Path filePath)
|
|
{
|
|
String fileExt = filePath.getExtension();
|
|
|
|
if (TSShape::sFindRegInfo(fileExt))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool TSShape::addSequence(const Torque::Path& path, const String& assetId, 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<TSShape> hSrcShape;
|
|
TSShape* srcShape = this; // Assume we are copying an existing sequence
|
|
|
|
if (isShapeFileType(path))
|
|
{
|
|
// 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<TSShape*>((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<S32> 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<S32> 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
|
|
String fromData = path.getFullPath();
|
|
if (assetId.isNotEmpty() && AssetDatabase.isDeclaredAsset(assetId.c_str()))
|
|
fromData = assetId;
|
|
|
|
seq.sourceData.from = String::ToString("%s\t%s", fromData.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;
|
|
}
|