forked from Mirrors/openclonk
Allow a mesh to play multiple animations at the same time
parent
6c00302928
commit
7c5619646c
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue