Introduce animation stack, adapt existing scripts

stable-5.2
Armin Burgmeier 2010-01-22 19:27:02 +01:00
parent eaab9e528f
commit 5dcf92121d
17 changed files with 1528 additions and 409 deletions

View File

@ -204,6 +204,8 @@ set(OC_CLONK_SOURCES
src/game/object/C4IDList.h
src/game/object/C4InfoCore.cpp
src/game/object/C4InfoCore.h
src/game/object/C4MeshAnimation.cpp
src/game/object/C4MeshAnimation.h
src/game/object/C4Movement.cpp
src/game/object/C4ObjectCom.cpp
src/game/object/C4ObjectCom.h

View File

@ -251,6 +251,8 @@ src/game/object/C4IDList.cpp \
src/game/object/C4IDList.h \
src/game/object/C4InfoCore.cpp \
src/game/object/C4InfoCore.h \
src/game/object/C4MeshAnimation.cpp \
src/game/object/C4MeshAnimation.h \
src/game/object/C4Movement.cpp \
src/game/object/C4ObjectCom.cpp \
src/game/object/C4ObjectCom.h \

View File

@ -334,20 +334,29 @@ protected func CheckStuck()
/* Status */
// TODO: Make this more sophisticated, readd turn animation and other
// adaptions
public func IsClonk() { return true; }
/*
// Test to synchronize the walkanimation with the movement
local OldPos;
static CLNK_WalkStates; // TODO: Well wasn't there once a patch, allowing arrys to be assigned to global static?
static CLNK_HangleStates;
static CLNK_SwimStates;
*/
/* Walk */
static const CLNK_WalkStand = "Stand";
static const CLNK_WalkWalk = "Walk";
static const CLNK_WalkRun = "Run";
func StartWalk()
{
if(CLNK_WalkStates == nil)
CLNK_WalkStates = ["Stand", "Walk", "Run", "StandTurn", "RunTurn"];
// if(CLNK_WalkStates == nil)
// CLNK_WalkStates = ["Stand", "Walk", "Run", "StandTurn", "RunTurn"];
if(!GetEffect("IntWalk", this))
AddEffect("IntWalk", this, 1, 1, this);
}
@ -357,6 +366,61 @@ func StopWalk()
if(GetAction() != "Walk") RemoveEffect("IntWalk", this);
}
func GetCurrentWalkAnimation()
{
var velocity = Distance(0,0,GetXDir(),GetYDir());
if(velocity < 1) return CLNK_WalkStand;
if(velocity < 10) return CLNK_WalkWalk;
return CLNK_WalkRun;
}
func GetWalkAnimationPosition(string anim)
{
// TODO: Choose proper starting positions, depending on the current
// animation and its position: For Stand->Walk or Stand->Run, start
// with a frame where one of the clonk's feets is on the ground and
// the other one is in the air. For Walk->Run and Run->Walk, fade to
// a state where its feets are at a similar position (just taking
// over previous animation's position might work, using
// GetAnimationPosition()). Walk->Stand is arbitrary I guess.
// First parameter of Anim_Linear/Anim_AbsX is initial position.
// Movement synchronization might also be tweaked somewhat as well.
if(anim == CLNK_WalkStand)
return Anim_Linear(0, 0, GetAnimationLength(anim), 35, ANIM_Loop);
else if(anim == CLNK_WalkWalk)
return Anim_AbsX(0, 0, GetAnimationLength(anim), 20);
else if(anim == CLNK_WalkRun)
return Anim_AbsX(0, 0, GetAnimationLength(anim), 50);
}
func FxIntWalkStart(pTarget, iNumber, fTmp)
{
if(fTmp) return;
// Always start in Stand for now... should maybe fade properly from previous animation instead
var anim = "Stand"; //GetCurrentWalkAnimation();
EffectVar(0, pTarget, iNumber) = anim;
EffectVar(1, pTarget, iNumber) = PlayAnimation(anim, 5, GetWalkAnimationPosition(anim), Anim_Const(1000));
}
func FxIntWalkStop(pTarget, iNumber, fTmp)
{
if(fTmp) return;
// Remove all
StopAnimation(GetRootAnimation(5));
}
func FxIntWalkTimer(pTarget, iNumber)
{
var anim = GetCurrentWalkAnimation();
if(anim != EffectVar(0, pTarget, iNumber))
{
EffectVar(0, pTarget, iNumber) = anim;
EffectVar(1, pTarget, iNumber) = PlayAnimation(anim, 5, GetWalkAnimationPosition(anim), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
}
}
/*
func FxIntWalkStart(pTarget, iNumber, fTmp)
{
if(fTmp) return;
@ -488,11 +552,28 @@ func FxIntWalkTimer(pTarget, iNumber, iTime)
}
EffectVar(14, pTarget, iNumber) = iState;
}
*/
/* Scale */
func StartScale()
{
// TODO: Tweak animation speed
PlayAnimation("Scale", 5, Anim_Y(0, GetAnimationLength("Scale"), 0, 15), Anim_Const(1000));
}
func StopScale()
{
StopAnimation(GetRootAnimation(5));
}
/* Hangle */
func StartHangle()
{
if(CLNK_HangleStates == nil)
CLNK_HangleStates = ["HangleStand", "Hangle"];
/* if(CLNK_HangleStates == nil)
CLNK_HangleStates = ["HangleStand", "Hangle"];*/
if(!GetEffect("IntHangle", this))
AddEffect("IntHangle", this, 1, 1, this);
}
@ -506,99 +587,95 @@ func FxIntHangleStart(pTarget, iNumber, fTmp)
{
EffectVar(10, pTarget, iNumber) = GetPhysical("Hangle");
if(fTmp) return;
AnimationPlay("Hangle", 0);
AnimationPlay("HangleStand", 1000);
EffectVar(0, pTarget, iNumber) = 0; // Phase
EffectVar(1, pTarget, iNumber) = 1000; // HangleStand weight
EffectVar(2, pTarget, iNumber) = 0; // Hangle weight
EffectVar(4, pTarget, iNumber) = 0; // Oldstate
EffectVar(5, pTarget, iNumber) = 0; // Remember if the last frame had COMD_Stop (so a single COMD_Stop frame doesn't stop the movement)
EffectVar(6, pTarget, iNumber) = 1; // Scedule Stop
EffectVar(7, pTarget, iNumber) = 0; // Hanging Pose (Front to player or Back)
// EffectVars:
// 0: whether the clonk is currently moving or not (<=> current animation is Hangle or HangleStand)
// 1: Current animation number
// 6: Player requested the clonk to stop
// 7: Whether the HangleStand animation is shown front-facing or back-facing
// 10: Previous Hangle physical
EffectVar(1, pTarget, iNumber) = PlayAnimation("HangleStand", 5, Anim_Linear(0, 0, 2000, 100, ANIM_Loop), Anim_Const(1000));
}
func FxIntHangleStop(pTarget, iNumber, iReasonm, fTmp)
{
SetPhysical("Hangle", EffectVar(10, pTarget, iNumber), 2);
if(fTmp) return;
AnimationStop("Hangle");
AnimationStop("HangleStand");
StopAnimation(GetRootAnimation(5));
}
func FxIntHangleTimer(pTarget, iNumber, iTime)
{
// Make a cosine movment speed (the clonk only moves whem he makes a "stroke")
var iSpeed = 50-Cos(EffectVar(0, pTarget, iNumber)*360*2/1000, 50);
SetPhysical("Hangle", EffectVar(10, pTarget, iNumber)/50*iSpeed, 2);
var iState = 0;
// (TODO: Instead of EffectVar(0, pTarget, iNumber) we should be able
// to query the current animation... maybe via a to-be-implemented
// GetAnimationName() engine function.
// Continue movement, if the clonk still has momentum
if(GetComDir() == COMD_Stop && iSpeed>10)
// If we are currently moving
if(EffectVar(0, pTarget, iNumber))
{
EffectVar(6, pTarget, iNumber) = 1;
if(GetDir())
SetComDir(COMD_Right);
else
SetComDir(COMD_Left);
// Use a cosine-shaped movement speed (the clonk only moves when he makes a "stroke")
var iSpeed = 50-Cos(GetAnimationPosition(EffectVar(1, pTarget, iNumber))/10*360*2/1000, 50);
SetPhysical("Hangle", EffectVar(10, pTarget, iNumber)/50*iSpeed, 2);
// Exec movement animation (TODO: Use Anim_Linear?)
var position = GetAnimationPosition(EffectVar(1, pTarget, iNumber));
position += (EffectVar(10, pTarget, iNumber)/6000*1000/(14*2));
SetAnimationPosition(EffectVar(1, pTarget, iNumber), Anim_Const(position % GetAnimationLength("Hangle")));
Log("Moving, speed %d, new pos %d/%d", iSpeed, position, GetAnimationPosition(EffectVar(1, pTarget, iNumber)));
// Continue movement, if the clonk still has momentum
if(GetComDir() == COMD_Stop && iSpeed>10)
{
// Make it stop after the current movement
EffectVar(6, pTarget, iNumber) = 1;
if(GetDir())
SetComDir(COMD_Right);
else
SetComDir(COMD_Left);
}
// Stop movement if the clonk has lost his momentum
else if(iSpeed <= 10 && (GetComDir() == COMD_Stop || EffectVar(6, pTarget, iNumber)))
{
EffectVar(6, pTarget, iNumber) = 0;
SetComDir(COMD_Stop);
// and remeber the pose (front or back)
if(GetAnimationPosition(EffectVar(1, pTarget, iNumber)) > 2500 && GetAnimationPosition(EffectVar(1, pTarget, iNumber)) < 7500)
EffectVar(7, pTarget, iNumber) = 1;
else
EffectVar(7, pTarget, iNumber) = 0;
// Change to HangleStand animation
var begin = 4000*EffectVar(7, pTarget, iNumber);
var end = 2000+begin;
EffectVar(1, pTarget, iNumber) = PlayAnimation("HangleStand", 5, Anim_Linear(begin, begin, end, 100, ANIM_Loop), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
EffectVar(0, pTarget, iNumber) = 0;
}
}
// Stop movement if clonk has lost his momentum
else if(EffectVar(6, pTarget, iNumber))
{
EffectVar(6, pTarget, iNumber) = 0;
SetComDir(COMD_Stop);
// and remeber the pose (front or back)
if(EffectVar(0, pTarget, iNumber) > 250 && EffectVar(0, pTarget, iNumber) < 750)
EffectVar(7, pTarget, iNumber) = 1;
else
EffectVar(7, pTarget, iNumber) = 0;
}
// Play stand animation when not moving
if(GetComDir() == COMD_Stop && EffectVar(5, pTarget, iNumber))
{
AnimationSetState("HangleStand", ((iTime/5)%21)*100+4000*EffectVar(7, pTarget, iNumber), nil);
iState = 1;
}
// When moving
else
{
var iSpeed = EffectVar(10, pTarget, iNumber)/6000;
if(EffectVar(4, pTarget, iNumber) != 2)
EffectVar(0, pTarget, iNumber) = 100+500*EffectVar(7, pTarget, iNumber); // start with frame 100 or from the back hanging pose frame 600
else EffectVar(0, pTarget, iNumber) += iSpeed*100/(14*2);
if(EffectVar(0, pTarget, iNumber) > 1000) EffectVar(0, pTarget, iNumber) -= 1000;
AnimationSetState("Hangle", EffectVar(0, pTarget, iNumber)*10, nil);
iState = 2;
}
// Save wether he have COMD_Stop or not. So a single frame with COMD_Stop keeps the movement
if(GetComDir() == COMD_Stop) EffectVar(5, pTarget, iNumber) = 1;
else EffectVar(5, pTarget, iNumber) = 0;
// Blend between the animations: The actuall animations gains weight till it reaches 1000
// the other animations lose weight until they are at 0
for(var i = 1; i <= 2; i++)
{
if(i == iState)
// We are currently not moving
if(GetComDir() != COMD_Stop)
{
if(EffectVar(i, pTarget, iNumber) < 1000)
EffectVar(i, pTarget, iNumber) += 200;
Log("Switch to move");
// Switch to move
EffectVar(0, pTarget, iNumber) = 1;
// start with frame 100 or from the back hanging pose frame 600
var begin = 10*(100 + 500*EffectVar(7, pTarget, iNumber));
EffectVar(1, pTarget, iNumber) = PlayAnimation("Hangle", 5, Anim_Const(begin), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
}
else
{
if(EffectVar(i, pTarget, iNumber) > 0)
EffectVar(i, pTarget, iNumber) -= 200;
}
AnimationSetState(CLNK_HangleStates[i-1], nil, EffectVar(i, pTarget, iNumber));
}
EffectVar(4, pTarget, iNumber) = iState;
}
/* Swim */
func StartSwim()
{
if(CLNK_SwimStates == nil)
CLNK_SwimStates = ["SwimStand", "Swim", "SwimDive", "SwimTurn", "SwimDiveTurn", "SwimDiveUp", "SwimDiveDown"];
/* if(CLNK_SwimStates == nil)
CLNK_SwimStates = ["SwimStand", "Swim", "SwimDive", "SwimTurn", "SwimDiveTurn", "SwimDiveUp", "SwimDiveDown"];*/
if(!GetEffect("IntSwim", this))
AddEffect("IntSwim", this, 1, 1, this);
}
@ -611,6 +688,10 @@ func StopSwim()
func FxIntSwimStart(pTarget, iNumber, fTmp)
{
if(fTmp) return;
EffectVar(0, pTarget, iNumber) = "SwimStand";
EffectVar(1, pTarget, iNumber) = PlayAnimation("SwimStand", 5, Anim_Linear(0, 0, GetAnimationLength("SwimStand"), 20, ANIM_Loop), Anim_Const(1000));
/*
for(var i = 0; i < GetLength(CLNK_SwimStates); i++)
AnimationPlay(CLNK_SwimStates[i], 0);
EffectVar(0, pTarget, iNumber) = 0; // Phase
@ -623,129 +704,72 @@ func FxIntSwimStart(pTarget, iNumber, fTmp)
EffectVar(7, pTarget, iNumber) = GetDir(); // OldDir
EffectVar(8, pTarget, iNumber) = 0; // Turn Phase
AnimationSetState("SwimStand", 0, 1000);*/
}
func FxIntSwimStop(pTarget, iNumber, iReason, fTmp)
{
if(fTmp) return;
for(var i = 0; i < GetLength(CLNK_SwimStates); i++)
AnimationStop(CLNK_SwimStates[i]);
StopAnimation(GetRootAnimation(5));
}
func FxIntSwimTimer(pTarget, iNumber, iTime)
{
DoEnergy(1); //TODO Remove this! Endless Energy while diving is only for the testers
if(EffectVar(7, pTarget, iNumber) != GetDir() && 0)
{
EffectVar(7, pTarget, iNumber) = GetDir();
EffectVar(8, pTarget, iNumber) = 1;
}
var iSpeed = Distance(0,0,GetXDir(),GetYDir());
var iState = 0;
// TODO: Smaller transition time between dive<->swim, keep 15 for swimstand<->swim/swimstand<->dive
// Play stand animation when not moving
if(Abs(GetXDir()) < 1 && EffectVar(5, pTarget, iNumber) && !GBackSemiSolid(0, -4))
if(Abs(GetXDir()) < 1 && !GBackSemiSolid(0, -4))
{
AnimationSetState("SwimStand", ((iTime/1)%20)*100, nil);
iState = 1;
if(EffectVar(0, pTarget, iNumber) != "SwimStand")
{
EffectVar(0, pTarget, iNumber) = "SwimStand";
EffectVar(1, pTarget, iNumber) = PlayAnimation("SwimStand", 5, Anim_Linear(0, 0, GetAnimationLength("SwimStand"), 20, ANIM_Loop), Anim_Linear(0, 0, 1000, 15, ANIM_Remove));
}
}
// Swimming
else if(!GBackSemiSolid(0, -4))
{
if(EffectVar(8, pTarget, iNumber))
// Animation speed by X
if(EffectVar(0, pTarget, iNumber) != "Swim")
{
AnimationSetState("SwimTurn", EffectVar(8, pTarget, iNumber)*100, nil);
EffectVar(8, pTarget, iNumber) += 2;
if(EffectVar(8, pTarget, iNumber) >= 40)
{
EffectVar(8, pTarget, iNumber) = 0;
SetDir(EffectVar(7, pTarget, iNumber));
}
else
SetDir(!EffectVar(7, pTarget, iNumber));
iState = 4;
}
else
{
EffectVar(0, pTarget, iNumber) += Abs(GetXDir())*40/(16*2);
if(EffectVar(0, pTarget, iNumber) > 400) EffectVar(0, pTarget, iNumber) -= 400;
AnimationSetState("Swim", EffectVar(0, pTarget, iNumber)*10, nil);
iState = 2;
EffectVar(0, pTarget, iNumber) = "Swim";
// TODO: Determine starting position from previous animation
PlayAnimation("Swim", 5, Anim_X(0, 0, GetAnimationLength("Swim"), 25), Anim_Linear(0, 0, 1000, 15, ANIM_Remove));
}
}
// Diving
else
{
EffectVar(0, pTarget, iNumber) += iSpeed*40/(16*2);
if(EffectVar(0, pTarget, iNumber) > 400) EffectVar(0, pTarget, iNumber) -= 400;
AnimationSetState("SwimDive", ((iTime/2)%20)*100, nil);
AnimationSetState("SwimDiveUp", ((iTime/2)%20)*100, nil);
AnimationSetState("SwimDiveDown", ((iTime/2)%20)*100, nil);
iState = 3;
}
// Save wether he have COMD_Stop or not. So a single frame with COMD_Stop keeps the movement
if(GetComDir() == COMD_Stop) EffectVar(5, pTarget, iNumber) = 1;
else EffectVar(5, pTarget, iNumber) = 0;
// Blend between the animations: The actuall animations gains weight till it reaches 1000
// the other animations lose weight until they are at 0
for(var i = 1; i <= 3; i++)
{
if(i == iState)
if(EffectVar(0, pTarget, iNumber) != "SwimDive")
{
if(EffectVar(i, pTarget, iNumber) < 1000)
EffectVar(i, pTarget, iNumber) += 100;
EffectVar(0, pTarget, iNumber) = "SwimDive";
// TODO: Determine starting position from previous animation
EffectVar(2, pTarget, iNumber) = PlayAnimation("SwimDiveUp", 5, Anim_Linear(0, 0, GetAnimationLength("SwimDiveUp"), 40, ANIM_Loop), Anim_Linear(0, 0, 1000, 15, ANIM_Remove));
EffectVar(3, pTarget, iNumber) = PlayAnimation("SwimDiveDown", 5, Anim_Linear(0, 0, GetAnimationLength("SwimDiveDown"), 40, ANIM_Loop), Anim_Const(500), EffectVar(2, pTarget, iNumber));
EffectVar(1, pTarget, iNumber) = EffectVar(3, pTarget, iNumber) + 1;
// TODO: This should depend on which animation we come from
// Guess for SwimStand we should fade from 0, otherwise from 90.
EffectVar(4, pTarget, iNumber) = 90;
}
else
if(iSpeed)
{
if(EffectVar(i, pTarget, iNumber) > 0)
EffectVar(i, pTarget, iNumber) -= 100;
var iRot = Angle(-Abs(GetXDir()), GetYDir());
EffectVar(4, pTarget, iNumber) += BoundBy(iRot - EffectVar(4, pTarget, iNumber), -4, 4);
}
AnimationSetState(CLNK_SwimStates[i-1], nil, EffectVar(i, pTarget, iNumber));
// TODO: Shouldn't weight go by sin^2 or cos^2 instead of linear in angle?
var weight = 1000*EffectVar(4, pTarget, iNumber)/180;
SetAnimationWeight(EffectVar(1, pTarget, iNumber), Anim_Const(1000 - weight));
}
// Adjust Swim direction
if(iSpeed > 1)
{
var iRot = Angle(-Abs(GetXDir()), GetYDir());
EffectVar(6, pTarget, iNumber) += BoundBy(iRot-EffectVar(6, pTarget, iNumber), -4, 4);
}
iRot = EffectVar(6, pTarget, iNumber);
Message("%d", this, iRot);
AnimationSetState("SwimDiveUp", nil, EffectVar(3, pTarget, iNumber)*iRot/180);
AnimationSetState("SwimDive", nil, 0);
AnimationSetState("SwimDiveDown", nil, EffectVar(3, pTarget, iNumber)-EffectVar(3, pTarget, iNumber)*iRot/180);
if(iRot < 90 && 0)
{
AnimationSetState("SwimDiveUp", nil, 0);
AnimationSetState("SwimDive", nil, EffectVar(3, pTarget, iNumber)*iRot/90);
AnimationSetState("SwimDiveDown", nil, EffectVar(3, pTarget, iNumber)-EffectVar(3, pTarget, iNumber)*iRot/90);
}
else if(0)
{
AnimationSetState("SwimDiveUp", nil, EffectVar(3, pTarget, iNumber)*(iRot-90)/90);
AnimationSetState("SwimDive", nil, EffectVar(3, pTarget, iNumber)-EffectVar(3, pTarget, iNumber)*(iRot-90)/90);
AnimationSetState("SwimDiveDown", nil, 0);
}
EffectVar(4, pTarget, iNumber) = iState;
}
func StartScale()
{
if(!GetEffect("IntScale", this))
AddEffect("IntScale", this, 1, 1, this);
}
func StopScale()
{
if(GetAction() != "Scale") RemoveEffect("IntScale", this);
}
/*
func FxIntScaleStart(pTarget, iNumber, fTmp)
{
if(fTmp) return;
@ -808,7 +832,7 @@ func FxIntScaleTimer(pTarget, iNumber, iTime)
// AnimationSetState(CLNK_WalkStates[i-1], nil, EffectVar(i, pTarget, iNumber));
}
EffectVar(4, pTarget, iNumber) = iState;
}
}*/
/* Act Map */

View File

@ -4,7 +4,7 @@ Version=4,9,8
Category=C4D_Structure|C4D_SelectBuilding|C4D_SelectKnowledge
MaxUserSelect=3
TimerCall=Wind2Turn
Timer=1
Timer=35
Width=70
Height=90
Offset=-35,-45
@ -22,4 +22,4 @@ BlastIncinerate=60
LineConnect=C4D_PowerOutput|C4D_PowerGenerator
Construction=1
Rotate=1
Float=1
Float=1

View File

@ -9,23 +9,46 @@ public func GetGeneratorPriority() { return 256; }
/* Initialisierung */
local wind_anim;
protected func Initialize()
{
AnimationPlay("Turn");
iRot = 0;
wind_anim = PlayAnimation("Turn", 5, Anim_Const(0), Anim_Const(1000));
// Set initial position
Wind2Turn();
return _inherited(...);
}
local iRot;
local b;
func Wind2Turn()
{
if(!Random(10))
DoPower(Abs(GetWind()/4));
iRot += GetWind()/2;
if(iRot < 0) iRot += 3600;
if(iRot >= 3600) iRot -= 3600;
AnimationSetState("Turn", iRot*12000/3600);
DoPower(Abs(GetWind()/3));
// Fade linearly in time until next timer call
var start = 0;
var end = GetAnimationLength("Turn");
if(GetWind() < 0)
{
start = end;
end = 0;
}
// Number of frames for one revolution: the more wind the more
// revolutions per frame.
var l = 7200/Abs(GetWind());
// Note ending is irrelevant since this is called again after 35 frames
if(!b)
if(l > 0)
{
b=1;
SetAnimationPosition(wind_anim, Anim_Linear(GetAnimationPosition(wind_anim), start, end, l, ANIM_Loop));
}
else
{
b = 1;
SetAnimationPosition(wind_anim, Anim_Const(GetAnimationPosition(wind_anim)));
}
}
func Definition(def) {

View File

@ -8,10 +8,14 @@ public func IsLorry() { return 1; }
public func IsToolProduct() { return 1; }
local drive_anim;
local tremble_anim;
protected func Initialize()
{
AnimationPlay("Drive");
AnimationPlay("Tremble");
drive_anim = PlayAnimation("Drive", 5, Anim_Const(0), Anim_Const(500) /* ignored anyway */);
tremble_anim = PlayAnimation("Tremble", 5, Anim_Const(0), Anim_Const(500));
iRotWheels = 0;
iTremble = 0;
}
@ -133,16 +137,18 @@ local iTremble;
func TurnWheels()
{
// TODO: Use Anim_X(Dir), keep from timer=1
// TODO: Could also use GetAnimationPosition() instead of these local variables...
iRotWheels += GetXDir()*2000/100; // Einmal rum (20 frames mal 10fps) nach 10 m
while(iRotWheels < 0) iRotWheels += 2000;
while(iRotWheels > 2000) iRotWheels -= 2000;
AnimationSetState("Drive", iRotWheels);
SetAnimationPosition(drive_anim, Anim_Const(iRotWheels));
if(Random(100) < Abs(GetXDir()))
{
iTremble += 100;
if(iTremble < 0) iTremble += 2000;
if(iTremble > 2000) iTremble -= 2000;
AnimationSetState("Tremble", iTremble);
SetAnimationPosition(tremble_anim, Anim_Const(iTremble));
}
}

View File

@ -0,0 +1,76 @@
#strict 2
// Wrappers for conventient calls to PlayAnimation
global func Anim_Const(int Value)
{
return [C4AVP_Const, Value];
}
global func Anim_Linear(int Position, int Begin, int End, int Length, int Ending)
{
return [C4AVP_Linear, Position, Begin, End, Length, Ending];
}
global func Anim_X(int Position, int Begin, int End, int Length)
{
return [C4AVP_X, Position, Begin, End, Length];
}
global func Anim_Y(int Position, int Begin, int End, int Length)
{
return [C4AVP_Y, Position, Begin, End, Length];
}
global func Anim_AbsX(int Position, int Begin, int End, int Length)
{
return [C4AVP_AbsX, Position, Begin, End, Length];
}
global func Anim_AbsY(int Position, int Begin, int End, int Length)
{
return [C4AVP_AbsY, Position, Begin, End, Length];
}
global func Anim_XDir(int Begin, int End, int MaxXDir, int Prec)
{
if(Prec == nil) Prec = 10;
return [C4AVP_XDir, Begin, End, MaxXDir, Prec];
}
global func Anim_YDir(int Begin, int End, int MaxYDir, int Prec)
{
if(Prec == nil) Prec = 10;
return [C4AVP_YDir, Begin, End, MaxYDir, Prec];
}
global func Anim_RDir(int Begin, int End, int MaxRDir, int Prec)
{
if(Prec == nil) Prec = 10;
return [C4AVP_RDir, Begin, End, MaxRDir, Prec];
}
global func Anim_CosR(int Begin, int End, int Offset, int Prec)
{
if(Prec == nil) Prec = 1;
return [C4AVP_CosR, Begin, End, Offset, Prec];
}
global func Anim_SinR(int Begin, int End, int Offset, int Prec)
{
if(Prec == nil) Prec = 1;
return [C4AVP_SinR, Begin, End, Offset, Prec];
}
global func Anim_CosV(int Begin, int End, int Offset, int Prec)
{
if(Prec == nil) Prec = 1;
return [C4AVP_CosV, Begin, End, Offset, Prec];
}
global func Anim_SinV(int Begin, int End, int Offset, int Prec)
{
if(Prec == nil) Prec = 1;
return [C4AVP_SinV, Begin, End, Offset, Prec];
}

View File

@ -45,6 +45,7 @@ void C4Action::Default()
Facet.Default();
FacetX=FacetY=0;
t_attach=CNAT_None;
Animation = NULL;
}
void C4Action::CompileFunc(StdCompiler *pComp)

View File

@ -41,6 +41,7 @@
#include <C4GameObjects.h>
#include <C4RankSystem.h>
#include <C4GraphicsResource.h>
#include <C4MeshAnimation.h>
// Helper class to load additional ressources required for meshes from
// a C4Group.
@ -749,13 +750,14 @@ void C4GraphicsOverlay::UpdateFacet()
}
else
{
C4String* Animation = action->GetPropertyStr(P_Animation);
C4String* AnimationName = action->GetPropertyStr(P_Animation);
if(!AnimationName) return;
pMeshInstance = new StdMeshInstance(*pSourceGfx->Mesh);
const StdMeshAnimation* Animation = pSourceGfx->Mesh->GetAnimationByName(AnimationName->GetData());
if(!Animation) return;
pMeshInstance = new StdMeshInstance(*pSourceGfx->Mesh);
pMeshInstance->PlayAnimation(Animation->GetData(), 1.0f);
StdMeshInstance::AnimationRef ref(pMeshInstance, Animation->GetData());
ref.SetPosition(iPhase * ref.GetAnimation().Length / action->GetPropertyInt(P_Length));
pMeshInstance->PlayAnimation(*Animation, 0, NULL, new C4ValueProviderRef<int32_t>(iPhase, Animation->Length / action->GetPropertyInt(P_Length)), new C4ValueProviderConst(1.0f));
}
break;

View File

@ -244,7 +244,7 @@ class C4GraphicsOverlay
C4Object *GetOverlayObject() const { return pOverlayObj; }
int32_t GetID() const { return iID; }
void SetID(int32_t aID) { iID = aID; }
void SetPhase(int32_t iToPhase); // TODO: This is not implemented(?) - Remember to set mesh animation position when it is
void SetPhase(int32_t iToPhase);
C4GraphicsOverlay *GetNext() const { return pNext; }
void SetNext(C4GraphicsOverlay *paNext) { pNext = paNext; }
bool IsPicture() { return eMode == MODE_Picture; }

View File

@ -0,0 +1,326 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2010 Armin Burgmeier
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
#include "C4Include.h"
#include "C4MeshAnimation.h"
#include "C4Object.h"
#include "C4ValueList.h"
#include "C4Game.h"
StdMeshInstance::ValueProvider* CreateValueProviderFromArray(C4Object* pForObj, C4ValueArray& Data)
{
int32_t type = Data[0].getInt();
switch(type)
{
case C4AVP_Const:
return new C4ValueProviderConst(Data[1].getInt()/1000.0f);
case C4AVP_Linear:
return new C4ValueProviderLinear(Data[1].getInt()/1000.0f, Data[2].getInt()/1000.0f, Data[3].getInt()/1000.0f, Data[4].getInt(), static_cast<C4AnimationEnding>(Data[5].getInt()));
case C4AVP_X:
if(!pForObj) return NULL;
return new C4ValueProviderX(pForObj, Data[1].getInt()/1000.0f, Data[2].getInt()/1000.0f, Data[3].getInt()/1000.0f, Data[4].getInt());
case C4AVP_Y:
if(!pForObj) return NULL;
return new C4ValueProviderY(pForObj, Data[1].getInt()/1000.0f, Data[2].getInt()/1000.0f, Data[3].getInt()/1000.0f, Data[4].getInt());
case C4AVP_AbsX:
if(!pForObj) return NULL;
return new C4ValueProviderAbsX(pForObj, Data[1].getInt()/1000.0f, Data[2].getInt()/1000.0f, Data[3].getInt()/1000.0f, Data[4].getInt());
case C4AVP_AbsY:
if(!pForObj) return NULL;
return new C4ValueProviderAbsY(pForObj, Data[1].getInt()/1000.0f, Data[2].getInt()/1000.0f, Data[3].getInt()/1000.0f, Data[4].getInt());
case C4AVP_XDir:
if(!pForObj) return NULL;
return new C4ValueProviderXDir(pForObj, Data[1].getInt()/1000.0f, Data[2].getInt()/1000.0f, itofix(Data[3].getInt(),Data[4].getInt()));
case C4AVP_YDir:
if(!pForObj) return NULL;
return new C4ValueProviderYDir(pForObj, Data[1].getInt()/1000.0f, Data[2].getInt()/1000.0f, itofix(Data[3].getInt(),Data[4].getInt()));
case C4AVP_RDir:
if(!pForObj) return NULL;
return new C4ValueProviderRDir(pForObj, Data[1].getInt()/1000.0f, Data[2].getInt()/1000.0f, itofix(Data[3].getInt(),Data[4].getInt()));
case C4AVP_CosR:
if(!pForObj) return NULL;
return new C4ValueProviderCosR(pForObj, Data[1].getInt()/1000.0f, Data[2].getInt()/1000.0f, itofix(Data[3].getInt(),Data[4].getInt()));
case C4AVP_SinR:
if(!pForObj) return NULL;
return new C4ValueProviderSinR(pForObj, Data[1].getInt()/1000.0f, Data[2].getInt()/1000.0f, itofix(Data[3].getInt(),Data[4].getInt()));
case C4AVP_CosV:
if(!pForObj) return NULL;
return new C4ValueProviderCosV(pForObj, Data[1].getInt()/1000.0f, Data[2].getInt()/1000.0f, itofix(Data[3].getInt(),Data[4].getInt()));
case C4AVP_SinV:
if(!pForObj) return NULL;
return new C4ValueProviderSinV(pForObj, Data[1].getInt()/1000.0f, Data[2].getInt()/1000.0f, itofix(Data[3].getInt(),Data[4].getInt()));
case C4AVP_Action:
if(!pForObj) return NULL;
return new C4ValueProviderAction(pForObj);
default:
return NULL;
}
}
C4ValueProviderConst::C4ValueProviderConst(float value)
{
Value = value;
}
bool C4ValueProviderConst::Execute()
{
// Keep value we set in ctor
return true;
}
C4ValueProviderLinear::C4ValueProviderLinear(float pos, float begin, float end, unsigned int length, C4AnimationEnding ending):
Begin(begin), End(end), Length(length), Ending(ending), LastTick(Game.FrameCounter)
{
Value = pos;
}
bool C4ValueProviderLinear::Execute()
{
Value += (End - Begin) * (Game.FrameCounter - LastTick) / Length;
LastTick = Game.FrameCounter;
assert( (End >= Begin && Value >= Begin) || (End <= Begin && Value <= Begin));
while( (End > Begin && Value > End) || (End < Begin && Value < End))
{
switch(Ending)
{
case ANIM_Loop:
Value -= (End - Begin);
return true;
case ANIM_Hold:
Value = End;
return true;
case ANIM_Remove:
Value = End;
return false;
}
}
return true;
}
C4ValueProviderX::C4ValueProviderX(const C4Object* object, float pos, float begin, float end, unsigned int length):
Object(object), Begin(begin), End(end), Length(length), LastX(fixtof(object->fix_x))
{
Value = pos;
}
bool C4ValueProviderX::Execute()
{
const float obj_x = fixtof(Object->fix_x);
Value += (End - Begin) * (obj_x - LastX) / Length; // TODO: Use xdir instead?
LastX = obj_x;
if(End > Begin)
{
while(Value > End)
Value -= (End - Begin);
while(Value < Begin)
Value += (End - Begin);
}
else
{
while(Value > Begin)
Value -= (Begin - End);
while(Value < End)
Value += (Begin - End);
}
return true;
}
C4ValueProviderY::C4ValueProviderY(const C4Object* object, float pos, float begin, float end, unsigned int length):
Object(object), Begin(begin), End(end), Length(length), LastY(fixtof(object->fix_y))
{
Value = pos;
}
bool C4ValueProviderY::Execute()
{
const float obj_y = fixtof(Object->fix_y);
Value += (End - Begin) * (obj_y - LastY) / Length; // TODO: Use ydir instead?
LastY = obj_y;
if(End > Begin)
{
while(Value > End)
Value -= (End - Begin);
while(Value < Begin)
Value += (End - Begin);
}
else
{
while(Value > Begin)
Value -= (Begin - End);
while(Value < End)
Value += (Begin - End);
}
return true;
}
C4ValueProviderAbsX::C4ValueProviderAbsX(const C4Object* object, float pos, float begin, float end, unsigned int length):
Object(object), Begin(begin), End(end), Length(length), LastX(fixtof(object->fix_x))
{
Value = pos;
}
bool C4ValueProviderAbsX::Execute()
{
const float obj_x = fixtof(Object->fix_x);
Value += (End - Begin) * fabs(obj_x - LastX) / Length;
LastX = obj_x;
assert( (End >= Begin && Value >= Begin) || (End <= Begin && Value <= Begin));
while( (End > Begin && Value > End) || (End < Begin && Value < End))
Value -= (End - Begin);
return true;
}
C4ValueProviderAbsY::C4ValueProviderAbsY(const C4Object* object, float pos, float begin, float end, unsigned int length):
Object(object), Begin(begin), End(end), Length(length), LastY(fixtof(object->fix_y))
{
Value = pos;
}
bool C4ValueProviderAbsY::Execute()
{
const float obj_y = fixtof(Object->fix_y);
Value += (End - Begin) * fabs(obj_y - LastY) / Length;
LastY = obj_y;
assert( (End >= Begin && Value >= Begin) || (End <= Begin && Value <= Begin));
while( (End > Begin && Value > End) || (End < Begin && Value < End))
Value -= (End - Begin);
return true;
}
C4ValueProviderXDir::C4ValueProviderXDir(const C4Object* object, float begin, float end, FIXED max_xdir):
Object(object), Begin(begin), End(end), MaxXDir(max_xdir)
{
Execute();
}
bool C4ValueProviderXDir::Execute()
{
Value = Begin + (End - Begin) * Min<float>(fabs(fixtof(Object->xdir/MaxXDir)), 1.0f);
return true;
}
C4ValueProviderYDir::C4ValueProviderYDir(const C4Object* object, float begin, float end, FIXED max_ydir):
Object(object), Begin(begin), End(end), MaxYDir(max_ydir)
{
Execute();
}
bool C4ValueProviderYDir::Execute()
{
Value = Begin + (End - Begin) * Min<float>(fabs(fixtof(Object->ydir/MaxYDir)), 1.0f);
return true;
}
C4ValueProviderRDir::C4ValueProviderRDir(const C4Object* object, float begin, float end, FIXED max_rdir):
Object(object), Begin(begin), End(end), MaxRDir(max_rdir)
{
Execute();
}
bool C4ValueProviderRDir::Execute()
{
Value = Begin + (End - Begin) * Min<float>(fabs(fixtof(Object->rdir/MaxRDir)), 1.0f);
return true;
}
C4ValueProviderCosR::C4ValueProviderCosR(const C4Object* object, float begin, float end, FIXED offset):
Object(object), Begin(begin), End(end), Offset(offset)
{
Execute();
}
bool C4ValueProviderCosR::Execute()
{
Value = Begin + (End - Begin) * cos(fixtof(Object->fix_r + Offset)); // TODO: Use fixed-point math if necessary
return true;
}
C4ValueProviderSinR::C4ValueProviderSinR(const C4Object* object, float begin, float end, FIXED offset):
Object(object), Begin(begin), End(end), Offset(offset)
{
Execute();
}
bool C4ValueProviderSinR::Execute()
{
Value = Begin + (End - Begin) * sin(fixtof(Object->fix_r + Offset)); // TODO: Use fixed-point math if necessary
return true;
}
C4ValueProviderCosV::C4ValueProviderCosV(const C4Object* object, float begin, float end, FIXED offset):
Object(object), Begin(begin), End(end), Offset(offset)
{
Execute();
}
bool C4ValueProviderCosV::Execute()
{
// TODO: Maybe we can optimize this by using cos(r) = x/sqrt(x*x+y*y), sin(r)=y/sqrt(x*x+y*y)
// plus addition theorems for sin or cos.
int angle = Angle(0, 0, fixtoi(Object->xdir, 256), fixtoi(Object->ydir, 256));
Value = Begin + (End - Begin) * cos(angle + fixtof(Offset)); // TODO: Use fixed-point math if necessary
return true;
}
C4ValueProviderSinV::C4ValueProviderSinV(const C4Object* object, float begin, float end, FIXED offset):
Object(object), Begin(begin), End(end), Offset(offset)
{
Execute();
}
bool C4ValueProviderSinV::Execute()
{
// TODO: Maybe we can optimize this by using cos(r) = x/sqrt(x*x+y*y), sin(r)=y/sqrt(x*x+y*y),
// plus addition theorems for sin or cos.
int angle = Angle(0, 0, fixtoi(Object->xdir, 256), fixtoi(Object->ydir, 256));
Value = Begin + (End - Begin) * sin(angle + fixtof(Offset)); // TODO: Use fixed-point math if necessary
return true;
}
C4ValueProviderAction::C4ValueProviderAction(const C4Object* object):
Action(object->Action)
{
}
bool C4ValueProviderAction::Execute()
{
// TODO: We could cache these...
const StdMeshAnimation* animation = Action.Animation->GetAnimation();
const int32_t length = Action.pActionDef->GetPropertyInt(P_Length);
const int32_t delay = Action.pActionDef->GetPropertyInt(P_Delay);
if(delay)
Value = static_cast<float>(Action.Phase * delay + Action.PhaseDelay) / (delay * length) * animation->Length;
else
Value = static_cast<float>(Action.Phase) / length * animation->Length;
return true;
}

View File

@ -0,0 +1,261 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2010 Armin Burgmeier
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
/* Value providers for mesh animation, cf. StdMeshInstance */
#ifndef INC_C4MeshAnimation
#define INC_C4MeshAnimation
#include "StdMesh.h"
class C4Action;
class C4Object;
class C4ValueArray;
enum C4AnimationValueProviderID
{
C4AVP_Const,
C4AVP_Linear,
C4AVP_X,
C4AVP_Y,
C4AVP_AbsX,
C4AVP_AbsY,
C4AVP_XDir,
C4AVP_YDir,
C4AVP_RDir,
C4AVP_CosR,
C4AVP_SinR,
C4AVP_CosV,
C4AVP_SinV,
C4AVP_Action
};
enum C4AnimationEnding
{
ANIM_Loop,
ANIM_Hold,
ANIM_Remove
};
// Keep a constant value
class C4ValueProviderConst: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderConst(float value);
virtual bool Execute();
};
// Interpolate linearly in time between two values
class C4ValueProviderLinear: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderLinear(float pos, float begin, float end, unsigned int length, C4AnimationEnding ending);
virtual bool Execute();
private:
const float Begin;
const float End;
const unsigned int Length;
const C4AnimationEnding Ending;
int32_t LastTick;
};
class C4ValueProviderX: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderX(const C4Object* object, float pos, float begin, float end, unsigned int length);
virtual bool Execute();
private:
const C4Object* const Object;
const float Begin;
const float End;
const unsigned int Length;
float LastX;
};
class C4ValueProviderY: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderY(const C4Object* object, float pos, float begin, float end, unsigned int length);
virtual bool Execute();
private:
const C4Object* const Object;
const float Begin;
const float End;
const unsigned int Length;
float LastY;
};
class C4ValueProviderAbsX: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderAbsX(const C4Object* object, float pos, float begin, float end, unsigned int length);
virtual bool Execute();
private:
const C4Object* const Object;
const float Begin;
const float End;
const unsigned int Length;
float LastX;
};
class C4ValueProviderAbsY: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderAbsY(const C4Object* object, float pos, float begin, float end, unsigned int length);
virtual bool Execute();
private:
const C4Object* const Object;
const float Begin;
const float End;
const unsigned int Length;
float LastY;
};
class C4ValueProviderXDir: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderXDir(const C4Object* object, float begin, float end, FIXED max_xdir);
virtual bool Execute();
private:
const C4Object* const Object;
const float Begin;
const float End;
const FIXED MaxXDir;
};
class C4ValueProviderYDir: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderYDir(const C4Object* object, float begin, float end, FIXED max_ydir);
virtual bool Execute();
private:
const C4Object* const Object;
const float Begin;
const float End;
const FIXED MaxYDir;
};
class C4ValueProviderRDir: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderRDir(const C4Object* object, float begin, float end, FIXED max_rdir);
virtual bool Execute();
private:
const C4Object* const Object;
const float Begin;
const float End;
const FIXED MaxRDir;
};
class C4ValueProviderCosR: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderCosR(const C4Object* object, float begin, float end, FIXED offset);
virtual bool Execute();
private:
const C4Object* const Object;
const float Begin;
const float End;
const FIXED Offset;
};
class C4ValueProviderSinR: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderSinR(const C4Object* object, float begin, float end, FIXED offset);
virtual bool Execute();
private:
const C4Object* const Object;
const float Begin;
const float End;
const FIXED Offset;
};
class C4ValueProviderCosV: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderCosV(const C4Object* object, float begin, float end, FIXED offset);
virtual bool Execute();
private:
const C4Object* const Object;
const float Begin;
const float End;
const FIXED Offset;
};
class C4ValueProviderSinV: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderSinV(const C4Object* object, float begin, float end, FIXED offset);
virtual bool Execute();
private:
const C4Object* const Object;
const float Begin;
const float End;
const FIXED Offset;
};
class C4ValueProviderAction: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderAction(const C4Object* object);
virtual bool Execute();
private:
const C4Action& Action;
};
// Reference another value (which is convertible to float), and optionally scale it
template<typename SourceT>
class C4ValueProviderRef: public StdMeshInstance::ValueProvider
{
public:
C4ValueProviderRef(const SourceT& ref, float scale):
Ref(ref), Scale(scale) {}
virtual bool Execute()
{
Value = static_cast<float>(Ref) * Scale;
return true;
}
private:
const SourceT& Ref;
float Scale;
};
StdMeshInstance::ValueProvider* CreateValueProviderFromArray(C4Object* pForObj, C4ValueArray& Data);
#endif

View File

@ -51,6 +51,7 @@
#include <C4PlayerList.h>
#include <C4GameObjects.h>
#include <C4Record.h>
#include <C4MeshAnimation.h>
void DrawVertex(C4Facet &cgo, int32_t tx, int32_t ty, int32_t col, int32_t contact)
{
@ -1098,6 +1099,8 @@ void C4Object::Execute()
ExecLife();
// Base
ExecBase();
// Animation
if(pMeshInstance) pMeshInstance->ExecuteAnimation();
// Timer
Timer++;
if (Timer>=Def->Timer)
@ -2192,29 +2195,9 @@ 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);
const int32_t delay = Action.pActionDef->GetPropertyInt(P_Delay);
Action.Phase=BoundBy<int32_t>(iPhase,0,length);
Action.PhaseDelay = 0;
if(pMeshInstance)
{
C4String* AnimationName = Action.pActionDef->GetPropertyStr(P_Animation);
if(AnimationName)
{
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;
}
@ -2733,9 +2716,10 @@ void C4Object::CompileFunc(StdCompiler *pComp)
pComp->Value(TemporaryPhysical);
}
// TODO: Animations / attached meshes
// Commands
if(pComp->FollowName("Commands"))
// Commands
if(pComp->FollowName("Commands"))
if(fCompiler)
{
C4Command *pCmd = NULL;
@ -2761,41 +2745,45 @@ void C4Object::CompileFunc(StdCompiler *pComp)
}
}
// Compiling? Do initialization.
if(fCompiler)
{
// add to def count
Def->Count++;
// Compiling? Do initialization.
if(fCompiler)
{
// add to def count
Def->Count++;
// set local variable names
LocalNamed.SetNameList(&Def->Script.LocalNamed);
// set local variable names
LocalNamed.SetNameList(&Def->Script.LocalNamed);
// Set action (override running data)
int32_t iTime=Action.Time;
int32_t iPhase=Action.Phase;
int32_t iPhaseDelay=Action.PhaseDelay;
/* FIXME if (SetActionByName(Action.pActionDef->GetName(),0,0,false))
{
Action.Time=iTime;
Action.Phase=iPhase; // No checking for valid phase
Action.PhaseDelay=iPhaseDelay;
}*/
// Set action (override running data)
int32_t iTime=Action.Time;
int32_t iPhase=Action.Phase;
int32_t iPhaseDelay=Action.PhaseDelay;
/* FIXME if (SetActionByName(Action.pActionDef->GetName(),0,0,false))
{
Action.Time=iTime;
Action.Phase=iPhase; // No checking for valid phase
Action.PhaseDelay=iPhaseDelay;
}*/
// if on fire but no effect is present (old-style savegames), re-incinerate
int32_t iFireNumber;
C4Value Par1, Par2, Par3, Par4;
if (OnFire && !pEffects) new C4Effect(this, C4Fx_Fire, C4Fx_FirePriority, C4Fx_FireTimer, NULL, 0, Par1, Par2, Par3, Par4, false, iFireNumber);
// Set Action animation by slot 0
if(pMeshInstance)
Action.Animation = pMeshInstance->GetRootAnimationForSlot(0);
// blit mode not assigned? use definition default then
if (!BlitMode) BlitMode = Def->BlitMode;
// if on fire but no effect is present (old-style savegames), re-incinerate
int32_t iFireNumber;
C4Value Par1, Par2, Par3, Par4;
if (OnFire && !pEffects) new C4Effect(this, C4Fx_Fire, C4Fx_FirePriority, C4Fx_FireTimer, NULL, 0, Par1, Par2, Par3, Par4, false, iFireNumber);
// object needs to be resorted? May happen if there's unsorted objects in savegame
if (Unsorted) Game.fResortAnyObject = true;
// blit mode not assigned? use definition default then
if (!BlitMode) BlitMode = Def->BlitMode;
// object needs to be resorted? May happen if there's unsorted objects in savegame
if (Unsorted) Game.fResortAnyObject = true;
// initial OCF update
SetOCF();
}
}
}
@ -3276,22 +3264,14 @@ bool C4Object::SetAction(C4PropList * Act, C4Object *pTarget, C4Object *pTarget2
// such an animation.
if(pMeshInstance)
{
C4String* Animation = Act ? Act->GetPropertyStr(P_Animation) : NULL;
C4String* OldAnimation = LastAction ? LastAction->GetPropertyStr(P_Animation) : NULL;
if(OldAnimation)
pMeshInstance->StopAnimation(OldAnimation->GetData());
if(Action.Animation) pMeshInstance->StopAnimation(Action.Animation);
Action.Animation = NULL;
C4String* Animation = Act ? Act->GetPropertyStr(P_Animation) : NULL;
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);
// note that weight is ignored
Action.Animation = pMeshInstance->PlayAnimation(Animation->GetData(), 0, NULL, new C4ValueProviderAction(this), new C4ValueProviderConst(1.0f));
}
}
// Stop previous act sound
@ -4577,7 +4557,6 @@ void C4Object::ExecAction()
if (pAction->GetPropertyInt(P_Delay))
{
Action.PhaseDelay+=iPhaseAdvance;
bool set_new_action = false;
if (Action.PhaseDelay >= pAction->GetPropertyInt(P_Delay))
{
// Advance Phase
@ -4605,12 +4584,11 @@ void C4Object::ExecAction()
{
// Set new action
SetActionByName(next_action, NULL, NULL, SAC_StartCall | SAC_EndCall);
set_new_action = true;
SetActionByName(next_action, NULL, NULL, SAC_StartCall | SAC_EndCall);
}
}
}
#if 0
// Update animation on mesh instance. If a new action was set,
// then this will already have happened for the new action.
if(pMeshInstance && !set_new_action)
@ -4627,6 +4605,7 @@ void C4Object::ExecAction()
}
}
}
#endif
}
return;

View File

@ -100,6 +100,7 @@ class C4Action
C4Object *Target,*Target2;
C4Facet Facet; // NoSave //
int32_t FacetX,FacetY; // NoSave //
StdMeshInstance::AnimationNode* Animation; // NoSave //
public:
void Default();
void CompileFunc(StdCompiler *pComp);

View File

@ -52,6 +52,7 @@
#include <C4Game.h>
#include <C4GameObjects.h>
#include <C4GameControl.h>
#include <C4MeshAnimation.h>
//========================== Some Support Functions =======================================
@ -5545,42 +5546,108 @@ static C4Void FnDoNoCollectDelay(C4AulObjectContext *ctx, int change)
return C4VNull;
}
static bool FnAnimationPlay(C4AulContext *ctx, C4String *szAnimation, Nillable<long> weight)
static Nillable<int> FnPlayAnimation(C4AulObjectContext *ctx, C4String *szAnimation, int iSlot, C4ValueArray* PositionProvider, C4ValueArray* WeightProvider, Nillable<int> iSibling)
{
if(!ctx->Obj) return C4VNull;
if(!ctx->Obj->pMeshInstance) return C4VNull;
if(iSlot == 0) return C4VNull; // Reserved for ActMap animations
if(!PositionProvider) return C4VNull;
if(!WeightProvider) return C4VNull;
StdMeshInstance::AnimationNode* s_node = NULL;
if(!iSibling.IsNil())
{
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);
s_node = ctx->Obj->pMeshInstance->GetAnimationNodeByNumber(iSibling);
if(!s_node || s_node->GetSlot() != iSlot) return C4VNull;
}
static bool FnAnimationStop(C4AulContext *ctx, C4String *szAnimation)
StdMeshInstance::ValueProvider* p_provider = CreateValueProviderFromArray(ctx->Obj, *PositionProvider);
StdMeshInstance::ValueProvider* w_provider = CreateValueProviderFromArray(ctx->Obj, *WeightProvider);
if(!p_provider || !w_provider)
{
if(!ctx->Obj) return false;
if(!ctx->Obj->pMeshInstance) return false;
return ctx->Obj->pMeshInstance->StopAnimation(szAnimation->GetData());
delete p_provider;
delete w_provider;
return C4VNull;
}
static bool FnAnimationSetState(C4AulContext *ctx, C4String *szAnimation, Nillable<long> position, Nillable<long> weight)
{
StdMeshInstance::AnimationNode* n_node = ctx->Obj->pMeshInstance->PlayAnimation(szAnimation->GetData(), iSlot, s_node, p_provider, w_provider);
if(!n_node) return C4VNull;
return n_node->GetNumber();
}
static bool FnStopAnimation(C4AulObjectContext *ctx, int iAnimationNumber)
{
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);
StdMeshInstance::AnimationNode* node = ctx->Obj->pMeshInstance->GetAnimationNodeByNumber(iAnimationNumber);
// slot 0 is reserved for ActMap animations
if(!node || node->GetSlot() == 0) return false;
ctx->Obj->pMeshInstance->StopAnimation(node);
return true;
}
}
static Nillable<int> FnGetRootAnimation(C4AulObjectContext *ctx, int iSlot)
{
if(!ctx->Obj) return C4VNull;
if(!ctx->Obj->pMeshInstance) return C4VNull;
StdMeshInstance::AnimationNode* node = ctx->Obj->pMeshInstance->GetRootAnimationForSlot(iSlot);
if(!node) return C4VNull;
return node->GetNumber();
}
static Nillable<int> FnGetAnimationLength(C4AulObjectContext *ctx, C4String *szAnimation)
{
if(!ctx->Obj) return C4VNull;
if(!ctx->Obj->pMeshInstance) return C4VNull;
const StdMeshAnimation* animation = ctx->Obj->pMeshInstance->Mesh.GetAnimationByName(szAnimation->GetData());
if(!animation) return C4VNull;
return static_cast<int>(animation->Length * 1000.0f); // TODO: sync critical?
}
static Nillable<int> FnGetAnimationPosition(C4AulObjectContext *ctx, int iAnimationNumber)
{
if(!ctx->Obj) return C4VNull;
if(!ctx->Obj->pMeshInstance) return C4VNull;
StdMeshInstance::AnimationNode* node = ctx->Obj->pMeshInstance->GetAnimationNodeByNumber(iAnimationNumber);
if(!node || node->GetType() != StdMeshInstance::AnimationNode::LeafNode) return C4VNull;
return static_cast<int>(node->GetPosition() * 1000.0f); // TODO: sync critical?
}
static Nillable<int> FnGetAnimationWeight(C4AulObjectContext *ctx, int iAnimationNumber)
{
if(!ctx->Obj) return C4VNull;
if(!ctx->Obj->pMeshInstance) return C4VNull;
StdMeshInstance::AnimationNode* node = ctx->Obj->pMeshInstance->GetAnimationNodeByNumber(iAnimationNumber);
if(!node || node->GetType() != StdMeshInstance::AnimationNode::LinearInterpolationNode) return C4VNull;
return static_cast<int>(node->GetWeight() * 1000.0f); // TODO: sync critical?
}
static bool FnSetAnimationPosition(C4AulObjectContext *ctx, int iAnimationNumber, C4ValueArray* PositionProvider)
{
if(!ctx->Obj) return false;
if(!ctx->Obj->pMeshInstance) return false;
StdMeshInstance::AnimationNode* node = ctx->Obj->pMeshInstance->GetAnimationNodeByNumber(iAnimationNumber);
// slot 0 is reserved for ActMap animations
if(!node || node->GetSlot() == 0 || node->GetType() != StdMeshInstance::AnimationNode::LeafNode) return false;
StdMeshInstance::ValueProvider* p_provider = CreateValueProviderFromArray(ctx->Obj, *PositionProvider);
if(!p_provider) return false;
ctx->Obj->pMeshInstance->SetAnimationPosition(node, p_provider);
return true;
}
static bool FnSetAnimationWeight(C4AulObjectContext *ctx, int iAnimationNumber, C4ValueArray* WeightProvider)
{
if(!ctx->Obj) return false;
if(!ctx->Obj->pMeshInstance) return false;
StdMeshInstance::AnimationNode* node = ctx->Obj->pMeshInstance->GetAnimationNodeByNumber(iAnimationNumber);
// slot 0 is reserved for ActMap animations
if(!node || node->GetSlot() == 0 || node->GetType() != StdMeshInstance::AnimationNode::LinearInterpolationNode) return false;
StdMeshInstance::ValueProvider* w_provider = CreateValueProviderFromArray(ctx->Obj, *WeightProvider);
if(!w_provider) return false;
ctx->Obj->pMeshInstance->SetAnimationWeight(node, w_provider);
return true;
}
static Nillable<long> FnAttachMesh(C4AulContext *ctx, C4ID idMesh, C4String* szParentBone, C4String* szChildBone, Nillable<long> scale)
{
@ -6074,9 +6141,14 @@ void InitFunctionMap(C4AulScriptEngine *pEngine)
AddFunc(pEngine, "GetPlayerControlEnabled", FnGetPlayerControlEnabled);
//FIXME new C4AulDefCastFunc(pEngine, "ScoreboardCol", C4V_C4ID, C4V_Int);
AddFunc(pEngine, "AnimationPlay", FnAnimationPlay);
AddFunc(pEngine, "AnimationStop", FnAnimationStop);
AddFunc(pEngine, "AnimationSetState", FnAnimationSetState);
AddFunc(pEngine, "PlayAnimation", FnPlayAnimation);
AddFunc(pEngine, "StopAnimation", FnStopAnimation);
AddFunc(pEngine, "GetRootAnimation", FnGetRootAnimation);
AddFunc(pEngine, "GetAnimationLength", FnGetAnimationLength);
AddFunc(pEngine, "GetAnimationPosition", FnGetAnimationPosition);
AddFunc(pEngine, "GetAnimationWeight", FnGetAnimationWeight);
AddFunc(pEngine, "SetAnimationPosition", FnSetAnimationPosition);
AddFunc(pEngine, "SetAnimationWeight", FnSetAnimationWeight);
AddFunc(pEngine, "AttachMesh", FnAttachMesh);
AddFunc(pEngine, "DetachMesh", FnDetachMesh);
@ -6425,6 +6497,25 @@ C4ScriptConstDef C4ScriptConstMap[]={
{ "CSPF_NoEliminationCheck" ,C4V_Int, CSPF_NoEliminationCheck },
{ "CSPF_Invisible" ,C4V_Int, CSPF_Invisible },
{ "C4AVP_Const" ,C4V_Int, C4AVP_Const },
{ "C4AVP_Linear" ,C4V_Int, C4AVP_Linear },
{ "C4AVP_X" ,C4V_Int, C4AVP_X },
{ "C4AVP_Y" ,C4V_Int, C4AVP_Y },
{ "C4AVP_AbsX" ,C4V_Int, C4AVP_AbsX },
{ "C4AVP_AbsY" ,C4V_Int, C4AVP_AbsY },
{ "C4AVP_XDir" ,C4V_Int, C4AVP_XDir },
{ "C4AVP_YDir" ,C4V_Int, C4AVP_YDir },
{ "C4AVP_RDir" ,C4V_Int, C4AVP_RDir },
{ "C4AVP_CosR" ,C4V_Int, C4AVP_CosR },
{ "C4AVP_SinR" ,C4V_Int, C4AVP_SinR },
{ "C4AVP_CosV" ,C4V_Int, C4AVP_CosV },
{ "C4AVP_SinV" ,C4V_Int, C4AVP_SinV },
{ "C4AVP_Action" ,C4V_Int, C4AVP_Action },
{ "ANIM_Loop" ,C4V_Int, ANIM_Loop },
{ "ANIM_Hold" ,C4V_Int, ANIM_Hold },
{ "ANIM_Remove" ,C4V_Int, ANIM_Remove },
{ NULL, C4V_Any, 0} };
#define MkFnC4V (C4Value (*)(C4AulContext *cthr, C4Value*, C4Value*, C4Value*, C4Value*, C4Value*,\

View File

@ -1,7 +1,7 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2009 Armin Burgmeier
* Copyright (c) 2009-2010 Armin Burgmeier
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
*
* Portions might be copyrighted by other authors who have contributed
@ -64,6 +64,18 @@ namespace
}
};
// Reset all animation list entries corresponding to node or its children
void ClearAnimationListRecursively(std::vector<StdMeshInstance::AnimationNode*>& list, StdMeshInstance::AnimationNode* node)
{
list[node->GetNumber()] = NULL;
if(node->GetType() == StdMeshInstance::AnimationNode::LinearInterpolationNode)
{
ClearAnimationListRecursively(list, node->GetLeftChild());
ClearAnimationListRecursively(list, node->GetRightChild());
}
}
// Generate matrix to convert the mesh from Ogre coordinate system to Clonk coordinate system.
StdMeshMatrix CoordCorrection = StdMeshMatrix::Scale(-1.0f, 1.0f, 1.0f) * StdMeshMatrix::Rotate(M_PI/2.0f, 1.0f, 0.0f, 0.0f) * StdMeshMatrix::Rotate(M_PI/2.0f, 0.0f, 0.0f, 1.0f);
StdMeshMatrix CoordCorrectionInverse = StdMeshMatrix::Inverse(CoordCorrection);
@ -246,6 +258,18 @@ void StdMeshQuaternion::Normalize()
z /= length;
}
StdMeshQuaternion StdMeshQuaternion::Nlerp(const StdMeshQuaternion& lhs, const StdMeshQuaternion& rhs, float w)
{
StdMeshQuaternion q;
float c = lhs.w * rhs.w + lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z;
if(c < 0.0f)
q = lhs + w * (-rhs - lhs);
else
q = lhs + w * ( rhs - lhs);
q.Normalize();
return q;
}
StdMeshTransformation StdMeshTransformation::Zero()
{
StdMeshTransformation t;
@ -267,11 +291,11 @@ StdMeshTransformation StdMeshTransformation::Identity()
StdMeshTransformation StdMeshTransformation::Inverse(const StdMeshTransformation& transform)
{
StdMeshTransformation inv;
inv.scale = 1.0f/transform.scale;
inv.rotate = -transform.rotate;
inv.translate = inv.rotate * (inv.scale * -transform.translate);
return inv;
StdMeshTransformation t;
t.scale = 1.0f/transform.scale;
t.rotate = -transform.rotate;
t.translate = t.rotate * (t.scale * -transform.translate);
return t;
}
StdMeshTransformation StdMeshTransformation::Translate(float dx, float dy, float dz)
@ -303,6 +327,15 @@ StdMeshTransformation StdMeshTransformation::Rotate(float angle, float rx, float
return t;
}
StdMeshTransformation StdMeshTransformation::Nlerp(const StdMeshTransformation& lhs, const StdMeshTransformation& rhs, float w)
{
StdMeshTransformation t;
t.translate = (1 - w) * lhs.translate + w * rhs.translate;
t.rotate = StdMeshQuaternion::Nlerp(lhs.rotate, rhs.rotate, w);
t.scale = (1 - w) * lhs.scale + w * rhs.scale;
return t;
}
StdMeshMatrix StdMeshMatrix::Zero()
{
StdMeshMatrix m;
@ -591,6 +624,16 @@ StdMeshQuaternion operator+(const StdMeshQuaternion& lhs, const StdMeshQuaternio
return q;
}
StdMeshQuaternion operator-(const StdMeshQuaternion& lhs, const StdMeshQuaternion& rhs)
{
StdMeshQuaternion q;
q.w = lhs.w - rhs.w;
q.x = lhs.x - rhs.x;
q.y = lhs.y - rhs.y;
q.z = lhs.z - rhs.z;
return q;
}
StdMeshTransformation operator*(const StdMeshTransformation& lhs, const StdMeshTransformation& rhs)
{
StdMeshTransformation t;
@ -754,6 +797,12 @@ StdMeshTransformation StdMeshTrack::GetTransformAt(float time) const
// a) time > animation length
// b) The track does not include a frame for the very end of the animation
// Both is considered an error
if(iter == Frames.end())
{
std::map<float, StdMeshKeyFrame>::const_iterator citer = iter; --citer;
printf("Time: %f (%f-%f)\n", time, Frames.begin()->first, citer->first);
if(time<=0.0) iter = Frames.begin();
}
assert(iter != Frames.end());
if(iter == Frames.begin())
@ -774,6 +823,7 @@ StdMeshTransformation StdMeshTrack::GetTransformAt(float time) const
transformation.rotate = weight1 * iter->second.Transformation.rotate + weight2 * prev_iter->second.Transformation.rotate; // TODO: slerp or renormalize
transformation.translate = weight1 * iter->second.Transformation.translate + weight2 * prev_iter->second.Transformation.translate;
return transformation;
//return StdMeshTransformation::Nlerp(prev_iter->second.Transformation, iter->second.Transformation, weight1);
}
StdMeshAnimation::StdMeshAnimation(const StdMeshAnimation& other):
@ -1141,42 +1191,61 @@ const StdMeshAnimation* StdMesh::GetAnimationByName(const StdStrBuf& name) const
return &iter->second;
}
StdMeshInstance::AnimationRef::AnimationRef(StdMeshInstance* instance, const StdStrBuf& animation_name):
Instance(instance), Anim(NULL)
StdMeshInstance::AnimationNode::AnimationNode(const StdMeshAnimation* animation, ValueProvider* position):
Type(LeafNode), Parent(NULL)
{
const StdMeshAnimation* animation = instance->Mesh.GetAnimationByName(animation_name);
if(animation)
Leaf.Animation = animation;
Leaf.Position = position;
}
StdMeshInstance::AnimationNode::AnimationNode(AnimationNode* child_left, AnimationNode* child_right, ValueProvider* weight):
Type(LinearInterpolationNode), Parent(NULL)
{
LinearInterpolation.ChildLeft = child_left;
LinearInterpolation.ChildRight = child_right;
LinearInterpolation.Weight = weight;
}
StdMeshInstance::AnimationNode::~AnimationNode()
{
switch(Type)
{
for(unsigned int i = 0; i < instance->Animations.size(); ++i)
if(instance->Animations[i].MeshAnimation == animation)
{ Anim = &instance->Animations[i]; break; }
case LeafNode:
delete Leaf.Position;
break;
case LinearInterpolationNode:
delete LinearInterpolation.ChildLeft;
delete LinearInterpolation.ChildRight;
delete LinearInterpolation.Weight;
break;
}
}
StdMeshInstance::AnimationRef::AnimationRef(StdMeshInstance* instance, const StdMeshAnimation& animation):
Instance(instance), Anim(NULL)
bool StdMeshInstance::AnimationNode::GetBoneTransform(unsigned int bone, StdMeshTransformation& transformation)
{
for(unsigned int i = 0; i < instance->Animations.size(); ++i)
if(instance->Animations[i].MeshAnimation == &animation)
{ Anim = &instance->Animations[i]; break; }
}
StdMeshTransformation combine_with;
StdMeshTrack* track;
const StdMeshAnimation& StdMeshInstance::AnimationRef::GetAnimation() const
{
return *Anim->MeshAnimation;
}
switch(Type)
{
case LeafNode:
//if(!Leaf.Animation) return false;
track = Leaf.Animation->Tracks[bone];
if(!track) return false;
transformation = track->GetTransformAt(Leaf.Position->Value);
return true;
case LinearInterpolationNode:
if(!LinearInterpolation.ChildLeft->GetBoneTransform(bone, transformation))
return LinearInterpolation.ChildRight->GetBoneTransform(bone, transformation);
if(!LinearInterpolation.ChildRight->GetBoneTransform(bone, combine_with))
return true; // First Child affects bone
void StdMeshInstance::AnimationRef::SetPosition(float position)
{
assert(position <= Anim->MeshAnimation->Length);
Anim->Position = position;
Instance->BoneTransformsDirty = true;
}
void StdMeshInstance::AnimationRef::SetWeight(float weight)
{
Anim->Weight = weight;
Instance->BoneTransformsDirty = true;
transformation = StdMeshTransformation::Nlerp(transformation, combine_with, LinearInterpolation.Weight->Value);
return true;
default:
assert(false);
return false;
}
}
StdMeshInstance::StdMeshInstance(const StdMesh& mesh):
@ -1202,6 +1271,9 @@ StdMeshInstance::~StdMeshInstance()
{
for(AttachedMeshIter iter = AttachChildren.begin(); iter != AttachChildren.end(); ++iter)
delete iter->Child;
while(!AnimationStack.empty())
StopAnimation(AnimationStack.front());
assert(AnimationNodes.empty());
}
void StdMeshInstance::SetFaceOrdering(FaceOrdering ordering)
@ -1228,50 +1300,172 @@ void StdMeshInstance::SetFaceOrdering(FaceOrdering ordering)
}
}
bool StdMeshInstance::PlayAnimation(const StdStrBuf& animation_name, float weight)
StdMeshInstance::AnimationNode* StdMeshInstance::PlayAnimation(const StdStrBuf& animation_name, int slot, AnimationNode* sibling, ValueProvider* position, ValueProvider* weight)
{
const StdMeshAnimation* animation = Mesh.GetAnimationByName(animation_name);
if(!animation) return false;
return PlayAnimation(*animation, weight);
if(!animation) { delete position; delete weight; return NULL; }
return PlayAnimation(*animation, slot, sibling, position, weight);
}
bool StdMeshInstance::PlayAnimation(const StdMeshAnimation& animation, float weight)
StdMeshInstance::AnimationNode* StdMeshInstance::PlayAnimation(const StdMeshAnimation& animation, int slot, AnimationNode* sibling, ValueProvider* position, ValueProvider* weight)
{
for(unsigned int i = 0; i < Animations.size(); ++i)
if(Animations[i].MeshAnimation == &animation)
return false;
// Default
if(!sibling) sibling = GetRootAnimationForSlot(slot);
assert(!sibling || sibling->Slot == slot);
Animation anim;
anim.MeshAnimation = &animation;
anim.Position = 0.0f;
anim.Weight = weight;
Animations.push_back(anim);
// 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;
BoneTransformsDirty = true;
return true;
}
if(Number1 == AnimationNodes.size()) AnimationNodes.push_back(NULL);
if(sibling && Number2 == AnimationNodes.size()) AnimationNodes.push_back(NULL);
bool StdMeshInstance::StopAnimation(const StdStrBuf& animation_name)
{
const StdMeshAnimation* animation = Mesh.GetAnimationByName(animation_name);
if(!animation) return false;
return StopAnimation(*animation);
}
AnimationNode* child = new AnimationNode(&animation, position);
AnimationNodes[Number1] = child;
child->Number = Number1;
child->Slot = slot;
bool StdMeshInstance::StopAnimation(const StdMeshAnimation& animation)
{
for(std::vector<Animation>::iterator iter = Animations.begin();
iter != Animations.end(); ++iter)
if(sibling)
{
if(iter->MeshAnimation == &animation)
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)
{
Animations.erase(iter);
BoneTransformsDirty = true;
return true;
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;
return child;
}
void StdMeshInstance::StopAnimation(AnimationNode* node)
{
ClearAnimationListRecursively(AnimationNodes, node);
AnimationNode* parent = node->Parent;
if(parent == NULL)
{
AnimationNodeList::iterator iter = GetStackIterForSlot(node->Slot, false);
assert(iter != AnimationStack.end() && *iter == node);
AnimationStack.erase(iter);
delete node;
}
else
{
assert(parent->Type == AnimationNode::LinearInterpolationNode);
// Remove parent interpolation node and re-link
AnimationNode* other_child;
if(parent->LinearInterpolation.ChildLeft == node)
{
other_child = parent->LinearInterpolation.ChildRight;
parent->LinearInterpolation.ChildRight = NULL;
}
else
{
other_child = parent->LinearInterpolation.ChildLeft;
parent->LinearInterpolation.ChildLeft = NULL;
}
if(parent->Parent)
{
assert(parent->Parent->Type == AnimationNode::LinearInterpolationNode);
if(parent->Parent->LinearInterpolation.ChildLeft == parent)
parent->Parent->LinearInterpolation.ChildLeft = other_child;
else
parent->Parent->LinearInterpolation.ChildRight = other_child;
other_child->Parent = parent->Parent;
}
else
{
AnimationNodeList::iterator iter = GetStackIterForSlot(node->Slot, false);
assert(iter != AnimationStack.end() && *iter == parent);
*iter = other_child;
other_child->Parent = NULL;
}
AnimationNodes[parent->Number] = NULL;
// Recursively deletes parent and its descendants
delete parent;
}
return false;
while(AnimationNodes.back() == NULL)
AnimationNodes.erase(AnimationNodes.end()-1);
BoneTransformsDirty = true;
}
StdMeshInstance::AnimationNode* StdMeshInstance::GetAnimationNodeByNumber(unsigned int number)
{
if(number >= AnimationNodes.size()) return NULL;
return AnimationNodes[number];
}
StdMeshInstance::AnimationNode* StdMeshInstance::GetRootAnimationForSlot(int slot)
{
AnimationNodeList::iterator iter = GetStackIterForSlot(slot, false);
if(iter == AnimationStack.end()) return NULL;
return *iter;
}
void StdMeshInstance::SetAnimationPosition(AnimationNode* node, ValueProvider* position)
{
assert(node->GetType() == AnimationNode::LeafNode);
delete node->Leaf.Position;
node->Leaf.Position = position;
BoneTransformsDirty = true;
}
void StdMeshInstance::SetAnimationWeight(AnimationNode* node, ValueProvider* weight)
{
assert(node->GetType() == AnimationNode::LinearInterpolationNode);
delete node->LinearInterpolation.Weight; node->LinearInterpolation.Weight = weight;
BoneTransformsDirty = true;
}
void StdMeshInstance::ExecuteAnimation()
{
// Iterate from the back since slots might get removed
for(unsigned int i = AnimationStack.size(); i > 0; --i)
ExecuteAnimationNode(AnimationStack[i-1]);
}
const StdMeshInstance::AttachedMesh* StdMeshInstance::AttachMesh(const StdMesh& mesh, const StdStrBuf& parent_bone, const StdStrBuf& child_bone, float scale)
@ -1339,28 +1533,28 @@ void StdMeshInstance::UpdateBoneTransforms()
// Compute transformation matrix for each bone.
for(unsigned int i = 0; i < BoneTransforms.size(); ++i)
{
float accum_weight = 0.0f;
StdMeshTransformation Transformation = StdMeshTransformation::Zero();
for(unsigned int j = 0; j < Animations.size(); ++j)
{
StdMeshTrack* track = Animations[j].MeshAnimation->Tracks[i];
if(track)
{
accum_weight += Animations[j].Weight;
StdMeshTransformation Track = track->GetTransformAt(Animations[j].Position);
Transformation.scale += Animations[j].Weight * Track.scale;
Transformation.rotate += Animations[j].Weight * Track.rotate; // TODO: introduce animation stack, then slerp
Transformation.translate += Animations[j].Weight * Track.translate;
}
}
StdMeshTransformation Transformation;
const StdMeshBone& bone = Mesh.GetBone(i);
const StdMeshBone* parent = bone.GetParent();
assert(!parent || parent->Index < i);
if(!accum_weight)
bool have_transform = false;
for(unsigned int j = 0; j < AnimationStack.size(); ++j)
{
if(have_transform)
{
StdMeshTransformation other;
if(AnimationStack[j]->GetBoneTransform(i, other))
Transformation = StdMeshTransformation::Nlerp(Transformation, other, 1.0f); // TODO: Allow custom weighing for slot combination
}
else
{
have_transform = AnimationStack[j]->GetBoneTransform(i, Transformation);
}
}
if(!have_transform)
{
if(parent)
BoneTransforms[i] = BoneTransforms[parent->Index];
@ -1369,14 +1563,8 @@ void StdMeshInstance::UpdateBoneTransforms()
}
else
{
Transformation.scale *= 1.0f/accum_weight;
Transformation.rotate.Normalize(); // renormalize. We can skip this if we decide to use slerp
Transformation.translate *= 1.0f/accum_weight;
BoneTransforms[i] = StdMeshMatrix::Transform(bone.Transformation * Transformation * bone.InverseTransformation);
if(parent)
BoneTransforms[i] = BoneTransforms[parent->Index] * BoneTransforms[i];
if(parent) BoneTransforms[i] = BoneTransforms[parent->Index] * BoneTransforms[i];
}
}
@ -1413,7 +1601,7 @@ void StdMeshInstance::UpdateBoneTransforms()
}
}
// Update attachment's inverse bone transformations. Note this needs not to be done recursively.
// Update attachment's attach transformations. Note this is done recursively.
for(AttachedMeshList::iterator iter = AttachChildren.begin(); iter != AttachChildren.end(); ++iter)
{
iter->Child->UpdateBoneTransforms();
@ -1440,6 +1628,90 @@ void StdMeshInstance::UpdateBoneTransforms()
BoneTransformsDirty = false;
}
StdMeshInstance::AnimationNodeList::iterator StdMeshInstance::GetStackIterForSlot(int slot, bool create)
{
// TODO: bsearch
for(AnimationNodeList::iterator iter = AnimationStack.begin(); iter != AnimationStack.end(); ++iter)
{
if((*iter)->Slot == slot)
{
return iter;
}
else if((*iter)->Slot < slot)
{
if(!create)
return AnimationStack.end();
else
return AnimationStack.insert(iter, NULL);
}
}
if(!create)
return AnimationStack.end();
else
return AnimationStack.insert(AnimationStack.end(), NULL);
}
bool StdMeshInstance::ExecuteAnimationNode(AnimationNode* node)
{
ValueProvider* provider = NULL;
switch(node->GetType())
{
case AnimationNode::LeafNode:
provider = node->GetPositionProvider();
break;
case AnimationNode::LinearInterpolationNode:
provider = node->GetWeightProvider();
break;
}
const float old_value = provider->Value;
if(!provider->Execute())
{
if(node->GetType() == AnimationNode::LeafNode) return false;
// Remove the child with less weight (normally weight reaches 0.0 or 1.0)
if(node->GetWeight() > 0.5)
{
// Remove both children (by parent) if other wants to be deleted as well
if(!ExecuteAnimationNode(node->GetRightChild())) return false;
// Remove left child as it has less weight
StopAnimation(node->LinearInterpolation.ChildLeft);
}
else
{
// Remove both children (by parent) if other wants to be deleted as well
if(!ExecuteAnimationNode(node->GetLeftChild())) return false;
// Remove right child as it has less weight
StopAnimation(node->LinearInterpolation.ChildRight);
}
}
else
{
if(provider->Value != old_value) BoneTransformsDirty = true;
if(node->GetType() == AnimationNode::LinearInterpolationNode)
{
const bool left_result = ExecuteAnimationNode(node->GetLeftChild());
const bool right_result = ExecuteAnimationNode(node->GetRightChild());
// Remove this node completely
if(!left_result && !right_result)
return false;
// Note that either of this also removes node
if(!left_result)
StopAnimation(node->GetLeftChild());
if(!right_result)
StopAnimation(node->GetRightChild());
}
}
return true;
}
void StdMeshInstance::ReorderFaces()
{
for(unsigned int i = 0; i < Faces.size(); ++i)

View File

@ -1,7 +1,7 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2009 Armin Burgmeier
* Copyright (c) 2009-2010 Armin Burgmeier
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
*
* Portions might be copyrighted by other authors who have contributed
@ -80,7 +80,8 @@ struct StdMeshQuaternion
float LenSqr() const { return w*w+x*x+y*y+z*z; }
void Normalize();
//static StdMeshQuaternion Slerp(const StdMeshQuaternion& lhs, const StdMeshQuaternion& rhs, float t);
static StdMeshQuaternion Nlerp(const StdMeshQuaternion& lhs, const StdMeshQuaternion& rhs, float w);
//static StdMeshQuaternion Slerp(const StdMeshQuaternion& lhs, const StdMeshQuaternion& rhs, float w);
};
struct StdMeshTransformation
@ -95,6 +96,10 @@ struct StdMeshTransformation
static StdMeshTransformation Translate(float dx, float dy, float dz);
static StdMeshTransformation Scale(float sx, float sy, float sz);
static StdMeshTransformation Rotate(float angle, float rx, float ry, float rz);
// TODO: Might add path parameter if necessary
static StdMeshTransformation Nlerp(const StdMeshTransformation& lhs, const StdMeshTransformation& rhs, float w);
//static StdMeshQuaternion Slerp(const StdMeshTransformation& lhs, const StdMeshTransformation& rhs, float w);
};
class StdMeshMatrix
@ -128,6 +133,7 @@ StdMeshQuaternion operator*(const StdMeshQuaternion& lhs, float rhs);
StdMeshQuaternion operator*(float lhs, const StdMeshQuaternion& rhs);
StdMeshQuaternion& operator+=(StdMeshQuaternion& lhs, const StdMeshQuaternion& rhs);
StdMeshQuaternion operator+(const StdMeshQuaternion& lhs, const StdMeshQuaternion& rhs);
StdMeshQuaternion operator-(const StdMeshQuaternion& lhs, const StdMeshQuaternion& rhs);
StdMeshTransformation operator*(const StdMeshTransformation& lhs, const StdMeshTransformation& rhs);
StdMeshVector operator-(const StdMeshVector& rhs);
@ -300,9 +306,6 @@ private:
class StdMeshInstance
{
protected:
struct Animation;
public:
StdMeshInstance(const StdMesh& mesh);
~StdMeshInstance();
@ -316,32 +319,85 @@ public:
FaceOrdering GetFaceOrdering() const { return CurrentFaceOrdering; }
void SetFaceOrdering(FaceOrdering ordering);
// 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
// Provider for animation position or weight.
class ValueProvider
{
AnimationRef(StdMeshInstance* instance, const StdStrBuf& animation_name);
AnimationRef(StdMeshInstance* instance, const StdMeshAnimation& animation);
operator void*() const { return Anim; } // for use in boolean expressions
public:
ValueProvider(): Value(0.0f) {}
virtual ~ValueProvider() {}
const StdMeshAnimation& GetAnimation() const;
// Return false if the corresponding node is to be removed or true
// otherwise.
virtual bool Execute() = 0;
void SetPosition(float position);
void SetWeight(float weight);
private:
AnimationRef(const AnimationRef&); // noncopyable
AnimationRef& operator=(const AnimationRef&); // noncopyable
StdMeshInstance* Instance;
Animation* Anim;
float Value; // Current provider value
};
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);
// A node in the animation tree
// Can be either a leaf node, or interpolation between two other nodes
class AnimationNode
{
friend class StdMeshInstance;
public:
enum NodeType { LeafNode, LinearInterpolationNode };
AnimationNode(const StdMeshAnimation* animation, ValueProvider* position);
AnimationNode(AnimationNode* child_left, AnimationNode* child_right, ValueProvider* weight);
~AnimationNode();
bool GetBoneTransform(unsigned int bone, StdMeshTransformation& transformation);
int GetSlot() const { return Slot; }
unsigned int GetNumber() const { return Number; }
NodeType GetType() const { return Type; }
AnimationNode* GetParent() { return Parent; }
const StdMeshAnimation* GetAnimation() const { assert(Type == LeafNode); return Leaf.Animation; }
ValueProvider* GetPositionProvider() { assert(Type == LeafNode); return Leaf.Position; }
float GetPosition() const { assert(Type == LeafNode); return Leaf.Position->Value; }
AnimationNode* GetLeftChild() { assert(Type == LinearInterpolationNode); return LinearInterpolation.ChildLeft; }
AnimationNode* GetRightChild() { assert(Type == LinearInterpolationNode); return LinearInterpolation.ChildRight; }
ValueProvider* GetWeightProvider() { assert(Type == LinearInterpolationNode); return LinearInterpolation.Weight; }
float GetWeight() const { assert(Type == LinearInterpolationNode); return LinearInterpolation.Weight->Value; }
protected:
int Slot;
unsigned int Number;
NodeType Type;
AnimationNode* Parent;
union
{
struct
{
const StdMeshAnimation* Animation;
ValueProvider* Position;
} Leaf;
struct
{
AnimationNode* ChildLeft;
AnimationNode* ChildRight;
ValueProvider* Weight;
} LinearInterpolation;
};
};
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);
void StopAnimation(AnimationNode* node);
AnimationNode* GetAnimationNodeByNumber(unsigned int number);
AnimationNode* GetRootAnimationForSlot(int slot);
// 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 SetAnimationWeight(AnimationNode* node, ValueProvider* weight);
// Update animations' value providers; call once a frame
void ExecuteAnimation();
struct AttachedMesh
{
@ -354,7 +410,7 @@ public:
// Cache attach transformation, updated in UpdateBoneTransforms()
StdMeshMatrix AttachTrans;
};
typedef std::list<AttachedMesh> AttachedMeshList;
typedef AttachedMeshList::const_iterator AttachedMeshIter;
@ -394,19 +450,16 @@ public:
const StdMesh& Mesh;
protected:
typedef std::vector<AnimationNode*> AnimationNodeList;
AnimationNodeList::iterator GetStackIterForSlot(int slot, bool create);
bool ExecuteAnimationNode(AnimationNode* node);
void ReorderFaces();
FaceOrdering CurrentFaceOrdering;
struct Animation
{
const StdMeshAnimation* MeshAnimation;
float Position;
float Weight;
};
std::vector<Animation> Animations;
AnimationNodeList AnimationNodes; // for simple lookup of animation nodes by their unique number
AnimationNodeList AnimationStack; // contains top level nodes only, ordered by slot number
std::vector<StdMeshMatrix> BoneTransforms;
// Vertices transformed according to current animation, for each submesh