Add TransformBone and SetAnimationBoneTransform script functions

stable-5.4
Armin Burgmeier 2013-05-27 19:12:05 +02:00
parent 90ee4d2380
commit 695a9a88c3
12 changed files with 445 additions and 90 deletions

View File

@ -53,6 +53,7 @@ var swim_comb = swim_down + 1;</code>
</example>
</examples>
<related>
<funclink>TransformBone</funclink>
<funclink>StopAnimation</funclink>
<funclink>SetAnimationPosition</funclink>
<funclink>SetAnimationWeight</funclink>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE funcs
SYSTEM '../../../clonk.dtd'>
<?xml-stylesheet type="text/xsl" href="../../../clonk.xsl"?>
<funcs>
<func>
<title>SetAnimationBoneTransform</title>
<category>Animations</category>
<version>5.4 OC</version>
<syntax>
<rtype>int</rtype>
<params>
<param>
<type>int</type>
<name>animation_number</name>
<desc>Animation number of the animation whose bone transformation to change. The animation must have been created with <funclink>TransformBone</funclink>.</desc>
</param>
<param>
<type>array</type>
<name>transformation</name>
<desc>An array with 12 entries representing a 3x4 transformation matrix in row-major order. These matrices can be created via <funclink>Trans_Identity</funclink>, <funclink>Trans_Translate</funclink>, <funclink>Trans_Rotate</funclink> and <funclink>Trans_Scale</funclink> or they can be combined via <funclink>Trans_Mul</funclink>.</desc>
</param>
<param>
<type>int</type>
<name>attach_number</name>
<desc>If given, refers to the number of the attached mesh whose animation to change.</desc>
<optional />
</param>
</params>
</syntax>
<desc>This function can be used to change the transformation of a bone set with <funclink>TransformBone</funclink>. This allows to create dynamic animations by script. Returns <code>true</code> if the new transformation was set or <code>false</code> if there is no such animation node or it was not created with <funclink>TransformBone</funclink>.</desc>
<remark>See the <emlink href="definition/animations.html">animation documentation</emlink> for further explanations of the animation system.</remark>
<remark>The transformation passed to this function is not completely arbitrary, in particular it must not have components which skew the mesh along one of the axes. Skewing is not supported by the animation blending system. Skew matrices cannot be produced with one of the Trans_* functions directly, but it can result of the multiplication of a rotation matrix with a rotated scale matrix, e.g. <code><funclink>Trans_Mul</funclink>(<funclink>Trans_Rotate</funclink>(...), <funclink>Trans_Scale(...)</funclink>, <funclink>Trans_Rotate</funclink>(...))</code>. Skewing cannot occur by combining translation and rotation matrices only.</remark>
<examples>
<example>
<code><funclink>SetAnimationBoneTransform</funclink>(animation_number, <funclink>Trans_Rotate</funclink>(<funclink>FrameCounter</funclink>() % 360, 0, 1, 0));</code>
<text>Script for a timer to be called each frame: The bone for which the animation with <code>animation_number</code> was started is turning with one revolution per 360 frames.</text>
</example>
</examples>
<related>
<funclink>TransformBone</funclink>
<funclink>StopAnimation</funclink>
</related>
</func>
<author>Clonk-Karl</author><date>2013-05</date>
</funcs>

View File

@ -20,6 +20,12 @@
<name>position</name>
<desc>Specifies how to compute the position of the animation. The value needs to be created with one of the "Anim_" animation functions.</desc>
</param>
<param>
<type>int</type>
<name>attach_number</name>
<desc>If given, refers to the number of the attached mesh whose animation to change.</desc>
<optional />
</param>
</params>
</syntax>
<desc>Sets a new position for the given animation. Returns <code>true</code> if the new AVP was set or <code>false</code> if there is no such animation with the given number or the number refers to a combination node.</desc>

View File

