mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-02-22 16:13:45 +00:00
Required changes for Inverse Kinematics
Added a * operator compute from to -> adds safeties around shortestArc conjugate -> reverses the xyz of the quaternion IK Solver commit Added: IKChain struct to tsshape commands to tsshapeconstruct to create and setup ikchains ik solvers -> ccd and fabrik, these are in their own file tsIKSolver TODO: there needs to be some tooling added to the shape editor for this
This commit is contained in:
parent
42e8687067
commit
9866908e99
10 changed files with 1048 additions and 1 deletions
|
|
@ -342,3 +342,40 @@ QuatF & QuatF::shortestArc( const VectorF &a, const VectorF &b )
|
|||
return *this;
|
||||
}
|
||||
|
||||
QuatF& QuatF::computeRotationFromTo(const VectorF& from, const VectorF& to)
|
||||
{
|
||||
VectorF f = from;
|
||||
VectorF t = to;
|
||||
|
||||
f.normalizeSafe();
|
||||
t.normalizeSafe();
|
||||
|
||||
if (f.isZero() || t.isZero())
|
||||
{
|
||||
return identity();
|
||||
}
|
||||
|
||||
F32 dot = mClampF(mDot(f, t), -1.0f, 1.0f);
|
||||
|
||||
// Parallel = no rotation.
|
||||
if (dot > 0.9999f)
|
||||
{
|
||||
return identity();
|
||||
}
|
||||
|
||||
// Opposite = pick perpendicular.
|
||||
if (dot < -0.9999f)
|
||||
{
|
||||
VectorF axis;
|
||||
if (mFabs(f.x) < mFabs(f.z))
|
||||
axis.set(0, -f.z, f.y);
|
||||
else
|
||||
axis.set(-f.y, f.x, 0);
|
||||
|
||||
axis.normalizeSafe();
|
||||
return set(axis, M_PI_F); // 180 degrees
|
||||
}
|
||||
|
||||
return shortestArc(f, t);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,10 +70,12 @@ public:
|
|||
QuatF& operator /=( F32 a );
|
||||
|
||||
QuatF operator-( const QuatF &c ) const;
|
||||
QuatF operator*(const QuatF& rhs) const;
|
||||
QuatF operator*( F32 a ) const;
|
||||
|
||||
QuatF& square();
|
||||
QuatF& neg();
|
||||
QuatF& conjugate();
|
||||
F32 dot( const QuatF &q ) const;
|
||||
|
||||
MatrixF* setMatrix( MatrixF * mat ) const;
|
||||
|
|
@ -91,6 +93,7 @@ public:
|
|||
|
||||
// Vectors passed in must be normalized
|
||||
QuatF& shortestArc( const VectorF &normalizedA, const VectorF &normalizedB );
|
||||
QuatF& computeRotationFromTo(const VectorF& from, const VectorF& to);
|
||||
};
|
||||
|
||||
// a couple simple utility methods
|
||||
|
|
@ -208,6 +211,18 @@ inline QuatF QuatF::operator -( const QuatF &c ) const
|
|||
w - c.w );
|
||||
}
|
||||
|
||||
inline QuatF QuatF::operator*(const QuatF& rhs) const
|
||||
{
|
||||
QuatF out;
|
||||
|
||||
out.w = w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z;
|
||||
out.x = w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y;
|
||||
out.y = w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x;
|
||||
out.z = w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
inline QuatF QuatF::operator *( F32 a ) const
|
||||
{
|
||||
return QuatF( x * a,
|
||||
|
|
@ -225,6 +240,14 @@ inline QuatF& QuatF::neg()
|
|||
return *this;
|
||||
}
|
||||
|
||||
inline QuatF& QuatF::conjugate()
|
||||
{
|
||||
x = -x;
|
||||
y = -y;
|
||||
z = -z;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline F32 QuatF::dot( const QuatF &q ) const
|
||||
{
|
||||
return mClampF(w*q.w + x*q.x + y*q.y + z*q.z, -1.0f, 1.0f);
|
||||
|
|
|
|||
|
|
@ -866,8 +866,21 @@ void TSShapeInstance::animate(S32 dl)
|
|||
|
||||
// animate nodes?
|
||||
if (dirtyFlags & TransformDirty)
|
||||
{
|
||||
animateNodes(ss);
|
||||
|
||||
//---------------------------------------
|
||||
// TODO: Implement different ik methods
|
||||
// add limits to ik chain nodes
|
||||
// cache bone lengths.
|
||||
//---------------------------------------
|
||||
for (U32 i = 0; i < mShape->ikChains.size(); i++)
|
||||
{
|
||||
if (mShape->ikChains[i].enabled)
|
||||
solveCCD(mShape->ikChains[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// animate objects?
|
||||
if (dirtyFlags & VisDirty)
|
||||
animateVisibility(ss);
|
||||
|
|
|
|||
292
Engine/source/ts/tsIKSolver.cpp
Normal file
292
Engine/source/ts/tsIKSolver.cpp
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
#include "ts/tsShapeInstance.h"
|
||||
|
||||
/// All ik solving happens to the global transform then is converted to localspace
|
||||
/// mNodeTransforms is the global transform vector
|
||||
/// smNodeLocalTransform is the local transform vector
|
||||
/// we do our solving after animateNodes so animations can change bone lengths
|
||||
/// but our fabrik solver does not!
|
||||
/// The book C++ Game Animation Programming 2nd Edition was used as reference.
|
||||
|
||||
Vector<F32> TSShapeInstance::smFabrikBoneLengths(__FILE__, __LINE__);
|
||||
Vector<Point3F> TSShapeInstance::smFabrikPositions(__FILE__, __LINE__);
|
||||
Vector<S32> TSShapeInstance::smIKChainNodes(__FILE__, __LINE__);
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// FABRIK FUNCTIONS
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
void TSShapeInstance::updateChildWorldTransforms(S32 node)
|
||||
{
|
||||
for (S32 child = mShape->nodes[node].firstChild; child != -1; child = mShape->nodes[child].nextSibling)
|
||||
{
|
||||
// world[child] = world[parent] * local[child]
|
||||
mNodeTransforms[child].mul(mNodeTransforms[node], smNodeLocalTransforms[child]);
|
||||
|
||||
// recurse
|
||||
updateChildWorldTransforms(child);
|
||||
}
|
||||
}
|
||||
|
||||
bool TSShapeInstance::calculateFabrikBoneLengths()
|
||||
{
|
||||
const S32 count = smIKChainNodes.size();
|
||||
|
||||
// not enough for fabrik.
|
||||
if (count < 2)
|
||||
return false;
|
||||
|
||||
smFabrikBoneLengths.clear();
|
||||
smFabrikBoneLengths.setSize(count - 1);
|
||||
|
||||
for (S32 i = 0; i < count - 1; i++)
|
||||
{
|
||||
const Point3F p0 = mNodeTransforms[smIKChainNodes[i]].getPosition();
|
||||
const Point3F p1 = mNodeTransforms[smIKChainNodes[i + 1]].getPosition();
|
||||
smFabrikBoneLengths[i] = (p1 - p0).magnitudeSafe();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TSShapeInstance::applyFabrik(TSShape::IKChain& chain)
|
||||
{
|
||||
const U32 count = smFabrikPositions.size();
|
||||
if (count < 2)
|
||||
return;
|
||||
|
||||
for (S32 i = count - 1; i > 0; i--)
|
||||
{
|
||||
S32 nodeIdx = smIKChainNodes[i];
|
||||
S32 nextNodeIdx = smIKChainNodes[i-1];
|
||||
|
||||
// Current joint global rotation & position
|
||||
QuatF curWorldRot;
|
||||
curWorldRot.set(mNodeTransforms[nodeIdx]);
|
||||
const Point3F curPos = mNodeTransforms[nodeIdx].getPosition();
|
||||
const Point3F nextPos = mNodeTransforms[nextNodeIdx].getPosition();
|
||||
|
||||
// Direction vectors
|
||||
VectorF toNext = nextPos - curPos;
|
||||
toNext.normalizeSafe();
|
||||
|
||||
VectorF toDesired = smFabrikPositions[i - 1] - smFabrikPositions[i];
|
||||
toDesired.normalizeSafe();
|
||||
|
||||
// Rotation direction -> target direction
|
||||
QuatF rotToTarget;
|
||||
rotToTarget.computeRotationFromTo(toDesired, toNext);
|
||||
|
||||
QuatF parentWorldRot;
|
||||
S32 parentIdx = mShape->nodes[nodeIdx].parentIndex;
|
||||
if (parentIdx >= 0)
|
||||
parentWorldRot.set(mNodeTransforms[parentIdx]);
|
||||
else
|
||||
parentWorldRot.identity();
|
||||
|
||||
QuatF curRotLocal = smNodeCurrentRotations[nodeIdx];
|
||||
QuatF newRotLocal = parentWorldRot * rotToTarget * parentWorldRot.conjugate();
|
||||
|
||||
QuatF blendedRot;
|
||||
|
||||
TSTransform::interpolate(curRotLocal, curRotLocal * newRotLocal, chain.weight, &blendedRot);
|
||||
|
||||
// Update local rotation & matrix
|
||||
smNodeCurrentRotations[nodeIdx] = blendedRot;
|
||||
TSTransform::setMatrix(blendedRot, smNodeCurrentTranslations[nodeIdx], &smNodeLocalTransforms[nodeIdx]);
|
||||
|
||||
// Update world transform for this node
|
||||
if (parentIdx >= 0)
|
||||
mNodeTransforms[nodeIdx].mul(mNodeTransforms[parentIdx], smNodeLocalTransforms[nodeIdx]);
|
||||
else
|
||||
mNodeTransforms[nodeIdx] = smNodeLocalTransforms[nodeIdx];
|
||||
|
||||
// Propagate world transform to children
|
||||
updateChildWorldTransforms(nodeIdx);
|
||||
}
|
||||
}
|
||||
|
||||
void TSShapeInstance::solveFabrikForward(const Point3F& target)
|
||||
{
|
||||
const U32 count = smFabrikPositions.size();
|
||||
if (count < 2)
|
||||
return;
|
||||
|
||||
smFabrikPositions[0] = target;
|
||||
|
||||
for (S32 i = 1; i < count; i++)
|
||||
{
|
||||
VectorF direction = (smFabrikPositions[i] - smFabrikPositions[i - 1]);
|
||||
direction.normalizeSafe();
|
||||
|
||||
Point3F offset = direction * smFabrikBoneLengths[i - 1];
|
||||
|
||||
smFabrikPositions[i] = smFabrikPositions[i - 1] + offset;
|
||||
}
|
||||
}
|
||||
|
||||
void TSShapeInstance::solveFabrikBackward(const Point3F& root)
|
||||
{
|
||||
const U32 count = smFabrikPositions.size();
|
||||
if (count < 2)
|
||||
return;
|
||||
|
||||
smFabrikPositions[count - 1] = root;
|
||||
|
||||
for (S32 i = count-2; i > 0; i--)
|
||||
{
|
||||
VectorF direction = (smFabrikPositions[i] - smFabrikPositions[i + 1]);
|
||||
direction.normalizeSafe();
|
||||
|
||||
Point3F offset = direction * smFabrikBoneLengths[i];
|
||||
|
||||
smFabrikPositions[i] = smFabrikPositions[i + 1] + offset;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// FABRIK FUNCTIONS END
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// MAIN SOLVER ENTRY FUNCTIONS
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
bool TSShapeInstance::solveCCD(TSShape::IKChain& chain)
|
||||
{
|
||||
PROFILE_SCOPE(TSShapeInstance_solveCCD);
|
||||
|
||||
if (!chain.enabled)
|
||||
return false;
|
||||
|
||||
// safety.
|
||||
smIKChainNodes.clear();
|
||||
smIKChainNodes = chain.nodes;
|
||||
|
||||
const U32 count = smIKChainNodes.size();
|
||||
if (count == 0) return false;
|
||||
|
||||
const S32 endNode = smIKChainNodes.first();
|
||||
const F32 threshold = chain.threshold;
|
||||
const U32 maxIterations = chain.maxIterations;
|
||||
const Point3F targetPos = mNodeTransforms[chain.targetIndex].getPosition();
|
||||
|
||||
for (U32 iter = 0; iter < maxIterations; iter++)
|
||||
{
|
||||
Point3F endPos = mNodeTransforms[endNode].getPosition();
|
||||
|
||||
// Early exit if the end effector is close enough
|
||||
if ((targetPos - endPos).magnitudeSafe() < threshold)
|
||||
return true;
|
||||
|
||||
// Iterate joints from the one before the end effector -> root
|
||||
// (exclude the end effector itself)
|
||||
for (S32 i = 1; i < count; i++)
|
||||
{
|
||||
S32 nodeIdx = smIKChainNodes[i];
|
||||
|
||||
// Current joint global rotation & position
|
||||
QuatF curWorldRot;
|
||||
curWorldRot.set(mNodeTransforms[nodeIdx]);
|
||||
const Point3F curPos = mNodeTransforms[nodeIdx].getPosition();
|
||||
|
||||
// Direction vectors
|
||||
VectorF toEnd = endPos - curPos;
|
||||
VectorF toTarget = targetPos - curPos;
|
||||
|
||||
toEnd.normalizeSafe();
|
||||
toTarget.normalizeSafe();
|
||||
|
||||
// Rotation direction -> target direction
|
||||
QuatF rotToTarget;
|
||||
rotToTarget.computeRotationFromTo(toTarget, toEnd);
|
||||
|
||||
QuatF parentWorldRot;
|
||||
S32 parentIdx = mShape->nodes[nodeIdx].parentIndex;
|
||||
if (parentIdx >= 0)
|
||||
parentWorldRot.set(mNodeTransforms[parentIdx]);
|
||||
else
|
||||
parentWorldRot.identity();
|
||||
|
||||
QuatF curRotLocal = smNodeCurrentRotations[nodeIdx];
|
||||
QuatF newRotLocal = parentWorldRot * rotToTarget * parentWorldRot.conjugate();
|
||||
|
||||
QuatF blendedRot;
|
||||
|
||||
TSTransform::interpolate(curRotLocal, curRotLocal * newRotLocal, chain.weight, &blendedRot);
|
||||
|
||||
// Update local rotation & matrix
|
||||
smNodeCurrentRotations[nodeIdx] = blendedRot;
|
||||
TSTransform::setMatrix(blendedRot, smNodeCurrentTranslations[nodeIdx], &smNodeLocalTransforms[nodeIdx]);
|
||||
|
||||
// Update world transform for this node
|
||||
if (parentIdx >= 0)
|
||||
mNodeTransforms[nodeIdx].mul(mNodeTransforms[parentIdx], smNodeLocalTransforms[nodeIdx]);
|
||||
else
|
||||
mNodeTransforms[nodeIdx] = smNodeLocalTransforms[nodeIdx];
|
||||
|
||||
// Propagate world transform to children
|
||||
updateChildWorldTransforms(nodeIdx);
|
||||
|
||||
// Check updated end effector
|
||||
endPos = mNodeTransforms[endNode].getPosition();
|
||||
if ((targetPos - endPos).magnitudeSafe() < threshold)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TSShapeInstance::solveFrabrik(TSShape::IKChain& chain)
|
||||
{
|
||||
PROFILE_SCOPE(TSShapeInstance_solveFabrik);
|
||||
if (!chain.enabled)
|
||||
return false;
|
||||
|
||||
// safety.
|
||||
smIKChainNodes.clear();
|
||||
|
||||
smIKChainNodes = chain.nodes;
|
||||
const U32 count = smIKChainNodes.size();
|
||||
|
||||
// not enough for fabrik.
|
||||
if (count < 2)
|
||||
return false;
|
||||
|
||||
// Resize working position buffer
|
||||
smFabrikPositions.setSize(count);
|
||||
|
||||
// Fill initial global positions
|
||||
for (U32 i = 0; i < count; i++)
|
||||
smFabrikPositions[i] = mNodeTransforms[smIKChainNodes[i]].getPosition();
|
||||
|
||||
if (!calculateFabrikBoneLengths())
|
||||
return false;
|
||||
|
||||
const S32 endNode = smIKChainNodes.first();
|
||||
const Point3F rootPos = smFabrikPositions[count-1];
|
||||
const Point3F targetPos = mNodeTransforms[chain.targetIndex].getPosition();
|
||||
const F32 threshold = chain.threshold;
|
||||
const U32 maxIterations = chain.maxIterations;
|
||||
|
||||
for (U32 i = 0; i < maxIterations; i++)
|
||||
{
|
||||
Point3F endPos = smFabrikPositions[0];
|
||||
// Early exit if the end effector is close enough
|
||||
if ((targetPos - endPos).magnitudeSafe() < threshold)
|
||||
{
|
||||
applyFabrik(chain);
|
||||
return true;
|
||||
}
|
||||
|
||||
solveFabrikForward(targetPos);
|
||||
solveFabrikBackward(rootPos);
|
||||
}
|
||||
|
||||
applyFabrik(chain);
|
||||
|
||||
Point3F endPos = mNodeTransforms[endNode].getPosition();
|
||||
if ((targetPos - endPos).magnitudeSafe() < threshold)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -101,6 +101,7 @@ TSShape::TSShape()
|
|||
VECTOR_SET_ASSOCIATION(billboardDetails);
|
||||
VECTOR_SET_ASSOCIATION(detailCollisionAccelerators);
|
||||
VECTOR_SET_ASSOCIATION(names);
|
||||
VECTOR_SET_ASSOCIATION(ikChains);
|
||||
|
||||
VECTOR_SET_ASSOCIATION( nodes );
|
||||
VECTOR_SET_ASSOCIATION( objects );
|
||||
|
|
@ -203,6 +204,17 @@ const String& TSShape::getSequenceName( S32 seqIndex ) const
|
|||
return names[nameIdx];
|
||||
}
|
||||
|
||||
const String& TSShape::getIKChainName(S32 ikIndex) const
|
||||
{
|
||||
AssertFatal(ikIndex >= 0 && ikIndex < ikChains.size(), "TSShape::getIKChainName index beyond range");
|
||||
|
||||
S32 nameIdx = ikChains[ikIndex].nameIndex;
|
||||
if (nameIdx < 0)
|
||||
return String::EmptyString;
|
||||
|
||||
return names[nameIdx];
|
||||
}
|
||||
|
||||
S32 TSShape::findName(const String &name) const
|
||||
{
|
||||
for (S32 i=0; i<names.size(); i++)
|
||||
|
|
@ -269,6 +281,14 @@ S32 TSShape::findSequence(S32 nameIndex) const
|
|||
return -1;
|
||||
}
|
||||
|
||||
S32 TSShape::findIKChain(S32 nameIndex) const
|
||||
{
|
||||
for (S32 i = 0; i < ikChains.size(); i++)
|
||||
if (ikChains[i].nameIndex == nameIndex)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool TSShape::findMeshIndex(const String& meshName, S32& objIndex, S32& meshIndex)
|
||||
{
|
||||
// Determine the object name and detail size from the mesh name
|
||||
|
|
|
|||
|
|
@ -289,6 +289,30 @@ class TSShape
|
|||
ConvexHullAccelerator* getAccelerator(S32 dl);
|
||||
/// @}
|
||||
|
||||
/// <summary>
|
||||
/// <c>IKChain</c> struct for building an ik chain.
|
||||
/// </summary>
|
||||
/// <para>A structure for holding our ik required data.</para>
|
||||
/// <param name="rootNode">The index of the root node.</param>
|
||||
/// <param name="endNode">The index of the end node.</param>
|
||||
/// <param name="nodes">Vector of nodes making up the chain, end->root order.</param>
|
||||
/// <param name="weight">The weight of the ik.</param>
|
||||
/// <param name="threshold">The minimum distance to consider as solved.</param>
|
||||
/// <param name="maxIterations">The maximum number of iterations to try to solve.</param>
|
||||
/// <param name="targetIndex">The index of the target node.</param>
|
||||
/// <param name="enabled"><c>True</c> if you want this chain to have an effect.</param>
|
||||
struct IKChain {
|
||||
S32 nameIndex;
|
||||
S32 rootNode;
|
||||
S32 endNode;
|
||||
Vector<S32> nodes;
|
||||
F32 weight;
|
||||
F32 threshold;
|
||||
S32 maxIterations;
|
||||
S32 targetIndex;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
|
||||
/// @name Shape Vector Data
|
||||
/// @{
|
||||
|
|
@ -346,6 +370,7 @@ class TSShape
|
|||
Vector<TSLastDetail*> billboardDetails;
|
||||
Vector<ConvexHullAccelerator*> detailCollisionAccelerators;
|
||||
Vector<String> names;
|
||||
Vector<IKChain> ikChains;
|
||||
|
||||
/// @}
|
||||
|
||||
|
|
@ -529,6 +554,9 @@ class TSShape
|
|||
/// Returns name string for sequence at the passed index.
|
||||
const String& getSequenceName( S32 seqIndex ) const;
|
||||
|
||||
// returns name string for the ikchain at the passed index.
|
||||
const String& getIKChainName(S32 ikIndex) const;
|
||||
|
||||
S32 getTargetCount() const;
|
||||
const String& getTargetName( S32 mapToNameIndex ) const;
|
||||
|
||||
|
|
@ -545,6 +573,9 @@ class TSShape
|
|||
S32 findSequence(S32 nameIndex) const;
|
||||
S32 findSequence(const String &name) const { return findSequence(findName(name)); }
|
||||
|
||||
S32 findIKChain(S32 nameIndex) const;
|
||||
S32 findIKChain(const String& name) const { return findIKChain(findName(name)); }
|
||||
|
||||
S32 getSubShapeForNode(S32 nodeIndex);
|
||||
S32 getSubShapeForObject(S32 objIndex);
|
||||
void getSubShapeDetails(S32 subShapeIndex, Vector<S32>& validDetails);
|
||||
|
|
@ -685,6 +716,21 @@ class TSShape
|
|||
bool addSequence(const Torque::Path& path, const String& assetId, const String& fromSeq, const String& name, S32 startFrame, S32 endFrame, bool padRotKeys, bool padTransKeys);
|
||||
bool removeSequence(const String& name);
|
||||
|
||||
/// <summary>
|
||||
/// Convenience function to make sure nodes are related to each other.
|
||||
/// </summary>
|
||||
/// <param name="ancestor">Ancestor node index to test.</param>
|
||||
/// <param name="descendant">Descendent node index to test.</param>
|
||||
/// <returns><c>true</c> if the nodes are related, otherwise <c>false</c>.</returns>
|
||||
bool isAncestorOf(S32 ancestor, S32 descendant) const;
|
||||
bool addIKChain(const String& name, const String& nodeA, const String& nodeB);
|
||||
bool removeIKChain(const String& name);
|
||||
bool setIKChainEnabled(const String& name, bool isEnabled);
|
||||
bool setIKChainWeight(const String& name, F32 weight);
|
||||
bool setIKChainThreshold(const String& name, F32 threshold);
|
||||
bool setIKChainMaxIterations(const String& name, S32 maxIterations);
|
||||
bool setIKChainTarget(const String& name, const String& targetNode);
|
||||
|
||||
bool addTrigger(const String& seqName, S32 keyframe, S32 state);
|
||||
bool removeTrigger(const String& seqName, S32 keyframe, S32 state);
|
||||
|
||||
|
|
|
|||
|
|
@ -679,6 +679,19 @@ TSShape::Sequence* var = &(mShape->sequences[var##Index]); \
|
|||
TORQUE_UNUSED(var##Index); \
|
||||
TORQUE_UNUSED(var);
|
||||
|
||||
// Do an IKChain lookup
|
||||
#define GET_IKCHAIN(func, var, name, ret) \
|
||||
S32 var##Index = mShape->findIKChain(name); \
|
||||
if (var##Index < 0) \
|
||||
{ \
|
||||
Con::errorf( "TSShapeConstructor::" #func ": Could not find " \
|
||||
"ikchain named '%s'", name); \
|
||||
return ret; \
|
||||
} \
|
||||
TSShape::IKChain* var = &(mShape->ikChains[var##Index]); \
|
||||
TORQUE_UNUSED(var##Index); \
|
||||
TORQUE_UNUSED(var);
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// DUMP
|
||||
|
|
@ -2298,6 +2311,164 @@ DefineEngineFunction(findShapeConstructorByFilename, S32, (const char* filename)
|
|||
return 0;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// IK CHAIN FUNCTIONS
|
||||
//------------------------------------------------------------------------------
|
||||
DefineTSShapeConstructorMethod(getIKChainCount, S32, (), , (), 0,
|
||||
"Get the total number of ikChains in the shape.\n"
|
||||
"@return the number of ikChains in the shape\n\n")
|
||||
{
|
||||
return mShape->ikChains.size();
|
||||
}}
|
||||
|
||||
DefineTSShapeConstructorMethod(getIKChainIndex, S32, (const char* name), ,
|
||||
(name), -1,
|
||||
"Find the index of the ikchain with the given name.\n"
|
||||
"@param name name of the ikchain to lookup\n"
|
||||
"@return index of the ikchain with matching name, or -1 if not found\n\n"
|
||||
"@tsexample\n"
|
||||
"// Check if a given sequence exists in the shape\n"
|
||||
"if ( %this.getIKChainIndex( \"walk\" ) == -1 )\n"
|
||||
" echo( \"Could not find 'foot_ik' ikchain\" );\n"
|
||||
"@endtsexample\n")
|
||||
{
|
||||
return mShape->findIKChain(name);
|
||||
}}
|
||||
|
||||
DefineTSShapeConstructorMethod(getIKChainName, const char*, (S32 index), ,
|
||||
(index), "",
|
||||
"Get the name of the indexed ikchain.\n"
|
||||
"@param index index of the ikchain to query (valid range is 0 - getIKChainCount()-1)\n"
|
||||
"@return the name of the ikchain\n\n"
|
||||
"@tsexample\n"
|
||||
"// print the name of all ikchain in the shape\n"
|
||||
"%count = %this.getIKChainCount();\n"
|
||||
"for ( %i = 0; %i < %count; %i++ )\n"
|
||||
" echo( %i SPC %this.getIKChainName( %i ) );\n"
|
||||
"@endtsexample\n")
|
||||
{
|
||||
CHECK_INDEX_IN_RANGE(getIKChainName, index, mShape->ikChains.size(), "");
|
||||
|
||||
return mShape->getName(mShape->ikChains[index].nameIndex);
|
||||
}}
|
||||
|
||||
DefineTSShapeConstructorMethod(addIKChain, bool, (const char* name, const char* nodeAName, const char* nodeBName), ,
|
||||
(name, nodeAName, nodeBName), false,
|
||||
"Add a new ik chain.\n"
|
||||
"@param name name of the sequence to modify\n"
|
||||
"@param nodeAName root node for the chain\n"
|
||||
"@param nodeBName end node for the chain\n"
|
||||
"@return true if successful, false otherwise\n\n"
|
||||
"@tsexample\n"
|
||||
"%this.addIKChain( \"RightLegChain\", \"right_hip\", \"right_foot\" );\n"
|
||||
"%this.addIKChain( \"LeftLegChain\", \"left_hip\", \"left_foot\" );\n"
|
||||
"@endtsexample\n")
|
||||
{
|
||||
if (!mShape->addIKChain(name, nodeAName, nodeBName))
|
||||
return false;
|
||||
|
||||
ADD_TO_CHANGE_SET();
|
||||
return true;
|
||||
}}
|
||||
|
||||
DefineTSShapeConstructorMethod(setIKChainWeight, bool, (const char* name, F32 weight), ,
|
||||
(name, weight), false,
|
||||
"Set the ikchain weight.\n"
|
||||
"@param name name of the ikchain to modify\n"
|
||||
"@param weight new weight value\n"
|
||||
"@return true if successful, false otherwise\n\n")
|
||||
{
|
||||
GET_IKCHAIN(setIKChainWeight, ikchain, name, false);
|
||||
|
||||
if (!mShape->setIKChainWeight(name, weight))
|
||||
return false;
|
||||
|
||||
ADD_TO_CHANGE_SET();
|
||||
return true;
|
||||
}}
|
||||
|
||||
DefineTSShapeConstructorMethod(setIKChainEnabled, bool, (const char* name, bool isEnabled), ,
|
||||
(name, isEnabled), false,
|
||||
"Set the ikchain enabled status.\n"
|
||||
"@param name name of the ikchain to modify\n"
|
||||
"@param isEnabled new enabled value\n"
|
||||
"@return true if successful, false otherwise\n\n")
|
||||
{
|
||||
GET_IKCHAIN(setIKChainEnabled, ikchain, name, false);
|
||||
|
||||
if (!mShape->setIKChainEnabled(name, isEnabled))
|
||||
return false;
|
||||
|
||||
ADD_TO_CHANGE_SET();
|
||||
return true;
|
||||
}}
|
||||
|
||||
DefineTSShapeConstructorMethod(setIKChainThreshold, bool, (const char* name, F32 threshold), ,
|
||||
(name, threshold), false,
|
||||
"Set the ikchain threshold.\n"
|
||||
"@param name name of the ikchain to modify\n"
|
||||
"@param threshold new threshold value\n"
|
||||
"@return true if successful, false otherwise\n\n")
|
||||
{
|
||||
GET_IKCHAIN(setIKChainThreshold, ikchain, name, false);
|
||||
|
||||
if (!mShape->setIKChainThreshold(name, threshold))
|
||||
return false;
|
||||
|
||||
ADD_TO_CHANGE_SET();
|
||||
return true;
|
||||
}}
|
||||
|
||||
DefineTSShapeConstructorMethod(setIKChainMaxIterations, bool, (const char* name, S32 iterations), ,
|
||||
(name, iterations), false,
|
||||
"Set the ikchain threshold.\n"
|
||||
"@param name name of the ikchain to modify\n"
|
||||
"@param iterations new iterations value\n"
|
||||
"@return true if successful, false otherwise\n\n")
|
||||
{
|
||||
GET_IKCHAIN(setIKChainMaxIterations, ikchain, name, false);
|
||||
|
||||
if (!mShape->setIKChainMaxIterations(name, iterations))
|
||||
return false;
|
||||
|
||||
ADD_TO_CHANGE_SET();
|
||||
return true;
|
||||
}}
|
||||
|
||||
DefineTSShapeConstructorMethod(setIKChainTarget, bool, (const char* name, const char* targetName), ,
|
||||
(name, targetName), false,
|
||||
"Set the ikchain threshold.\n"
|
||||
"@param name name of the ikchain to modify\n"
|
||||
"@param targetName new target node value\n"
|
||||
"@return true if successful, false otherwise\n\n")
|
||||
{
|
||||
GET_IKCHAIN(setIKChainTarget, ikchain, name, false);
|
||||
|
||||
if (!mShape->setIKChainTarget(name, targetName))
|
||||
return false;
|
||||
|
||||
ADD_TO_CHANGE_SET();
|
||||
return true;
|
||||
}}
|
||||
|
||||
|
||||
DefineTSShapeConstructorMethod(removeIKChain, bool, (const char* name), ,
|
||||
(name), false,
|
||||
"Remove an ikchain from the shape.\n"
|
||||
"@param name name of the ikchain to remove.\n"
|
||||
"@return true if successful, false otherwise.\n\n")
|
||||
{
|
||||
if (!mShape->removeIKChain(name))
|
||||
return false;
|
||||
|
||||
ADD_TO_CHANGE_SET();
|
||||
return true;
|
||||
}}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// IK CHAIN FUNCTIONS END
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Change-Set manipulation
|
||||
TSShapeConstructor::ChangeSet::eCommandType TSShapeConstructor::ChangeSet::getCmdType(const char* name)
|
||||
|
|
@ -2337,6 +2508,14 @@ else RETURN_IF_MATCH(SetSequenceBlend);
|
|||
else RETURN_IF_MATCH(SetSequencePriority);
|
||||
else RETURN_IF_MATCH(SetSequenceGroundSpeed);
|
||||
|
||||
else RETURN_IF_MATCH(AddIKChain);
|
||||
else RETURN_IF_MATCH(RemoveIKChain);
|
||||
else RETURN_IF_MATCH(SetIKChainWeight);
|
||||
else RETURN_IF_MATCH(SetIKChainEnabled);
|
||||
else RETURN_IF_MATCH(SetIKChainThreshold);
|
||||
else RETURN_IF_MATCH(SetIKChainMaxIterations);
|
||||
else RETURN_IF_MATCH(SetIKChainTarget);
|
||||
|
||||
else RETURN_IF_MATCH(AddTrigger);
|
||||
else RETURN_IF_MATCH(RemoveTrigger);
|
||||
|
||||
|
|
@ -2461,6 +2640,14 @@ void TSShapeConstructor::ChangeSet::add( TSShapeConstructor::ChangeSet::Command&
|
|||
case CmdRenameSequence: addCommand = addCmd_renameSequence(cmd); break;
|
||||
case CmdRemoveSequence: addCommand = addCmd_removeSequence(cmd); break;
|
||||
|
||||
case CmdAddIKChain: addCommand = addCmd_addIKChain(cmd); break;
|
||||
case CmdRemoveIKChain: addCommand = addCmd_removeIKChain(cmd); break;
|
||||
case CmdSetIKChainWeight: addCommand = addCmd_setIKChainWeight(cmd); break;
|
||||
case CmdSetIKChainEnabled: addCommand = addCmd_setIKChainEnabled(cmd); break;
|
||||
case CmdSetIKChainThreshold: addCommand = addCmd_setIKChainThreshold(cmd); break;
|
||||
case CmdSetIKChainMaxIterations: addCommand = addCmd_setIKChainMaxIterations(cmd); break;
|
||||
case CmdSetIKChainTarget: addCommand = addCmd_setIKChainTarget(cmd); break;
|
||||
|
||||
case CmdAddTrigger: addCommand = addCmd_addTrigger(cmd); break;
|
||||
case CmdRemoveTrigger: addCommand = addCmd_removeTrigger(cmd); break;
|
||||
|
||||
|
|
@ -2903,6 +3090,176 @@ bool TSShapeConstructor::ChangeSet::addCmd_removeSequence(const TSShapeConstruct
|
|||
return true;
|
||||
}
|
||||
|
||||
bool TSShapeConstructor::ChangeSet::addCmd_addIKChain(const Command& newCmd)
|
||||
{
|
||||
for (S32 index = mCommands.size() - 1; index >= 0; index--)
|
||||
{
|
||||
Command& cmd = mCommands[index];
|
||||
switch (cmd.type)
|
||||
{
|
||||
case CmdAddIKChain:
|
||||
if (namesEqual(cmd.argv[0], newCmd.argv[0]))
|
||||
{
|
||||
return false; // command already exists
|
||||
}
|
||||
break;
|
||||
case CmdRemoveIKChain:
|
||||
if (namesEqual(cmd.argv[0], newCmd.argv[0]))
|
||||
{
|
||||
mCommands.erase(index); // Remove previous removeIKChain command
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSShapeConstructor::ChangeSet::addCmd_setIKChainWeight(const Command& newCmd)
|
||||
{
|
||||
for (S32 index = mCommands.size() - 1; index >= 0; index--)
|
||||
{
|
||||
Command& cmd = mCommands[index];
|
||||
switch (cmd.type)
|
||||
{
|
||||
case CmdSetIKChainWeight:
|
||||
if (namesEqual(cmd.argv[0], newCmd.argv[0]))
|
||||
{
|
||||
cmd.argv[1] = newCmd.argv[1]; // Collapse successive set weight commands
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSShapeConstructor::ChangeSet::addCmd_setIKChainEnabled(const Command& newCmd)
|
||||
{
|
||||
for (S32 index = mCommands.size() - 1; index >= 0; index--)
|
||||
{
|
||||
Command& cmd = mCommands[index];
|
||||
switch (cmd.type)
|
||||
{
|
||||
case CmdSetIKChainEnabled:
|
||||
if (namesEqual(cmd.argv[0], newCmd.argv[0]))
|
||||
{
|
||||
cmd.argv[1] = newCmd.argv[1]; // Collapse successive set enabled commands
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSShapeConstructor::ChangeSet::addCmd_setIKChainThreshold(const Command& newCmd)
|
||||
{
|
||||
for (S32 index = mCommands.size() - 1; index >= 0; index--)
|
||||
{
|
||||
Command& cmd = mCommands[index];
|
||||
switch (cmd.type)
|
||||
{
|
||||
case CmdSetIKChainThreshold:
|
||||
if (namesEqual(cmd.argv[0], newCmd.argv[0]))
|
||||
{
|
||||
cmd.argv[1] = newCmd.argv[1]; // Collapse successive set threshold commands
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSShapeConstructor::ChangeSet::addCmd_setIKChainMaxIterations(const Command& newCmd)
|
||||
{
|
||||
for (S32 index = mCommands.size() - 1; index >= 0; index--)
|
||||
{
|
||||
Command& cmd = mCommands[index];
|
||||
switch (cmd.type)
|
||||
{
|
||||
case CmdSetIKChainMaxIterations:
|
||||
if (namesEqual(cmd.argv[0], newCmd.argv[0]))
|
||||
{
|
||||
cmd.argv[1] = newCmd.argv[1]; // Collapse successive set max iterations commands
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSShapeConstructor::ChangeSet::addCmd_setIKChainTarget(const Command& newCmd)
|
||||
{
|
||||
for (S32 index = mCommands.size() - 1; index >= 0; index--)
|
||||
{
|
||||
Command& cmd = mCommands[index];
|
||||
switch (cmd.type)
|
||||
{
|
||||
case CmdSetIKChainTarget:
|
||||
if (namesEqual(cmd.argv[0], newCmd.argv[0]))
|
||||
{
|
||||
cmd.argv[1] = newCmd.argv[1]; // Collapse successive set target commands
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSShapeConstructor::ChangeSet::addCmd_removeIKChain(const Command& newCmd)
|
||||
{
|
||||
for (S32 index = mCommands.size() - 1; index >= 0; index--)
|
||||
{
|
||||
Command& cmd = mCommands[index];
|
||||
switch (cmd.type)
|
||||
{
|
||||
case CmdAddIKChain:
|
||||
if (namesEqual(cmd.argv[0], newCmd.argv[0]))
|
||||
{
|
||||
mCommands.erase(index); // Remove previous addIKChain command
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case CmdSetIKChainWeight:
|
||||
case CmdSetIKChainEnabled:
|
||||
case CmdSetIKChainThreshold:
|
||||
case CmdSetIKChainMaxIterations:
|
||||
case CmdSetIKChainTarget:
|
||||
if (namesEqual(cmd.argv[0], newCmd.argv[0]))
|
||||
mCommands.erase(index); // Remove any commands that reference the removed sequence
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSShapeConstructor::ChangeSet::addCmd_addTrigger(const TSShapeConstructor::ChangeSet::Command& newCmd)
|
||||
{
|
||||
// Remove a matching removeTrigger command, but stop if the sequence is used as
|
||||
|
|
|
|||
|
|
@ -90,6 +90,14 @@ public:
|
|||
CmdSetSequencePriority,
|
||||
CmdSetSequenceGroundSpeed,
|
||||
|
||||
CmdAddIKChain,
|
||||
CmdRemoveIKChain,
|
||||
CmdSetIKChainWeight,
|
||||
CmdSetIKChainEnabled,
|
||||
CmdSetIKChainThreshold,
|
||||
CmdSetIKChainMaxIterations,
|
||||
CmdSetIKChainTarget,
|
||||
|
||||
CmdAddTrigger,
|
||||
CmdRemoveTrigger,
|
||||
|
||||
|
|
@ -156,6 +164,14 @@ public:
|
|||
bool addCmd_renameSequence(const Command& newCmd);
|
||||
bool addCmd_removeSequence(const Command& newCmd);
|
||||
|
||||
bool addCmd_addIKChain(const Command& newCmd);
|
||||
bool addCmd_setIKChainWeight(const Command& newCmd);
|
||||
bool addCmd_setIKChainEnabled(const Command& newCmd);
|
||||
bool addCmd_setIKChainThreshold(const Command& newCmd);
|
||||
bool addCmd_setIKChainMaxIterations(const Command& newCmd);
|
||||
bool addCmd_setIKChainTarget(const Command& newCmd);
|
||||
bool addCmd_removeIKChain(const Command& newCmd);
|
||||
|
||||
bool addCmd_addTrigger(const Command& newCmd);
|
||||
bool addCmd_removeTrigger(const Command& newCmd);
|
||||
|
||||
|
|
@ -340,6 +356,20 @@ public:
|
|||
bool removeSequence(const char* name);
|
||||
///@}
|
||||
|
||||
/// @name IKChains
|
||||
///@{
|
||||
S32 getIKChainCount();
|
||||
S32 getIKChainIndex(const char* name);
|
||||
const char* getIKChainName(S32 index);
|
||||
bool addIKChain(const char* source, const char* nodeAName, const char* nodeBName);
|
||||
bool setIKChainWeight(const char* name, F32 weight);
|
||||
bool setIKChainEnabled(const char* name, bool isEnabled);
|
||||
bool setIKChainThreshold(const char* name, F32 threshold);
|
||||
bool setIKChainMaxIterations(const char* name, S32 maxIterations);
|
||||
bool setIKChainTarget(const char* name, const char* targetNode);
|
||||
bool removeIKChain(const char* name);
|
||||
///@}
|
||||
|
||||
/// @name Triggers
|
||||
///@{
|
||||
S32 getTriggerCount(const char* name);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
#include "core/stream/fileStream.h"
|
||||
#include "core/volume.h"
|
||||
#include "assets/assetManager.h"
|
||||
|
||||
#include "ts/tsShapeConstruct.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
|
@ -1880,6 +1880,219 @@ bool TSShape::removeSequence(const String& name)
|
|||
|
||||
return true;
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool TSShape::isAncestorOf(S32 ancestor, S32 descendant) const
|
||||
{
|
||||
S32 n = descendant;
|
||||
while (n != -1)
|
||||
{
|
||||
if (n == ancestor)
|
||||
return true;
|
||||
n = nodes[n].parentIndex;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TSShape::addIKChain(const String& name, const String& nodeAName, const String& nodeBName)
|
||||
{
|
||||
// Check that there is not already an ikchain with this name
|
||||
if (findIKChain(name) >= 0)
|
||||
{
|
||||
Con::errorf("TSShape::addIKChain: %s already exists!", name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
S32 nodeA = findNode(nodeAName);
|
||||
S32 nodeB = findNode(nodeBName);
|
||||
|
||||
if (nodeA < 0)
|
||||
{
|
||||
Con::errorf("TSShape::addIKChain: bone '%s' not found.", nodeAName.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nodeB < 0)
|
||||
{
|
||||
Con::errorf("TSShape::addIKChain: bone '%s' not found.", nodeBName.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
S32 rootNode = -1;
|
||||
S32 endNode = -1;
|
||||
|
||||
if (isAncestorOf(nodeA, nodeB))
|
||||
{
|
||||
rootNode = nodeA;
|
||||
endNode = nodeB;
|
||||
}
|
||||
else if (isAncestorOf(nodeB, nodeA))
|
||||
{
|
||||
rootNode = nodeB;
|
||||
endNode = nodeA;
|
||||
}
|
||||
else
|
||||
{
|
||||
Con::errorf("TSShape::addIKChain: '%s' and '%s' are not related in the skeleton hierarchy.",
|
||||
nodeAName.c_str(), nodeBName.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector<S32> chainNodes;
|
||||
S32 n = endNode;
|
||||
// loop through parents to get the root.
|
||||
while (n != -1)
|
||||
{
|
||||
chainNodes.push_back(n);
|
||||
if (n == rootNode)
|
||||
break;
|
||||
n = nodes[n].parentIndex;
|
||||
}
|
||||
|
||||
String targetNodeName = name + "_target";
|
||||
S32 targetNode = findNode(targetNodeName);
|
||||
if (targetNode < 0)
|
||||
{
|
||||
Con::warnf("TSShape::addIKChain: default target node '%s' not found, adding one.", targetNodeName.c_str());
|
||||
TransformF txfm = TransformF::Identity;
|
||||
Point3F pos(txfm.getPosition());
|
||||
QuatF rot(txfm.getOrientation());
|
||||
addNode(targetNodeName, "", pos, rot);
|
||||
|
||||
targetNode = findNode(targetNodeName);
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
// Create IK Chain.
|
||||
//------------------------------------
|
||||
IKChain chain;
|
||||
chain.nameIndex = addName(name);
|
||||
chain.rootNode = rootNode;
|
||||
chain.endNode = endNode;
|
||||
chain.nodes = chainNodes;
|
||||
chain.weight = 1.0f;
|
||||
chain.threshold = 0.1f;
|
||||
chain.maxIterations = 10;
|
||||
chain.targetIndex = targetNode;
|
||||
chain.enabled = true;
|
||||
|
||||
ikChains.push_back(chain);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSShape::removeIKChain(const String& name)
|
||||
{
|
||||
// Find the default target node, only remove this node as the ikchain added it.
|
||||
String targetNodeName = name + "_target";
|
||||
S32 targetNode = findNode(targetNodeName);
|
||||
if (targetNode >= 0)
|
||||
{
|
||||
removeNode(targetNodeName);
|
||||
}
|
||||
|
||||
// Find the ikchain to be removed
|
||||
S32 ikChainIndex = findIKChain(name);
|
||||
if (ikChainIndex < 0)
|
||||
{
|
||||
Con::errorf("TSShape::removeIKChain: Could not find ikchain '%s'", name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
ikChains.erase(ikChainIndex);
|
||||
|
||||
// Remove the ikchain name if it is no longer in use
|
||||
removeName(name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSShape::setIKChainEnabled(const String& name, bool isEnabled)
|
||||
{
|
||||
S32 ikIndex = findIKChain(name);
|
||||
if (ikIndex < 0)
|
||||
{
|
||||
Con::errorf("TSShape::setIKChainEnabled: Could not find ikchain named '%s'", name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
TSShape::IKChain& ikChain = ikChains[ikIndex];
|
||||
|
||||
ikChain.enabled = isEnabled;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSShape::setIKChainWeight(const String& name, F32 weight)
|
||||
{
|
||||
S32 ikIndex = findIKChain(name);
|
||||
if (ikIndex < 0)
|
||||
{
|
||||
Con::errorf("TSShape::setIKChainEnabled: Could not find ikchain named '%s'", name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
TSShape::IKChain& ikChain = ikChains[ikIndex];
|
||||
|
||||
ikChain.weight = weight;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSShape::setIKChainThreshold(const String& name, F32 threshold)
|
||||
{
|
||||
S32 ikIndex = findIKChain(name);
|
||||
if (ikIndex < 0)
|
||||
{
|
||||
Con::errorf("TSShape::setIKChainThreshold: Could not find ikchain named '%s'", name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
TSShape::IKChain& ikChain = ikChains[ikIndex];
|
||||
|
||||
ikChain.threshold = threshold;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSShape::setIKChainMaxIterations(const String& name, S32 maxIterations)
|
||||
{
|
||||
S32 ikIndex = findIKChain(name);
|
||||
if (ikIndex < 0)
|
||||
{
|
||||
Con::errorf("TSShape::setIKChainMaxIterations: Could not find ikchain named '%s'", name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
TSShape::IKChain& ikChain = ikChains[ikIndex];
|
||||
|
||||
ikChain.maxIterations = maxIterations;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TSShape::setIKChainTarget(const String& name, const String& targetNode)
|
||||
{
|
||||
S32 ikIndex = findIKChain(name);
|
||||
if (ikIndex < 0)
|
||||
{
|
||||
Con::errorf("TSShape::setIKChainTarget: Could not find ikchain named '%s'", name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
TSShape::IKChain& ikChain = ikChains[ikIndex];
|
||||
|
||||
S32 nodeIndex = findNode(targetNode);
|
||||
if (nodeIndex < 0)
|
||||
{
|
||||
Con::errorf("TSShape::setIKChainTarget: Could not find target node named '%s'", targetNode.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
ikChain.targetIndex = nodeIndex;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -584,6 +584,21 @@ protected:
|
|||
void setDirty(U32 dirty);
|
||||
void clearDirty(U32 dirty);
|
||||
|
||||
/// @name IKSolver functions
|
||||
/// @{
|
||||
static Vector<F32> smFabrikBoneLengths;
|
||||
static Vector<Point3F> smFabrikPositions;
|
||||
static Vector<S32> smIKChainNodes;
|
||||
void updateChildWorldTransforms(S32 node);
|
||||
|
||||
bool solveCCD(TSShape::IKChain& chain);
|
||||
bool calculateFabrikBoneLengths();
|
||||
void applyFabrik(TSShape::IKChain& chain);
|
||||
void solveFabrikForward(const Point3F& target);
|
||||
void solveFabrikBackward(const Point3F& root);
|
||||
bool solveFrabrik(TSShape::IKChain& chain);
|
||||
/// @}
|
||||
|
||||
//-------------------------------------------------------------------------------------
|
||||
// collision interface routines
|
||||
//-------------------------------------------------------------------------------------
|
||||
|
|
@ -824,6 +839,7 @@ public:
|
|||
U32 getSeqIndex() const { return sequence; }
|
||||
const TSSequence* getSequence() const { return &(mShapeInstance->mShape->sequences[sequence]); }
|
||||
const String& getSequenceName() const { return mShapeInstance->mShape->getSequenceName(sequence); }
|
||||
const String& getIKChainName(S32 ikIndex) const { return mShapeInstance->mShape->getIKChainName(ikIndex); }
|
||||
S32 operator<(const TSThread &) const;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue