Allow a mesh to play multiple animations at the same time

stable-5.2
Armin Burgmeier 2009-12-30 21:41:56 +01:00
parent 6c00302928
commit 7c5619646c
5 changed files with 267 additions and 61 deletions

View File

@ -1,5 +1,52 @@
#strict 2
static clonk;
func Initialize()
{
CreateObject(MONS, LandscapeWidth()/2, LandscapeHeight()/2);
//CreateObject(MONS, LandscapeWidth()/2, LandscapeHeight()/2);
clonk = CreateObject(CLNK, LandscapeWidth()/2, LandscapeHeight()/4);
clonk->SetAction("Idle");
clonk->AnimationPlay("Walk", 1000);
clonk->SetObjDrawTransform(500,0,500,0,1000,0);
AddEffect("IntAnimPlay", clonk, 1, 1);
}
global func FxIntAnimPlayTimer(object target, int number, int time)
{
var walk_pos = (time % 50) * 2400 / 50; // Walk animation ranges to 2400
if(!EffectVar(0, target, number))
{
// Let the walk animation last 50 frames
target->AnimationSetState("Walk", walk_pos);
if(!Random(100))
{
// Transition to Jump animation
EffectVar(0, target, number) = time;
target->AnimationPlay("Jump", 0);
}
}
else
{
// Jump animation lasts 20 frames
if(time - EffectVar(0, target, number) <= 20)
{
var off = time - EffectVar(0, target, number);
var walk_weight = 1000 - 50*off;
var jump_pos = off * 1000 / 20; // Jump animation ranges to 1000
target->AnimationSetState("Walk", walk_pos, walk_weight);
target->AnimationSetState("Jump", jump_pos, 1000 - walk_weight);
}
else
{
// Hold until 50 frames
if(time - EffectVar(0, target, number) > 50)
{
target->AnimationSetState("Walk", walk_pos, 1000);
target->AnimationStop("Jump");
EffectVar(0, target, number) = 0;
}
}
}
}

View File

@ -2151,7 +2151,7 @@ C4Value C4Object::Call(const char *szFunctionCall, C4AulParSet *pPars, bool fPas
}
bool C4Object::SetPhase(int32_t iPhase)
{
{
if (!Action.pActionDef) return false;
const int32_t length = Action.pActionDef->GetPropertyInt(P_Length);
@ -2161,15 +2161,23 @@ bool C4Object::SetPhase(int32_t iPhase)
Action.PhaseDelay = 0;
if(pMeshInstance)
{
C4String* AnimationName = Action.pActionDef->GetPropertyStr(P_Animation);
if(AnimationName)
{
if(delay)
pMeshInstance->SetPosition(static_cast<float>(Action.Phase * delay + Action.PhaseDelay) / (delay * length) * pMeshInstance->GetAnimation()->Length);
else
pMeshInstance->SetPosition(static_cast<float>(Action.Phase) / length * pMeshInstance->GetAnimation()->Length);
StdMeshInstance::AnimationRef ref(pMeshInstance, AnimationName->GetData());
if(ref)
{
if(delay)
ref.SetPosition(static_cast<float>(Action.Phase * delay + Action.PhaseDelay) / (delay * length) * ref.GetAnimation().Length);
else
ref.SetPosition(static_cast<float>(Action.Phase) / length * ref.GetAnimation().Length);
}
}
}
return true;
}
}
void C4Object::Draw(C4TargetFacet &cgo, int32_t iByPlayer, DrawMode eDrawMode)
{
@ -3904,10 +3912,22 @@ bool C4Object::SetAction(C4PropList * Act, C4Object *pTarget, C4Object *pTarget2
if(pMeshInstance)
{
C4String* Animation = Act ? Act->GetPropertyStr(P_Animation) : NULL;
if(!Animation || Animation->GetData() == "")
pMeshInstance->UnsetAnimation();
else if(!pMeshInstance->SetAnimationByName(Animation->GetData()))
return false;
C4String* OldAnimation = LastAction ? LastAction->GetPropertyStr(P_Animation) : NULL;
if(OldAnimation)
pMeshInstance->StopAnimation(OldAnimation->GetData());
if(Animation)
{
// overwrite existing animation, if any (maybe launched by script)
StdMeshInstance::AnimationRef ref(pMeshInstance, Animation->GetData());
if(ref)
{
ref.SetPosition(0.0f);
ref.SetWeight(1.0f);
}
else
pMeshInstance->PlayAnimation(Animation->GetData(), 1.0f);
}
}
// Stop previous act sound
if (LastAction)
@ -5212,9 +5232,21 @@ void C4Object::ExecAction()
}
// Update animation on mesh instance. If a new action was set,
// then will already have happened for the new action.
if(pMeshInstance && pMeshInstance->GetAnimation() && !set_new_action)
pMeshInstance->SetPosition(static_cast<float>(Action.Phase * pAction->GetPropertyInt(P_Delay) + Action.PhaseDelay) / (pAction->GetPropertyInt(P_Delay) * pAction->GetPropertyInt(P_Length)) * pMeshInstance->GetAnimation()->Length);
// then this will already have happened for the new action.
if(pMeshInstance && !set_new_action)
{
C4String* AnimationName = pAction->GetPropertyStr(P_Animation);
if(AnimationName)
{
StdMeshInstance::AnimationRef ref(pMeshInstance, AnimationName->GetData());
if(ref)
{
float delay = pAction->GetPropertyInt(P_Delay);
float length = pAction->GetPropertyInt(P_Length);
ref.SetPosition(static_cast<float>(Action.Phase * delay + Action.PhaseDelay) / (delay * length) * ref.GetAnimation().Length);
}
}
}
}
return;

View File

@ -5431,6 +5431,43 @@ static bool FnSetNextMission(C4AulContext *ctx, C4String *szNextMission, C4Strin
return true;
}
static bool FnAnimationPlay(C4AulContext *ctx, C4String *szAnimation, Nillable<long> weight)
{
if(!ctx->Obj) return false;
if(!ctx->Obj->pMeshInstance) return false;
float w = 1.0f;
if(!weight.IsNil()) w = weight / 1000.0f;
return ctx->Obj->pMeshInstance->PlayAnimation(szAnimation->GetData(), w);
}
static bool FnAnimationStop(C4AulContext *ctx, C4String *szAnimation)
{
if(!ctx->Obj) return false;
if(!ctx->Obj->pMeshInstance) return false;
return ctx->Obj->pMeshInstance->StopAnimation(szAnimation->GetData());
}
static bool FnAnimationSetState(C4AulContext *ctx, C4String *szAnimation, Nillable<long> position, Nillable<long> weight)
{
if(!ctx->Obj) return false;
if(!ctx->Obj->pMeshInstance) return false;
StdMeshInstance::AnimationRef ref(ctx->Obj->pMeshInstance, szAnimation->GetData());
if(!ref) return false;
if(!position.IsNil())
{
float pos = position / 1000.0f;
if(pos > ref.GetAnimation().Length) return false;
ref.SetPosition(pos);
}
if(!weight.IsNil())
ref.SetWeight(weight / 1000.0f);
return true;
}
//=========================== C4Script Function Map ===================================
// defined function class
@ -5902,6 +5939,10 @@ void InitFunctionMap(C4AulScriptEngine *pEngine)
AddFunc(pEngine, "SetNextMission", FnSetNextMission);
//FIXME new C4AulDefCastFunc(pEngine, "ScoreboardCol", C4V_C4ID, C4V_Int);
AddFunc(pEngine, "AnimationPlay", FnAnimationPlay);
AddFunc(pEngine, "AnimationStop", FnAnimationStop);
AddFunc(pEngine, "AnimationSetState", FnAnimationSetState);
AddFunc(pEngine, "goto", Fn_goto);
AddFunc(pEngine, "this", Fn_this);
AddFunc(pEngine, "ChangeDef", FnChangeDef);

View File

@ -753,8 +753,46 @@ const StdMeshAnimation* StdMesh::GetAnimationByName(const StdStrBuf& name) const
return &iter->second;
}
StdMeshInstance::AnimationRef::AnimationRef(StdMeshInstance* instance, const StdStrBuf& animation_name):
Instance(instance), Anim(NULL), Changed(false)
{
const StdMeshAnimation* animation = instance->Mesh.GetAnimationByName(animation_name);
if(animation)
{
for(unsigned int i = 0; i < instance->Animations.size(); ++i)
if(instance->Animations[i].Animation == animation)
{ Anim = &instance->Animations[i]; break; }
}
}
StdMeshInstance::AnimationRef::AnimationRef(StdMeshInstance* instance, const StdMeshAnimation& animation):
Instance(instance), Anim(NULL), Changed(false)
{
for(unsigned int i = 0; i < instance->Animations.size(); ++i)
if(instance->Animations[i].Animation == &animation)
{ Anim = &instance->Animations[i]; break; }
}
const StdMeshAnimation& StdMeshInstance::AnimationRef::GetAnimation() const
{
return *Anim->Animation;
}
void StdMeshInstance::AnimationRef::SetPosition(float position)
{
assert(position <= Anim->Animation.Length);
Anim->Position = position;
Changed = true;
}
void StdMeshInstance::AnimationRef::SetWeight(float weight)
{
Anim->Weight = weight;
Changed = true;
}
StdMeshInstance::StdMeshInstance(const StdMesh& mesh):
Mesh(mesh), CurrentFaceOrdering(FO_Fixed), Animation(NULL), Position(0.0f),
Mesh(mesh), CurrentFaceOrdering(FO_Fixed),
BoneTransforms(Mesh.GetNumBones()), Vertices(Mesh.GetNumVertices()),
Faces(Mesh.GetNumFaces())
{
@ -772,71 +810,83 @@ void StdMeshInstance::SetFaceOrdering(FaceOrdering ordering)
ReorderFaces();
}
bool StdMeshInstance::SetAnimationByName(const StdStrBuf& animation_name)
bool StdMeshInstance::PlayAnimation(const StdStrBuf& animation_name, float weight)
{
const StdMeshAnimation* animation = Mesh.GetAnimationByName(animation_name);
if(!animation) return false;
SetAnimation(*animation);
return PlayAnimation(*animation, weight);
}
bool StdMeshInstance::PlayAnimation(const StdMeshAnimation& animation, float weight)
{
for(unsigned int i = 0; i < Animations.size(); ++i)
if(Animations[i].Animation == &animation)
return false;
Animation anim;
anim.Animation = &animation;
anim.Position = 0.0f;
anim.Weight = weight;
Animations.push_back(anim);
UpdateBoneTransforms();
return true;
}
void StdMeshInstance::SetAnimation(const StdMeshAnimation& animation)
bool StdMeshInstance::StopAnimation(const StdStrBuf& animation_name)
{
// TODO: Make sure the animation belongs to this mesh
Animation = &animation;
SetPosition(0.0f);
const StdMeshAnimation* animation = Mesh.GetAnimationByName(animation_name);
if(!animation) return false;
return StopAnimation(*animation);
}
void StdMeshInstance::UnsetAnimation()
bool StdMeshInstance::StopAnimation(const StdMeshAnimation& animation)
{
Animation = NULL;
for(std::vector<Animation>::iterator iter = Animations.begin();
iter != Animations.end(); ++iter)
{
if(iter->Animation == &animation)
{
Animations.erase(iter);
UpdateBoneTransforms();
return true;
}
}
// Reset instance vertices
for(unsigned int i = 0; i < Mesh.GetNumVertices(); ++i)
Vertices[i] = Mesh.GetVertex(i);
return false;
}
void StdMeshInstance::SetPosition(float position)
void StdMeshInstance::UpdateBoneTransforms()
{
assert(Animation);
Position = position;
// Compute transformation matrix for each bone.
for(unsigned int i = 0; i < BoneTransforms.size(); ++i)
{
StdMeshTrack* track = Animation->Tracks[i];
if(track)
float accum_weight = 0.0f;
BoneTransforms[i].SetScale(0,0,0); // zero matrix
for(unsigned int j = 0; j < Animations.size(); ++j)
{
BoneTransforms[i] = track->GetTransformAt(position);
StdMeshTrack* track = Animations[j].Animation->Tracks[i];
if(track)
{
accum_weight += Animations[j].Weight;
StdMeshMatrix matr(track->GetTransformAt(Animations[j].Position));
matr.Mul(Animations[j].Weight);
BoneTransforms[i].Add(matr);
}
}
if(!accum_weight)
BoneTransforms[i].SetIdentity();
else
{
#if 0
// No track for this bone, so use parent transformation
const StdMeshBone* parent = Mesh.GetBone(i).GetParent();
if(parent)
{
// Parent should already have been processed, because the bone indices
// are supposed to be hierarchically ordered.
assert(parent->Index < i);
BoneTransforms[i] = BoneTransforms[parent->Index];
}
else
{
#endif
BoneTransforms[i].SetIdentity();
#if 0
}
#endif
}
BoneTransforms[i].Mul(1.0f/accum_weight);
const StdMeshBone* bone = Mesh.Bones[i];
assert(bone->Index < i);
BoneTransforms[i].Mul(bone->InverseTrans);
BoneTransforms[i].Transform(bone->Trans);
const StdMeshBone* parent = bone->GetParent();
assert(!parent || parent->Index < i);
if(parent)
BoneTransforms[i].Transform(BoneTransforms[parent->Index]);
}

View File

@ -220,6 +220,9 @@ private:
class StdMeshInstance
{
protected:
struct Animation;
public:
StdMeshInstance(const StdMesh& mesh);
@ -232,14 +235,38 @@ public:
FaceOrdering GetFaceOrdering() const { return CurrentFaceOrdering; }
void SetFaceOrdering(FaceOrdering ordering);
bool SetAnimationByName(const StdStrBuf& animation_name);
void SetAnimation(const StdMeshAnimation& animation);
void UnsetAnimation();
// Public API to modify animation. Updates bone transforms on
// destruction, so make sure to let this go out of scope before
// relying on the values set.
struct AnimationRef
{
AnimationRef(StdMeshInstance* instance, const StdStrBuf& animation_name);
AnimationRef(StdMeshInstance* instance, const StdMeshAnimation& animation);
const StdMeshAnimation* GetAnimation() const { return Animation; }
~AnimationRef()
{
if(Changed) Instance->UpdateBoneTransforms();
}
operator void*() const { return Anim; } // for use in boolean expressions
void SetPosition(float position);
float GetPosition() const { return Position; }
const StdMeshAnimation& GetAnimation() const;
void SetPosition(float position);
void SetWeight(float weight);
private:
AnimationRef(const AnimationRef&); // noncopyable
AnimationRef& operator=(const AnimationRef&); // noncopyable
StdMeshInstance* Instance;
Animation* Anim;
bool Changed;
};
bool PlayAnimation(const StdStrBuf& animation_name, float weight);
bool PlayAnimation(const StdMeshAnimation& animation, float weight);
bool StopAnimation(const StdStrBuf& animation_name);
bool StopAnimation(const StdMeshAnimation& animation);
// Get vertex of instance, with current animation applied. This needs to
// go elsewhere if/when we want to calculate this on the hardware.
@ -255,13 +282,22 @@ public:
const StdMesh& Mesh;
protected:
void UpdateBoneTransforms();
void ReorderFaces();
FaceOrdering CurrentFaceOrdering;
const StdMeshAnimation* Animation;
float Position;
struct Animation
{
const StdMeshAnimation* Animation;
float Position;
float Weight;
};
std::vector<Animation> Animations;
std::vector<StdMeshMatrix> BoneTransforms;
std::vector<StdMeshVertex> Vertices;
std::vector<const StdMeshFace*> Faces;
};