@ -20,6 +20,12 @@
<name>weight</name>
<desc>Specifies how to compute the weight of the animation in case the animation is combined with another animation in the given slot. The value needs to be created with one of the "Anim_" animation functions.</desc>
</param>
<param>
<type>int</type>
<name>attach_number</name>
<desc>If given, refers to the number of the attached mesh whose animation to change.</desc>
<optional />
</param>
</params>
</syntax>
<desc>Sets a new weight for the given animation. Returns <code>true</code> if the new AVP was set or <code>false</code> if there is no such animation with the given number or the refernced node is an animation node.</desc>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE funcs
SYSTEM '../../../clonk.dtd'>
<?xml-stylesheet type="text/xsl" href="../../../clonk.xsl"?>
<funcs>
<func>
<title>TransformBone</title>
<category>Animations</category>
<version>5.4 OC</version>
<syntax>
<rtype>int</rtype>
<params>
<param>
<type>string</type>
<name>bone</name>
<desc>Name of the bone to be transformed.</desc>
</param>
<param>
<type>array</type>
<name>transformation</name>
<desc>An array with 12 entries representing a 3x4 transformation matrix in row-major order. These matrices can be created via <funclink>Trans_Identity</funclink>, <funclink>Trans_Translate</funclink>, <funclink>Trans_Rotate</funclink> and <funclink>Trans_Scale</funclink> or they can be combined via <funclink>Trans_Mul</funclink>.</desc>
</param>
<param>
<type>int</type>
<name>slot</name>
<desc>Slot in the animation stack in which the animation should be inserted. See <emlink href="definition/animations.html">Animations</emlink>.</desc>
</param>
<param>
<type>array</type>
<name>weight</name>
<desc>Specifies how to compute the weight of the bone transformation in case it is combined with another animation in the given slot. The value needs to be created with one of the "Anim_" animation functions.</desc>
</param>
<param>
<type>int</type>
<name>sibling</name>
<desc>If the bone transformation is combined with another animation then this refers to the node with which the new node is combined. If not given or <code>nil</code> then the animation is combined with the animation at the top of the slot as returned by <funclink>GetRootAnimation</funclink>.</desc>
<optional />
</param>
</params>
</syntax>
<desc>This function is very similar to <funclink>PlayAnimation</funclink>. Instead of playing an animation which is pre-defined in the skeleton of the mesh, it allows individual bones to be transformed arbitrarily. The transformation is inserted as a leaf node into the animation tree for the given slot. The return value of this function is the animation number of the animation node inserted which can be used to manipulate or remove the animation later. If there are already animations in the given slot then additionally a combination node is created. This combination node is assigned the returned number plus 1.</desc>
<remark>See the <emlink href="definition/animations.html">animation documentation</emlink> for further explanations of the animation system.</remark>
<remark>The transformation passed to this function is not completely arbitrary, in particular it must not have components which skew the mesh along one of the axes. Skewing is not supported by the animation blending system. Skew matrices cannot be produced with one of the Trans_* functions directly, but it can result of the multiplication of a rotation matrix with a rotated scale matrix, e.g. <code><funclink>Trans_Mul</funclink>(<funclink>Trans_Rotate</funclink>(...), <funclink>Trans_Scale(...)</funclink>, <funclink>Trans_Rotate</funclink>(...))</code>. Skewing cannot occur by combining translation and rotation matrices only.</remark>
<examples>
<example>
<code><funclink>TransformBone</funclink>("skeleton_arm_upper.R", Trans_Rotate(90, 0, 1, 0), 5, <funclink>Anim_Const</funclink>(1000));</code>
<text>Rotates the right arm of a Clonk by 90 degrees around the Y axis (in bone coordinates).</text>
</example>
</examples>
<related>
<funclink>PlayAnimation</funclink>
<funclink>StopAnimation</funclink>
<funclink>SetAnimationBoneTransform</funclink>
</related>
</func>
<author>Clonk-Karl</author><date>2013-05</date>
</funcs>

View File

@ -153,11 +153,27 @@ namespace
ValueProviderAdapt mkValueProviderAdapt(StdMeshInstance::ValueProvider** ValueProvider) { return ValueProviderAdapt(ValueProvider); }
// Serialize a bone index by name with StdCompiler
struct TransformAdapt
void CompileFloat(StdCompiler* pComp, float& f)
{
// TODO: Teach StdCompiler how to handle float
if(pComp->isCompiler())
{
C4Real r;
pComp->Value(r);
f = fixtof(r);
}
else
{
C4Real r = ftofix(f);
pComp->Value(r);
}
}
// Serialize a transformation matrix by name with StdCompiler
struct MatrixAdapt
{
StdMeshMatrix& Matrix;
TransformAdapt(StdMeshMatrix& matrix): Matrix(matrix) {}
MatrixAdapt(StdMeshMatrix& matrix): Matrix(matrix) {}
void CompileFunc(StdCompiler* pComp)
{
@ -167,30 +183,42 @@ namespace
for(unsigned int j = 0; j < 4; ++j)
{
if(i != 0 || j != 0) pComp->Separator();
// TODO: Teach StdCompiler how to handle float
// pComp->Value(Matrix(i, j));
if(pComp->isCompiler())
{
C4Real f;
pComp->Value(f);
Matrix(i,j) = fixtof(f);
}
else
{
C4Real f = ftofix(Matrix(i,j));
pComp->Value(f);
}
CompileFloat(pComp, Matrix(i, j));
}
}
pComp->Separator(StdCompiler::SEP_END);
}
ALLOW_TEMP_TO_REF(TransformAdapt)
ALLOW_TEMP_TO_REF(MatrixAdapt)
};
TransformAdapt mkTransformAdapt(StdMeshMatrix& Matrix) { return TransformAdapt(Matrix); }
struct TransformAdapt
{
StdMeshTransformation& Trans;
TransformAdapt(StdMeshTransformation& trans): Trans(trans) {}
void CompileFunc(StdCompiler* pComp)
{
pComp->Separator(StdCompiler::SEP_START);
CompileFloat(pComp, Trans.translate.x);
CompileFloat(pComp, Trans.translate.y);
CompileFloat(pComp, Trans.translate.z);
CompileFloat(pComp, Trans.rotate.w);
CompileFloat(pComp, Trans.rotate.x);
CompileFloat(pComp, Trans.rotate.y);
CompileFloat(pComp, Trans.rotate.z);
CompileFloat(pComp, Trans.scale.x);
CompileFloat(pComp, Trans.scale.y);
CompileFloat(pComp, Trans.scale.z);
pComp->Separator(StdCompiler::SEP_END);
}
ALLOW_TEMP_TO_REF(TransformAdapt);
};
MatrixAdapt mkMatrixAdapt(StdMeshMatrix& Matrix) { return MatrixAdapt(Matrix); }
TransformAdapt mkTransformAdapt(StdMeshTransformation& Trans) { return TransformAdapt(Trans); }
// Reset all animation list entries corresponding to node or its children
void ClearAnimationListRecursively(std::vector<StdMeshInstance::AnimationNode*>& list, StdMeshInstance::AnimationNode* node)
@ -530,6 +558,13 @@ StdMeshInstance::AnimationNode::AnimationNode(const StdMeshAnimation* animation,
Leaf.Position = position;
}
StdMeshInstance::AnimationNode::AnimationNode(const StdMeshBone* bone, const StdMeshTransformation& trans):
Type(CustomNode), Parent(NULL)
{
Custom.BoneIndex = bone->Index;
Custom.Transformation = new StdMeshTransformation(trans);
}
StdMeshInstance::AnimationNode::AnimationNode(AnimationNode* child_left, AnimationNode* child_right, ValueProvider* weight):
Type(LinearInterpolationNode), Parent(NULL)
{
@ -545,6 +580,9 @@ StdMeshInstance::AnimationNode::~AnimationNode()
case LeafNode:
delete Leaf.Position;
break;
case CustomNode:
delete Custom.Transformation;
break;
case LinearInterpolationNode:
delete LinearInterpolation.ChildLeft;
delete LinearInterpolation.ChildRight;
@ -566,6 +604,12 @@ bool StdMeshInstance::AnimationNode::GetBoneTransform(unsigned int bone, StdMesh
if (!track) return false;
transformation = track->GetTransformAt(fixtof(Leaf.Position->Value));
return true;
case CustomNode:
if(bone == Custom.BoneIndex)
transformation = *Custom.Transformation;
else
return false;
return true;
case LinearInterpolationNode:
if (!LinearInterpolation.ChildLeft->GetBoneTransform(bone, transformation))
return LinearInterpolation.ChildRight->GetBoneTransform(bone, transformation);
@ -585,6 +629,7 @@ void StdMeshInstance::AnimationNode::CompileFunc(StdCompiler* pComp, const StdMe
static const StdEnumEntry<NodeType> NodeTypes[] =
{
{ "Leaf", LeafNode },
{ "Custom", CustomNode },
{ "LinearInterpolation", LinearInterpolationNode },
{ NULL, static_cast<NodeType>(0) }
@ -611,6 +656,22 @@ void StdMeshInstance::AnimationNode::CompileFunc(StdCompiler* pComp, const StdMe
pComp->Value(mkNamingAdapt(mkValueProviderAdapt(&Leaf.Position), "Position"));
break;
case CustomNode:
if(pComp->isCompiler())
{
StdCopyStrBuf bone_name;
pComp->Value(mkNamingAdapt(toC4CStrBuf(bone_name), "Bone"));
const StdMeshBone* bone = Mesh->GetBoneByName(bone_name);
if(!bone) pComp->excCorrupt("No such bone: \"%s\"", bone_name.getData());
Custom.BoneIndex = bone->Index;
}
else
{
pComp->Value(mkNamingAdapt(mkParAdapt(mkDecompileAdapt(Mesh->GetBone(Custom.BoneIndex).Name), StdCompiler::RCT_All), "Bone"));
}
pComp->Value(mkNamingAdapt(mkTransformAdapt(*Custom.Transformation), "Transformation"));
break;
case LinearInterpolationNode:
pComp->Value(mkParAdapt(mkNamingPtrAdapt(LinearInterpolation.ChildLeft, "ChildLeft"), Mesh));
pComp->Value(mkParAdapt(mkNamingPtrAdapt(LinearInterpolation.ChildRight, "ChildRight"), Mesh));
@ -639,6 +700,9 @@ void StdMeshInstance::AnimationNode::DenumeratePointers()
case LeafNode:
value_provider = dynamic_cast<SerializableValueProvider*>(Leaf.Position);
break;
case CustomNode:
value_provider = NULL;
break;
case LinearInterpolationNode:
value_provider = dynamic_cast<SerializableValueProvider*>(LinearInterpolation.Weight);
// non-recursive, StdMeshInstance::DenumeratePointers walks over all nodes
@ -656,6 +720,9 @@ void StdMeshInstance::AnimationNode::ClearPointers(class C4Object* pObj)
case LeafNode:
value_provider = dynamic_cast<SerializableValueProvider*>(Leaf.Position);
break;
case CustomNode:
value_provider = NULL;
break;
case LinearInterpolationNode:
value_provider = dynamic_cast<SerializableValueProvider*>(LinearInterpolation.Weight);
// non-recursive, StdMeshInstance::ClearPointers walks over all nodes
@ -729,8 +796,8 @@ void StdMeshInstance::AttachedMesh::CompileFunc(StdCompiler* pComp, DenumeratorF
pComp->Value(mkNamingAdapt(Number, "Number"));
pComp->Value(mkNamingAdapt(ParentBone, "ParentBone")); // TODO: Save as string
pComp->Value(mkNamingAdapt(ChildBone, "ChildBone")); // TODO: Save as string (note we can only resolve this in DenumeratePointers then!)
pComp->Value(mkNamingAdapt(mkTransformAdapt(AttachTrans), "AttachTransformation"));
pComp->Value(mkNamingAdapt(mkMatrixAdapt(AttachTrans), "AttachTransformation"));
uint8_t dwSyncFlags = static_cast<uint8_t>(Flags);
pComp->Value(mkNamingAdapt(mkBitfieldAdapt(dwSyncFlags, AM_Entries), "Flags", 0u));
if(pComp->isCompiler()) Flags = dwSyncFlags;
@ -745,7 +812,7 @@ void StdMeshInstance::AttachedMesh::DenumeratePointers()
bool StdMeshInstance::AttachedMesh::ClearPointers(class C4Object* pObj)
{
ChildDenumerator->ClearPointers(pObj);
return ChildDenumerator->ClearPointers(pObj);
}
StdMeshInstance::StdMeshInstance(const StdMesh& mesh, float completion):
@ -829,70 +896,16 @@ StdMeshInstance::AnimationNode* StdMeshInstance::PlayAnimation(const StdStrBuf&
StdMeshInstance::AnimationNode* StdMeshInstance::PlayAnimation(const StdMeshAnimation& animation, int slot, AnimationNode* sibling, ValueProvider* position, ValueProvider* weight)
{
// Default
if (!sibling) sibling = GetRootAnimationForSlot(slot);
assert(!sibling || sibling->Slot == slot);
// Find two subsequent numbers in case we need to create two nodes, so
// script can deduce the second node.
unsigned int Number1, Number2;
for (Number1 = 0; Number1 < AnimationNodes.size(); ++Number1)
if (AnimationNodes[Number1] == NULL && (!sibling || Number1+1 == AnimationNodes.size() || AnimationNodes[Number1+1] == NULL))
break;
/* for(Number2 = Number1+1; Number2 < AnimationNodes.size(); ++Number2)
if(AnimationNodes[Number2] == NULL)
break;*/
Number2 = Number1 + 1;
position->Value = BoundBy(position->Value, Fix0, ftofix(animation.Length));
weight->Value = BoundBy(weight->Value, Fix0, itofix(1));
if (Number1 == AnimationNodes.size()) AnimationNodes.push_back( (StdMeshInstance::AnimationNode*) NULL);
if (sibling && Number2 == AnimationNodes.size()) AnimationNodes.push_back( (StdMeshInstance::AnimationNode*) NULL);
AnimationNode* child = new AnimationNode(&animation, position);
AnimationNodes[Number1] = child;
child->Number = Number1;
child->Slot = slot;
InsertAnimationNode(child, slot, sibling, weight);
return child;
}
if (sibling)
{
AnimationNode* parent = new AnimationNode(child, sibling, weight);
AnimationNodes[Number2] = parent;
parent->Number = Number2;
parent->Slot = slot;
child->Parent = parent;
parent->Parent = sibling->Parent;
parent->LinearInterpolation.ChildLeft = sibling;
parent->LinearInterpolation.ChildRight = child;
if (sibling->Parent)
{
if (sibling->Parent->LinearInterpolation.ChildLeft == sibling)
sibling->Parent->LinearInterpolation.ChildLeft = parent;
else
sibling->Parent->LinearInterpolation.ChildRight = parent;
}
else
{
// set new parent
AnimationNodeList::iterator iter = GetStackIterForSlot(slot, false);
// slot must not be empty, since sibling uses same slot
assert(iter != AnimationStack.end() && *iter != NULL);
*iter = parent;
}
sibling->Parent = parent;
}
else
{
delete weight;
AnimationNodeList::iterator iter = GetStackIterForSlot(slot, true);
assert(!*iter); // we have a sibling if slot is not empty
*iter = child;
}
BoneTransformsDirty = true;
StdMeshInstance::AnimationNode* StdMeshInstance::PlayAnimation(const StdMeshBone* bone, const StdMeshTransformation& trans, int slot, AnimationNode* sibling, ValueProvider* weight)
{
AnimationNode* child = new AnimationNode(bone, trans);
InsertAnimationNode(child, slot, sibling, weight);
return child;
}
@ -977,6 +990,13 @@ void StdMeshInstance::SetAnimationPosition(AnimationNode* node, ValueProvider* p
BoneTransformsDirty = true;
}
void StdMeshInstance::SetAnimationBoneTransform(AnimationNode* node, const StdMeshTransformation& trans)
{
assert(node->GetType() == AnimationNode::CustomNode);
*node->Custom.Transformation = trans;
BoneTransformsDirty = true;
}
void StdMeshInstance::SetAnimationWeight(AnimationNode* node, ValueProvider* weight)
{
assert(node->GetType() == AnimationNode::LinearInterpolationNode);
@ -1356,6 +1376,72 @@ StdMeshInstance::AnimationNodeList::iterator StdMeshInstance::GetStackIterForSlo
return AnimationStack.insert(AnimationStack.end(), NULL);
}
void StdMeshInstance::InsertAnimationNode(AnimationNode* node, int slot, AnimationNode* sibling, ValueProvider* weight)
{
// Default
if (!sibling) sibling = GetRootAnimationForSlot(slot);
assert(!sibling || sibling->Slot == slot);
// Find two subsequent numbers in case we need to create two nodes, so
// script can deduce the second node.
unsigned int Number1, Number2;
for (Number1 = 0; Number1 < AnimationNodes.size(); ++Number1)
if (AnimationNodes[Number1] == NULL && (!sibling || Number1+1 == AnimationNodes.size() || AnimationNodes[Number1+1] == NULL))
break;
/* for(Number2 = Number1+1; Number2 < AnimationNodes.size(); ++Number2)
if(AnimationNodes[Number2] == NULL)
break;*/
Number2 = Number1 + 1;
weight->Value = BoundBy(weight->Value, Fix0, itofix(1));
if (Number1 == AnimationNodes.size()) AnimationNodes.push_back( (StdMeshInstance::AnimationNode*) NULL);
if (sibling && Number2 == AnimationNodes.size()) AnimationNodes.push_back( (StdMeshInstance::AnimationNode*) NULL);
AnimationNodes[Number1] = node;
node->Number = Number1;
node->Slot = slot;
if (sibling)
{
AnimationNode* parent = new AnimationNode(node, sibling, weight);
AnimationNodes[Number2] = parent;
parent->Number = Number2;
parent->Slot = slot;
node->Parent = parent;
parent->Parent = sibling->Parent;
parent->LinearInterpolation.ChildLeft = sibling;
parent->LinearInterpolation.ChildRight = node;
if (sibling->Parent)
{
if (sibling->Parent->LinearInterpolation.ChildLeft == sibling)
sibling->Parent->LinearInterpolation.ChildLeft = parent;
else
sibling->Parent->LinearInterpolation.ChildRight = parent;
}
else
{
// set new parent
AnimationNodeList::iterator iter = GetStackIterForSlot(slot, false);
// slot must not be empty, since sibling uses same slot
assert(iter != AnimationStack.end() && *iter != NULL);
*iter = parent;
}
sibling->Parent = parent;
}
else
{
delete weight;
AnimationNodeList::iterator iter = GetStackIterForSlot(slot, true);
assert(!*iter); // we have a sibling if slot is not empty
*iter = node;
}
BoneTransformsDirty = true;
}
bool StdMeshInstance::ExecuteAnimationNode(AnimationNode* node)
{
ValueProvider* provider = NULL;
@ -1369,6 +1455,9 @@ bool StdMeshInstance::ExecuteAnimationNode(AnimationNode* node)
min = Fix0;
max = ftofix(node->GetAnimation()->Length);
break;
case AnimationNode::CustomNode:
// No execution necessary
return true;
case AnimationNode::LinearInterpolationNode:
provider = node->GetWeightProvider();
min = Fix0;
@ -1378,6 +1467,8 @@ bool StdMeshInstance::ExecuteAnimationNode(AnimationNode* node)
assert(false);
break;
}
assert(provider);
const C4Real old_value = provider->Value;
if (!provider->Execute())

View File

@ -371,10 +371,11 @@ public:
friend class StdMeshInstance;
friend class StdMeshUpdate;
public:
enum NodeType { LeafNode, LinearInterpolationNode };
enum NodeType { LeafNode, CustomNode, LinearInterpolationNode };
AnimationNode();
AnimationNode(const StdMeshAnimation* animation, ValueProvider* position);
AnimationNode(const StdMeshBone* bone, const StdMeshTransformation& trans);
AnimationNode(AnimationNode* child_left, AnimationNode* child_right, ValueProvider* weight);
~AnimationNode();
@ -412,6 +413,12 @@ public:
ValueProvider* Position;
} Leaf;
struct
{
unsigned int BoneIndex;
StdMeshTransformation* Transformation;
} Custom;
struct
{
AnimationNode* ChildLeft;
@ -490,6 +497,7 @@ public:
AnimationNode* PlayAnimation(const StdStrBuf& animation_name, int slot, AnimationNode* sibling, ValueProvider* position, ValueProvider* weight);
AnimationNode* PlayAnimation(const StdMeshAnimation& animation, int slot, AnimationNode* sibling, ValueProvider* position, ValueProvider* weight);
AnimationNode* PlayAnimation(const StdMeshBone* bone, const StdMeshTransformation& trans, int slot, AnimationNode* sibling, ValueProvider* weight);
void StopAnimation(AnimationNode* node);
AnimationNode* GetAnimationNodeByNumber(unsigned int number);
@ -498,6 +506,7 @@ public:
// Set new value providers for a node's position or weight - cannot be in
// class AnimationNode since we need to mark BoneTransforms dirty.
void SetAnimationPosition(AnimationNode* node, ValueProvider* position);
void SetAnimationBoneTransform(AnimationNode* node, const StdMeshTransformation& trans);
void SetAnimationWeight(AnimationNode* node, ValueProvider* weight);
// Update animations; call once a frame
@ -553,6 +562,7 @@ protected:
typedef std::vector<AnimationNode*> AnimationNodeList;
AnimationNodeList::iterator GetStackIterForSlot(int slot, bool create);
void InsertAnimationNode(AnimationNode* node, int slot, AnimationNode* sibling, ValueProvider* weight);
bool ExecuteAnimationNode(AnimationNode* node);
void ApplyBoneTransformToVertices(const std::vector<StdSubMesh::Vertex>& mesh_vertices, std::vector<StdMeshVertex>& instance_vertices);

View File

@ -348,6 +348,53 @@ float StdMeshMatrix::Determinant() const
- a[0][0]*a[1][2]*a[2][1] - a[0][1]*a[1][0]*a[2][2] - a[0][2]*a[1][1]*a[2][0];
}
StdMeshTransformation StdMeshMatrix::Decompose() const
{
// Extract the scale part of the matrix
const float sx = sqrt(a[0][0]*a[0][0] + a[1][0]*a[1][0] + a[2][0]*a[2][0]);
const float sy = sqrt(a[0][1]*a[0][1] + a[1][1]*a[1][1] + a[2][1]*a[2][1]);
const float sz = sqrt(a[0][2]*a[0][2] + a[1][2]*a[1][2] + a[2][2]*a[2][2]);
// What remains is the rotation part
// TODO: This can be optimized by not doing the full matrix multiplication
StdMeshMatrix rot = Scale(1.0f/sx, 1.0f/sy, 1.0f/sz) * *this;
// Note that this does not work for skew matrices -- we cannot
// represent skews in StdMeshTransformation
const float cos_angle = 0.5f * (rot.a[0][0] + rot.a[1][1] + rot.a[2][2] - 1.0f);
const float rdx = rot.a[2][1] - rot.a[1][2];
const float rdy = rot.a[0][2] - rot.a[2][0];
const float rdz = rot.a[1][0] - rot.a[0][1];
const float det = sqrt(rdx*rdx + rdy*rdy + rdz*rdz);
const float rx = (rot.a[2][1] - rot.a[1][2]) / det;
const float ry = (rot.a[0][2] - rot.a[2][0]) / det;
const float rz = (rot.a[1][0] - rot.a[0][1]) / det;
const float angle = acos(cos_angle);
StdMeshTransformation trans;
trans.scale.x = sx;
trans.scale.y = sy;
trans.scale.z = sz;
trans.rotate = StdMeshQuaternion::AngleAxis(acos(cos_angle), StdMeshVector::Translate(rx, ry, rz));
trans.translate.x = a[0][3];
trans.translate.y = a[1][3];
trans.translate.z = a[2][3];
#if 0
// Double check that the result is correct. This check will fail if
// the original matrix has skew components.
StdMeshMatrix mat2 = StdMeshMatrix::Transform(trans);
for(unsigned int i = 0; i < 3; ++i)
for(unsigned int j = 0; j < 4; ++j)
assert( fabs(mat2.a[i][j] - a[i][j]) < 1e-3);
#endif
return trans;
}
StdMeshMatrix operator*(const StdMeshMatrix& lhs, const StdMeshMatrix& rhs)
{
StdMeshMatrix m;

View File

@ -95,6 +95,7 @@ public:
float operator()(int i, int j) const { return a[i][j]; }
float Determinant() const;
StdMeshTransformation Decompose() const;
private:
// 3x3 orthogonal + translation in last column

View File

@ -82,13 +82,13 @@ void StdMeshMaterialUpdate::Add(const StdMeshMaterial* material)
}
StdMeshUpdate::StdMeshUpdate(const StdMesh& old_mesh):
OldMesh(&old_mesh), BoneNames(OldMesh->GetNumBones())
OldMesh(&old_mesh), BoneNamesByIndex(OldMesh->GetNumBones())
{
for(std::map<StdCopyStrBuf, StdMeshAnimation>::const_iterator iter = OldMesh->Animations.begin(); iter != OldMesh->Animations.end(); ++iter)
AnimationNames[&iter->second] = iter->first;
for(unsigned int i = 0; i < OldMesh->GetNumBones(); ++i)
BoneNames[i] = OldMesh->GetBone(i).Name;
BoneNamesByIndex[i] = OldMesh->GetBone(i).Name;
}
void StdMeshUpdate::Update(StdMeshInstance* instance, const StdMesh& new_mesh) const
@ -110,7 +110,7 @@ void StdMeshUpdate::Update(StdMeshInstance* instance, const StdMesh& new_mesh) c
// in the updated mesh, then detach the mesh from its parent
if(instance->AttachParent)
{
if(!instance->AttachParent->SetChildBone(BoneNames[instance->AttachParent->ChildBone]))
if(!instance->AttachParent->SetChildBone(BoneNamesByIndex[instance->AttachParent->ChildBone]))
{
bool OwnChild = instance->AttachParent->OwnChild;
instance->AttachParent->Parent->DetachMesh(instance->AttachParent->Number);
@ -127,7 +127,7 @@ void StdMeshUpdate::Update(StdMeshInstance* instance, const StdMesh& new_mesh) c
std::vector<unsigned int> Removal;
for(StdMeshInstance::AttachedMeshIter iter = instance->AttachedMeshesBegin(); iter != instance->AttachedMeshesEnd(); ++iter)
{
if(!(*iter)->SetParentBone(BoneNames[(*iter)->ParentBone]))
if(!(*iter)->SetParentBone(BoneNamesByIndex[(*iter)->ParentBone]))
{
// Do not detach the mesh right here so we can finish iterating over
// all attached meshes first.
@ -166,6 +166,15 @@ bool StdMeshUpdate::UpdateAnimationNode(StdMeshInstance* instance, const StdMesh
provider->Value = BoundBy(provider->Value, min, max);
return true;
}
case StdMeshInstance::AnimationNode::CustomNode:
{
// Update bone index by bone name
StdCopyStrBuf bone_name = BoneNamesByIndex[node->Custom.BoneIndex];
const StdMeshBone* bone = new_mesh.GetBoneByName(bone_name);
if(!bone) return false;
node->Custom.BoneIndex = bone->Index;
return true;
}
case StdMeshInstance::AnimationNode::LinearInterpolationNode:
{
const bool left_result = UpdateAnimationNode(instance, new_mesh, node->GetLeftChild());

View File

@ -64,7 +64,7 @@ private:
const StdMesh* OldMesh;
std::map<const StdMeshAnimation*, StdCopyStrBuf> AnimationNames;
std::vector<StdCopyStrBuf> BoneNames;
std::vector<StdCopyStrBuf> BoneNamesByIndex;
};
#endif

View File

@ -1830,6 +1830,56 @@ static Nillable<int> FnPlayAnimation(C4Object *Obj, C4String *szAnimation, int i
return n_node->GetNumber();
}
static Nillable<int> FnTransformBone(C4Object *Obj, C4String *szBoneName, C4ValueArray* Transformation, int iSlot, C4ValueArray* WeightProvider, Nillable<int> iSibling, Nillable<int> iAttachNumber)
{
if (!Obj) return C4Void();
if (!Obj->pMeshInstance) return C4Void();
if (iSlot == 0) return C4Void(); // Reserved for ActMap animations
if (!Transformation) return C4Void();
if (!WeightProvider) return C4Void();
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation should be set directly on that object.
if (!Attached || !Attached->OwnChild) return C4Void();
Instance = Attached->Child;
}
StdMeshInstance::AnimationNode* s_node = NULL;
if (!iSibling.IsNil())
{
s_node = Instance->GetAnimationNodeByNumber(iSibling);
if (!s_node || s_node->GetSlot() != iSlot) return C4Void();
}
const StdMeshBone* bone = Instance->GetMesh().GetBoneByName(szBoneName->GetData());
if(!bone) return C4Void();
StdMeshInstance::ValueProvider* w_provider = CreateValueProviderFromArray(Obj, *WeightProvider);
if (!w_provider) return C4Void();
StdMeshMatrix matrix;
if (!C4ValueToMatrix(*Transformation, &matrix))
throw new C4AulExecError("TransformBone: Transformation is not a valid 3x4 matrix");
// For bone transformations we cannot use general matrix transformations, but we use decomposed
// translate, scale and rotation components (represented by the StdMeshTransformation class). This
// is less generic since it does not support skewing.
// Still, in the script API we want to expose a matrix parameter so that the convenient Trans_*
// functions can be used. We decompose the passed matrix at this point. If the matrix indeed has
// skewing components, the results will probably look strange since the decomposition would yield
// bogus values, however I don't think that's a practical use case. In the worst case we could add
// a check here and return nil if the matrix cannot be decomposed.
StdMeshTransformation trans = matrix.Decompose();
StdMeshInstance::AnimationNode* n_node = Instance->PlayAnimation(bone, trans, iSlot, s_node, w_provider);
if (!n_node) return C4Void();
return n_node->GetNumber();
}
static bool FnStopAnimation(C4Object *Obj, Nillable<int> iAnimationNumber, Nillable<int> iAttachNumber)
{
if (!Obj) return false;
@ -1974,6 +2024,35 @@ static bool FnSetAnimationPosition(C4Object *Obj, Nillable<int> iAnimationNumber
return true;
}
static bool FnSetAnimationBoneTransform(C4Object *Obj, Nillable<int> iAnimationNumber, C4ValueArray* Transformation, Nillable<int> iAttachNumber)
{
if (!Obj) return false;
if (!Obj->pMeshInstance) return false;
if (iAnimationNumber.IsNil()) return false; // distinguish nil from 0
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation should be set directly on that object.
if (!Attached || !Attached->OwnChild) return false;
Instance = Attached->Child;
}
StdMeshInstance::AnimationNode* node = Instance->GetAnimationNodeByNumber(iAnimationNumber);
// slot 0 is reserved for ActMap animations
if (!node || node->GetSlot() == 0 || node->GetType() != StdMeshInstance::AnimationNode::CustomNode) return false;
StdMeshMatrix matrix;
if (!C4ValueToMatrix(*Transformation, &matrix))
throw new C4AulExecError("TransformBone: Transformation is not a valid 3x4 matrix");
// Here the same remark applies as in FnTransformBone
StdMeshTransformation trans = matrix.Decompose();
Instance->SetAnimationBoneTransform(node, trans);
return true;
}
static bool FnSetAnimationWeight(C4Object *Obj, Nillable<int> iAnimationNumber, C4ValueArray* WeightProvider, Nillable<int> iAttachNumber)
{
if (!Obj) return false;
@ -2513,6 +2592,7 @@ void InitObjectFunctionMap(C4AulScriptEngine *pEngine)
AddFunc(pEngine, "ExecuteCommand", FnExecuteCommand);
AddFunc(pEngine, "PlayAnimation", FnPlayAnimation);
AddFunc(pEngine, "TransformBone", FnTransformBone);
AddFunc(pEngine, "StopAnimation", FnStopAnimation);
AddFunc(pEngine, "GetRootAnimation", FnGetRootAnimation);
AddFunc(pEngine, "GetAnimationLength", FnGetAnimationLength);
@ -2520,6 +2600,7 @@ void InitObjectFunctionMap(C4AulScriptEngine *pEngine)
AddFunc(pEngine, "GetAnimationPosition", FnGetAnimationPosition);
AddFunc(pEngine, "GetAnimationWeight", FnGetAnimationWeight);
AddFunc(pEngine, "SetAnimationPosition", FnSetAnimationPosition);
AddFunc(pEngine, "SetAnimationBoneTransform", FnSetAnimationBoneTransform);
AddFunc(pEngine, "SetAnimationWeight", FnSetAnimationWeight);
AddFunc(pEngine, "AttachMesh", FnAttachMesh);
AddFunc(pEngine, "DetachMesh", FnDetachMesh);