1
0
forked from 0ad/0ad

# Updated unit animation code.

Added UnitAnimation class, to act as the interface between the entity
and actor. (Currently doesn't work very well, but it does make
animations loop smoothly and sometimes kind of makes them stay
synchronised.)
Fixed corpse animation - it now plays the final frame of the death
animation before turning static.
Fixed update/interpolate timings.
Added JS function saveProfileData.
Updated ffmpeg library.

This was SVN commit r4880.
This commit is contained in:
Ykkrosh 2007-02-10 03:09:52 +00:00
parent 54f381cc1b
commit bdbb2bcb16
24 changed files with 350 additions and 172 deletions

View File

@ -3,6 +3,7 @@
#include "MapReader.h"
#include "graphics/Camera.h"
#include "graphics/CinemaTrack.h"
#include "graphics/GameView.h"
#include "graphics/Model.h"
#include "graphics/ObjectManager.h"

View File

@ -5,7 +5,6 @@
#include "lib/res/handle.h"
#include "ps/CStr.h"
#include "LightEnv.h"
#include "CinemaTrack.h"
#include "ps/FileUnpacker.h"
class CObjectEntry;

View File

@ -225,20 +225,35 @@ void CModel::Update(float time)
if (m_Anim && m_Anim->m_AnimDef && m_BoneMatrices)
{
// adjust for animation speed
float animtime = time*m_AnimSpeed;
float animTimeDelta = time*m_AnimSpeed;
float oldAnimTime = m_AnimTime;
// update animation time, but don't calculate bone matrices - do that (lazily) when
// something requests them; that saves some calculation work for offscreen models,
// and also assures the world space, inverted bone matrices (required for normal
// skinning) are up to date with respect to m_Transform
m_AnimTime += animtime;
m_AnimTime += animTimeDelta;
float duration = m_Anim->m_AnimDef->GetDuration();
if (m_AnimTime > duration)
{
if (m_Flags & MODELFLAG_NOLOOPANIMATION)
{
if (m_NextAnim)
SetAnimation(m_NextAnim);
else
{
// Changing to no animation - probably becoming a corpse.
// Make sure the last displayed frame is the final frame
// of the animation.
float nearlyEnd = duration - 1.f; // 1 msec
if (abs(oldAnimTime - nearlyEnd) < 1.f)
SetAnimation(NULL);
else
m_AnimTime = nearlyEnd;
}
}
else
m_AnimTime = fmod(m_AnimTime, duration);
}
@ -255,6 +270,24 @@ void CModel::Update(float time)
m_Props[i].m_Model->Update(time);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool CModel::NeedsNewAnim(float time) const
{
// TODO: fix UnitAnimation so it correctly loops animated props
if (m_Anim && m_Anim->m_AnimDef && m_BoneMatrices)
{
// adjust for animation speed
float animtime = time * m_AnimSpeed;
float duration = m_Anim->m_AnimDef->GetDuration();
if (m_AnimTime + animtime > duration)
return true;
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// InvalidatePosition
void CModel::InvalidatePosition()

View File

@ -29,6 +29,10 @@ class CSkeletonAnimDef;
// information for a model in game
class CModel : public CRenderableObject
{
friend class CUnitAnimation;
// HACK - we should probably move the rest of this class's animation state
// into the animation class, then it wouldn't need friend access
public:
struct Prop {
Prop() : m_Point(0), m_Model(0), m_ObjectEntry(0) {}
@ -50,6 +54,9 @@ public:
void CalcBounds();
// update this model's state; 'time' is the time since the last update, in MS
void Update(float time);
// returns true if Update(time) will require a new animation (due to the
// current one ending)
bool NeedsNewAnim(float time) const;
// get the model's geometry data
CModelDefPtr GetModelDef() { return m_pModelDef; }
@ -76,7 +83,7 @@ public:
bool SetAnimation(CSkeletonAnim* anim, bool once = false, float speed = 1000.0f, CSkeletonAnim* next = NULL);
// get the currently playing animation, if any
CSkeletonAnim* GetAnimation() { return m_Anim; }
CSkeletonAnim* GetAnimation() const { return m_Anim; }
// set the animation state to be the same as from another; both models should
// be compatible types (same type of skeleton)

View File

@ -9,6 +9,7 @@
#include "ps/XML/Xeromyces.h"
#include "ps/CLogger.h"
#include "lib/timer.h"
#include "maths/MathUtil.h"
#define LOG_CATEGORY "graphics"
@ -153,17 +154,13 @@ bool CObjectBase::Load(const char* filename)
}
else if (ae.Name == at_event)
{
anim.m_ActionPos = CStr(ae.Value).ToDouble();
if (anim.m_ActionPos < 0.0) anim.m_ActionPos = 0.0;
else if (anim.m_ActionPos > 100.0) anim.m_ActionPos = 1.0;
else if (anim.m_ActionPos > 1.0) anim.m_ActionPos /= 100.0;
float pos = CStr(ae.Value).ToFloat();
anim.m_ActionPos = clamp(pos, 0.f, 1.f);
}
else if (ae.Name == at_load)
{
anim.m_ActionPos2 = CStr(ae.Value).ToDouble();
if (anim.m_ActionPos2 < 0.0) anim.m_ActionPos2 = 0.0;
else if (anim.m_ActionPos2 > 100.0) anim.m_ActionPos2 = 1.0;
else if (anim.m_ActionPos2 > 1.0) anim.m_ActionPos2 /= 100.0;
float pos = CStr(ae.Value).ToFloat();
anim.m_ActionPos2 = clamp(pos, 0.f, 1.f);
}
else
; // unrecognised element

View File

@ -26,8 +26,8 @@ public:
float m_Speed;
// fraction of the way through the animation that the interesting bit(s)
// happens
double m_ActionPos;
double m_ActionPos2;
float m_ActionPos;
float m_ActionPos2;
};
struct Prop {

View File

@ -25,7 +25,10 @@ public:
CSkeletonAnimDef* m_AnimDef;
// speed at which this animation runs
float m_Speed;
// time(s) during the animation at which the interesting bit(s) happens (fractional)
// Times during the animation at which the interesting bits happen. Measured
// as fractions (0..1) of the total animation length.
// ActionPos is used for melee hits, projectile launches, etc.
// ActionPos2 is used for loading projectile ammunition.
float m_ActionPos;
float m_ActionPos2;
// object space bounds of the model when this animation is applied to it

View File

@ -9,6 +9,7 @@
#include "precompiled.h"
#include "SkeletonAnimDef.h"
#include "maths/MathUtil.h"
#include "ps/FilePacker.h"
#include "ps/FileUnpacker.h"
@ -31,28 +32,47 @@ CSkeletonAnimDef::~CSkeletonAnimDef()
// animation
void CSkeletonAnimDef::BuildBoneMatrices(float time,CMatrix3D* matrices) const
{
float fstartframe=time/m_FrameTime;
u32 startframe=u32(time/m_FrameTime);
float deltatime=fstartframe-startframe;
float fstartframe = time/m_FrameTime;
u32 startframe = u32(time/m_FrameTime);
float deltatime = fstartframe-startframe;
startframe%=m_NumFrames;
startframe %= m_NumFrames;
u32 endframe=startframe+1;
endframe%=m_NumFrames;
u32 endframe = startframe + 1;
endframe %= m_NumFrames;
u32 i;
for (i=0;i<m_NumKeys;i++) {
const Key& startkey=GetKey(startframe,i);
const Key& endkey=GetKey(endframe,i);
if (endframe == 0)
{
// This might be something like a death animation, and interpolating
// between the final frame and the initial frame is wrong, because they're
// totally different. So if we've looped around to endframe==0, just display
// the animation's final frame with no interpolation.
// (TODO: this is only sometimes valid - how can we tell the difference?)
for (u32 i = 0; i < m_NumKeys; i++)
{
const Key& key = GetKey(startframe, i);
matrices[i].SetIdentity();
matrices[i].Rotate(key.m_Rotation);
matrices[i].Translate(key.m_Translation);
}
}
else
{
for (u32 i = 0; i < m_NumKeys; i++)
{
const Key& startkey= GetKey(startframe, i);
const Key& endkey = GetKey(endframe, i);
CVector3D trans=startkey.m_Translation*(1-deltatime)+endkey.m_Translation*deltatime;
CVector3D trans = Interpolate(startkey.m_Translation, endkey.m_Translation, deltatime);
// TODO: is slerp the best thing to use here?
CQuaternion rot;
rot.Slerp(startkey.m_Rotation,endkey.m_Rotation,deltatime);
rot.Slerp(startkey.m_Rotation, endkey.m_Rotation, deltatime);
matrices[i].SetIdentity();
matrices[i].Rotate(rot);
matrices[i].Translate(trans);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -9,7 +9,6 @@
#ifndef _SKELETONANIMDEF_H
#define _SKELETONANIMDEF_H
#include "ps/CStr.h"
#include "maths/Vector3D.h"
#include "maths/Quaternion.h"

View File

@ -7,6 +7,7 @@
#include "ObjectManager.h"
#include "SkeletonAnim.h"
#include "SkeletonAnimDef.h"
#include "UnitAnimation.h"
#include "ps/Game.h"
#include "simulation/Entity.h"
@ -17,10 +18,12 @@ CUnit::CUnit(CObjectEntry* object, CEntity* entity, CObjectManager& objectManage
m_ID(-1), m_ActorSelections(actorSelections), m_PlayerID(-1),
m_ObjectManager(objectManager)
{
m_Animation = new CUnitAnimation(*this);
}
CUnit::~CUnit()
{
delete m_Animation;
delete m_Model;
}
@ -129,11 +132,31 @@ CSkeletonAnim* CUnit::GetRandomAnimation(const CStr& name)
return ::GetRandomAnimation(name, m_Object);
}
bool CUnit::HasAnimation(const CStr& name)
{
return (m_Object->GetRandomAnimation(name) != NULL);
}
bool CUnit::IsPlayingAnimation(const CStr& name)
{
return (m_Model->GetAnimation() && m_Model->GetAnimation()->m_Name == name);
}
void CUnit::SetAnimationState(const CStr& name, bool once, float speed, bool keepSelection)
{
m_Animation->SetAnimationState(name, once, speed, keepSelection);
}
void CUnit::SetAnimationSync(float timeUntilActionPos)
{
m_Animation->SetAnimationSync(timeUntilActionPos);
}
void CUnit::UpdateModel(float frameTime)
{
m_Animation->Update(frameTime);
}
void CUnit::SetPlayerID(int id)
{

View File

@ -10,7 +10,7 @@ class CObjectEntry;
class CObjectManager;
class CEntity;
class CSkeletonAnim;
class CStrW;
class CUnitAnimation;
/////////////////////////////////////////////////////////////////////////////////////////////
// CUnit: simple "actor" definition - defines a sole object within the world
@ -47,10 +47,16 @@ public:
// SetEntitySelection(name) should typically be used before this.
bool SetRandomAnimation(const CStr& name, bool once = false, float speed = 0.0f);
void SetAnimationState(const CStr& name, bool once = false, float speed = 0.0f, bool keepSelection = false);
void SetAnimationSync(float timeUntilActionPos);
void UpdateModel(float frameTime);
// Returns a random animation matching 'name'. If none is found,
// returns idle instead.
CSkeletonAnim* GetRandomAnimation(const CStr& name);
bool HasAnimation(const CStr& name);
// Sets the entity-selection, and updates the unit to use the new
// actor variation.
void SetEntitySelection(const CStr& selection);
@ -85,6 +91,8 @@ private:
// player id of this unit (only read for graphical effects), or -1 if unspecified
int m_PlayerID;
CUnitAnimation* m_Animation;
// unique (per map) ID number for units created in the editor, as a
// permanent way of referencing them. -1 for non-editor units.
int m_ID;

View File

@ -0,0 +1,95 @@
#include "precompiled.h"
#include "UnitAnimation.h"
#include "graphics/Model.h"
#include "graphics/SkeletonAnim.h"
#include "graphics/SkeletonAnimDef.h"
#include "graphics/Unit.h"
#include "ps/CStr.h"
namespace
{
// Randomly modify the speed, so that units won't stay perfectly
// synchronised if they're playing animations of the same length
float DesyncSpeed(float speed)
{
// const float var = 0.05f; // max fractional variation from default
// return speed * (1.f - var + 2.f*var*(rand(0, 256)/255.f));
// TODO: enable this desyncing for cases where we don't care about
// accurate looping, and just don't do it for e.g. projectile-launchers
// where we do care
return speed;
}
}
CUnitAnimation::CUnitAnimation(CUnit& unit)
: m_Unit(unit), m_State("idle"), m_Looping(true), m_Speed(0.f), m_OriginalSpeed(0.f), m_TimeToNextSync(0.f)
{
}
void CUnitAnimation::SetAnimationState(const CStr& name, bool once, float speed, bool keepSelection)
{
if (name == m_State)
return;
m_State = name;
m_Looping = !once;
m_Speed = m_OriginalSpeed = speed;
m_TimeToNextSync = 0.f;
if (! keepSelection)
m_Unit.SetEntitySelection(name);
m_Unit.SetRandomAnimation(m_State, !m_Looping, DesyncSpeed(m_Speed));
}
void CUnitAnimation::SetAnimationSync(float timeUntilActionPos)
{
// We need to finish looping our animation at the specified time from now.
// Assume it's playing at nearly the right speed, and we just need to perhaps
// shift it a little bit to stay in sync.
m_TimeToNextSync = timeUntilActionPos;
CModel* model = m_Unit.GetModel();
// Calculate the required playback speed so ActionPos coincides with timeUntilActionPos
float currentPos = model->m_AnimTime / model->m_Anim->m_AnimDef->GetDuration();
float length = (model->m_Anim->m_ActionPos - currentPos);
if (length < 0.f)
length += 1.f;
float requiredSpeed = length / m_TimeToNextSync;
// Shift in the right direction
if (requiredSpeed > m_OriginalSpeed)
m_Speed = std::min(requiredSpeed, m_OriginalSpeed*1.1f);
else if (requiredSpeed < m_OriginalSpeed)
m_Speed = std::max(requiredSpeed, m_OriginalSpeed*0.9f);
model->m_AnimSpeed = m_Speed * model->m_Anim->m_AnimDef->GetDuration() * model->m_Anim->m_Speed;
// TODO: this should use the ActionPos2, instead of totally ignoring it
m_Unit.ShowAmmunition();
}
void CUnitAnimation::Update(float time)
{
CModel* model = m_Unit.GetModel();
// Choose a new random animation if we're going to loop
if (m_Looping && model->NeedsNewAnim(time))
{
m_Unit.SetRandomAnimation(m_State, !m_Looping, DesyncSpeed(m_Speed));
}
if (m_TimeToNextSync >= 0.0 && m_TimeToNextSync-time < 0.0)
m_Unit.HideAmmunition();
m_TimeToNextSync -= time;
// TODO: props should get a new random animation once they loop, independent
// of the object they're propped onto
model->Update(time);
}

View File

@ -0,0 +1,28 @@
#ifndef UNITANIMATION_H__
#define UNITANIMATION_H__
#include "ps/CStr.h"
class CUnit;
class CUnitAnimation : boost::noncopyable
{
public:
CUnitAnimation(CUnit& unit);
// (All times are measured in seconds)
void SetAnimationState(const CStr& name, bool once, float speed, bool keepSelection);
void SetAnimationSync(float timeUntilActionPos);
void Update(float time);
private:
CUnit& m_Unit;
CStr m_State;
bool m_Looping;
float m_Speed;
float m_OriginalSpeed;
float m_TimeToNextSync;
};
#endif // UNITANIMATION_H__

View File

@ -1143,10 +1143,10 @@ static LibError SndData_reload(SndData * sd, const char * fn, Handle hsd)
ALsizei al_size = (ALsizei)file_size;
#ifdef OGG_HACK
std::vector<u8> data;
data.reserve(500000);
if(file_type == FT_OGG)
{
std::vector<u8> data;
data.reserve(500000);
if(file_type == FT_OGG)
{
sd->o = ogg_create();
ogg_give_raw(sd->o, (void*)file, file_size);
ogg_open(sd->o, sd->al_fmt, sd->al_freq);
@ -1163,11 +1163,11 @@ if(file_type == FT_OGG)
while(bytes_read > 0);
al_data = &data[0];
al_size = (ALsizei)datasize;
}
else
{
}
else
{
sd->o = NULL;
}
}
#endif
(void)file_buf_free(file);

View File

@ -10,8 +10,9 @@
#define _FILEUNPACKER_H
#include <vector>
#include "lib/res/file/file.h"
#include "CStr.h"
#include "lib/res/file/file_io.h"
class CStr8;
#include "ps/Errors.h"
#ifndef ERROR_GROUP_FILE_DEFINED
@ -47,7 +48,7 @@ public:
// the given number of bytes have been read
void UnpackRaw(void* rawdata, u32 rawdatalen);
// UnpackString: unpack a string from the raw data stream
void UnpackString(CStr& result);
void UnpackString(CStr8& result);
private:
// the data read from file and used during unpack operations

View File

@ -30,6 +30,7 @@
#include "ps/Util.h"
#include "ps/i18n.h"
#include "graphics/CinemaTrack.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/MapReader.h"

View File

@ -9,24 +9,28 @@
#include "ScriptGlue.h"
#include "JSConversions.h"
#include "GameEvents.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/MapWriter.h"
#include "graphics/Unit.h"
#include "graphics/UnitManager.h"
#include "graphics/scripting/JSInterface_Camera.h"
#include "graphics/scripting/JSInterface_LightEnv.h"
#include "gui/CGUI.h"
#include "lib/timer.h"
#include "maths/scripting/JSInterface_Vector3D.h"
#include "network/Client.h"
#include "network/Server.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/Game.h"
#include "ps/GameSetup/GameSetup.h"
#include "ps/Interact.h"
#include "network/Client.h"
#include "network/Server.h"
#include "ps/i18n.h"
#include "ps/Hotkey.h"
#include "ps/Interact.h"
#include "ps/ProfileViewer.h"
#include "ps/i18n.h"
#include "ps/scripting/JSCollection.h"
#include "ps/scripting/JSInterface_Console.h"
#include "ps/scripting/JSInterface_Selection.h"
@ -34,20 +38,18 @@
#include "renderer/Renderer.h"
#include "renderer/SkyManager.h"
#include "renderer/WaterManager.h"
#include "graphics/Unit.h"
#include "graphics/UnitManager.h"
#include "simulation/EntityTemplateCollection.h"
#include "simulation/TechnologyCollection.h"
#include "simulation/Entity.h"
#include "simulation/EntityFormation.h"
#include "simulation/EntityHandles.h"
#include "simulation/EntityManager.h"
#include "simulation/EntityTemplate.h"
#include "simulation/EntityTemplateCollection.h"
#include "simulation/FormationManager.h"
#include "simulation/TriggerManager.h"
#include "simulation/LOSManager.h"
#include "simulation/Scheduler.h"
#include "simulation/Simulation.h"
#include "simulation/TechnologyCollection.h"
#include "simulation/TriggerManager.h"
#ifndef NO_GUI
# include "gui/scripting/JSInterface_IGUIObject.h"
@ -1058,6 +1060,14 @@ JSBool getGlobal( JSContext* cx, JSObject* globalObject, uint argc, jsval* argv,
return( JS_TRUE );
}
// Saves the current profiling data to the logs/profile.txt file
JSBool saveProfileData( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval )
{
JSU_REQUIRE_NO_PARAMS();
g_ProfileViewer.SaveToFile();
return( JS_TRUE );
}
// Activates the building placement cursor for placing a building. The currently selected units
// are then ordered to construct the building if it is placed.
// params: templateName - the name of the entity to place.
@ -1474,6 +1484,7 @@ JSFunctionSpec ScriptFunctionTable[] =
JS_FUNC(v3dist, v3dist, 2)
JS_FUNC(buildTime, buildTime, 0)
JS_FUNC(getGlobal, getGlobal, 0)
JS_FUNC(saveProfileData, saveProfileData, 0)
// end of table marker
{0, 0, 0, 0, 0}

View File

@ -257,11 +257,12 @@ void CEntity::kill(bool keepActor)
g_Selection.removeAll( me );
entf_set(ENTF_DESTROYED);
g_EntityManager.m_refd[me.m_handle] = false; // refd must be made false when DESTROYED is set
g_EntityManager.SetDeath(true); // remember that a unit died this frame
// If we have a death animation and want to keep the actor, play that animation
if( keepActor && m_actor &&
m_actor->GetRandomAnimation( "death" ) != m_actor->GetRandomAnimation( "idle" ) )
m_actor->HasAnimation( "death" ) )
{
// Prevent "wiggling" as we try to interpolate between here and our death position (if we were moving)
m_graphics_position = m_position;
@ -274,16 +275,13 @@ void CEntity::kill(bool keepActor)
// Play death animation and keep the actor in the game in a dead state
// (TODO: remove the actor after some time through some kind of fading mechanism)
m_actor->SetEntitySelection( "death" );
m_actor->SetRandomAnimation( "death", true );
m_actor->SetAnimationState( "death", true );
}
else
{
g_Game->GetWorld()->GetUnitManager().RemoveUnit( m_actor );
delete( m_actor );
g_Game->GetWorld()->GetUnitManager().DeleteUnit( m_actor );
m_actor = NULL;
g_EntityManager.m_refd[me.m_handle] = false;
me = HEntity(); // Will deallocate the entity, assuming nobody else has a reference to it
}
}
@ -526,8 +524,7 @@ void CEntity::update( size_t timestep )
{
if( ( m_lastState != -1 ) || !m_actor->GetModel()->GetAnimation() )
{
m_actor->SetEntitySelection( "idle" );
m_actor->SetRandomAnimation( "idle" );
m_actor->SetAnimationState( "idle" );
}
}
}

View File

@ -210,9 +210,6 @@ public:
// Position in the current state's cycle
static const size_t NOT_IN_CYCLE = (size_t)-1;
size_t m_fsm_cyclepos; // -cycle_length....cycle_length
CSkeletonAnim* m_fsm_animation; // the animation we're about to play this cycle,
size_t m_fsm_anipos; // the time at which we should start playing it.
size_t m_fsm_anipos2; // for when there are two animation-related events we need to take care of.
CEntityOrders m_orderQueue;
std::deque<CEntityListener> m_listeners;

View File

@ -36,14 +36,14 @@ enum EGotoSituation
bool CEntity::shouldRun(float distance)
{
if (!entf_get(ENTF_SHOULD_RUN))
if( !entf_get(ENTF_SHOULD_RUN) )
return false;
// tired
if(m_staminaCurr <= 0)
if( m_staminaCurr <= 0 )
return false;
if(distance >= m_runMaxRange)
if( distance >= m_runMaxRange )
return false;
// don't start running if less than minimum
@ -65,22 +65,16 @@ float CEntity::chooseMovementSpeed( float distance )
int sector = rintf( angle / (PI/2) * m_base->m_pitchDivs );
speed -= sector * m_base->m_pitchValue;
// TODO: the animation code requires unicode for now. will be changed to
// 8bit later (for consistency; note that filenames etc. need not be
// unicode), so remove this then.
const CStrW u_anim_name(anim_name);
entf_set_to(ENTF_IS_RUNNING, should_run);
if ( m_actor )
{
if ( !m_actor->IsPlayingAnimation( anim_name ) )
{
m_actor->SetEntitySelection( u_anim_name );
m_actor->SetRandomAnimation( anim_name, false, speed );
m_actor->SetAnimationState( anim_name, false, speed );
// Animation desync
m_actor->GetModel()->Update( rand( 0, 1000 ) / 1000.0f );
entf_set_to(ENTF_IS_RUNNING, should_run);
}
}
@ -409,6 +403,7 @@ bool CEntity::processContactAction( CEntityOrder* current, size_t UNUSED(timeste
return true;
}
}
bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t timestep_millis, const CStr& animation, CScriptEvent* contactEvent, SEntityAction* action )
{
HEntity target = current->m_target_entity;
@ -416,31 +411,9 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times
if( m_fsm_cyclepos != NOT_IN_CYCLE )
{
size_t nextpos = m_fsm_cyclepos + timestep_millis * 2;
if( ( m_fsm_cyclepos <= m_fsm_anipos ) &&
( nextpos > m_fsm_anipos ) )
{
// Start playing.
// Start the animation. Actual damage/gather will be done in a
// few hundred ms, at the 'action point' of the animation we're
// now setting.
entf_clear(ENTF_IS_RUNNING);
// TODO: this is set to be looping, because apparently it otherwise
// plays one frame of 'idle' after e.g. attacks. But this way means
// animations sometimes play ~1.5 times then repeat, which looks
// broken too.
//m_actor->GetModel()->SetAnimation( m_fsm_animation, true, 1000.0f * m_fsm_animation->m_AnimDef->GetDuration() / (float)action->m_Speed, m_actor->GetRandomAnimation( "idle" ) );
m_actor->GetModel()->SetAnimation( m_fsm_animation, false, 1000.0f * m_fsm_animation->m_AnimDef->GetDuration() / (float)action->m_Speed );
}
if( ( m_fsm_cyclepos <= m_fsm_anipos2 ) &&
( nextpos > m_fsm_anipos2 ) )
{
// Load the ammunition.
m_actor->ShowAmmunition();
}
if( ( m_fsm_cyclepos <= action->m_Speed ) && ( nextpos > action->m_Speed ) )
{
m_actor->HideAmmunition();
// TODO: Play a sound here. Use m_base->m_SoundGroupTable[animation] to get the
// name of the soundgroup XML file to play.
@ -449,8 +422,7 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times
// Cancel current order
entf_clear(ENTF_IS_RUNNING);
entf_clear(ENTF_SHOULD_RUN);
m_actor->SetEntitySelection( "idle" );
m_actor->SetRandomAnimation( "idle" );
m_actor->SetAnimationState( "idle" );
popOrder();
if( m_orderQueue.empty() && target.isValid() )
{
@ -503,7 +475,7 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times
if( current->m_source == CEntityOrder::SOURCE_UNIT_AI && !m_stance->allowsMovement() )
{
popOrder();
m_actor->SetRandomAnimation( "idle" );
m_actor->SetAnimationState( "idle" );
return false; // We're not allowed to move at all by the current stance
}
@ -513,7 +485,7 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times
// The pathfinder will push its result in front of the current order
if( !g_Pathfinder.requestAvoidPath( me, current, action->m_MinRange + 2.0f ) )
{
m_actor->SetRandomAnimation( "idle" ); // Nothing we can do.. maybe we'll find a better target
m_actor->SetAnimationState( "idle" ); // Nothing we can do.. maybe we'll find a better target
popOrder();
}
@ -596,44 +568,8 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times
entf_clear(ENTF_IS_RUNNING);
}
// Pick our animation, calculate the time to play it, and start the timer.
m_actor->SetEntitySelection( animation );
m_fsm_animation = m_actor->GetRandomAnimation( animation );
// Here's the idea - we want to be at that animation's event point
// when the timer reaches action->m_Speed. The timer increments by 2 every millisecond.
// animation->m_actionpos is the time offset into that animation that event
// should happen. So...
m_fsm_anipos = (size_t)( action->m_Speed * ( 1.0f - 2 * m_fsm_animation->m_ActionPos ) );
// But...
if( m_fsm_anipos < 0 ) // (FIXME: m_fsm_anipos is unsigned, so this will never be true...)
{
// We ought to have started it in the past. Oh well.
// Here's what we'll do: play it now, and advance it to
// the point it should be by now.
m_actor->GetModel()->SetAnimation( m_fsm_animation, true, 1000.0f * m_fsm_animation->m_AnimDef->GetDuration() / (float)action->m_Speed, m_actor->GetRandomAnimation( "idle" ) );
m_actor->GetModel()->Update( action->m_Speed * ( m_fsm_animation->m_ActionPos / 1000.0f - 0.0005f ) );
}
else
{
// If we've just transitioned, play idle. Otherwise, let the previous animation complete, if it
// hasn't already.
if( entf_get(ENTF_TRANSITION) )
{
// (don't change actor's entity-selection)
m_actor->SetRandomAnimation( "idle" );
}
}
// Load time needs to be animation->m_ActionPos2 ms after the start of the animation.
m_fsm_anipos2 = m_fsm_anipos + (size_t)( action->m_Speed * m_fsm_animation->m_ActionPos2 * 2 );
if( m_fsm_anipos2 < 0 ) // (FIXME: m_fsm_anipos2 is unsigned, so this will never be true...)
{
// Load now.
m_actor->ShowAmmunition();
}
m_actor->SetAnimationState( animation, false, 1000.f / (float)action->m_Speed );
m_actor->SetAnimationSync( (float)( action->m_Speed / 2) / 1000.f );
m_fsm_cyclepos = 0;

View File

@ -101,6 +101,12 @@ bool CSimulation::Update(double frameTime)
return ok;
}
void CSimulation::DiscardMissedUpdates()
{
if (m_DeltaTime > 0.0)
m_DeltaTime = 0.0;
}
void CSimulation::Interpolate(double frameTime)
{
double turnLength = m_pTurnManager->GetTurnLength()/1000.0;
@ -110,10 +116,9 @@ void CSimulation::Interpolate(double frameTime)
// m_DeltaTime/turnLength will usually be between -1 and 0, indicating
// the time until the next frame, so we can use that easily.
// If the simulation is going too slowly and hasn't been giving a chance
// to catch up before Interpolate is called, then m_DeltaTime > 0, and
// we'll just end up being clamped to offset=1 inside CEntity::interpolate,
// which is alright.
Interpolate(frameTime, m_DeltaTime / turnLength + 1.0);
// to catch up before Interpolate is called, then m_DeltaTime > 0, so we'll
// just clamp it to offset=1, which is alright.
Interpolate(frameTime, clamp(m_DeltaTime / turnLength + 1.0, 0.0, 1.0));
}
void CSimulation::Interpolate(double frameTime, double offset)
@ -122,25 +127,27 @@ void CSimulation::Interpolate(double frameTime, double offset)
const std::vector<CUnit*>& units = m_pWorld->GetUnitManager().GetUnits();
for (size_t i = 0; i < units.size(); ++i)
units[i]->GetModel()->Update((float)frameTime);
units[i]->UpdateModel((float)frameTime);
g_EntityManager.interpolateAll(offset);
m_pWorld->GetProjectileManager().InterpolateAll(frameTime);
m_pWorld->GetProjectileManager().InterpolateAll(offset);
g_Renderer.GetWaterManager()->m_WaterTexTimer += frameTime;
}
void CSimulation::Simulate()
{
float time = m_pTurnManager->GetTurnLength();
PROFILE_START( "scheduler tick" );
g_Scheduler.update(m_pTurnManager->GetTurnLength());
g_Scheduler.update(time);
PROFILE_END( "scheduler tick" );
PROFILE_START( "entity updates" );
g_EntityManager.updateAll( m_pTurnManager->GetTurnLength() );
g_EntityManager.updateAll(time);
PROFILE_END( "entity updates" );
PROFILE_START( "projectile updates" );
m_pWorld->GetProjectileManager().UpdateAll( m_pTurnManager->GetTurnLength() );
m_pWorld->GetProjectileManager().UpdateAll(time);
PROFILE_END( "projectile updates" );
PROFILE_START( "los update" );
@ -148,7 +155,7 @@ void CSimulation::Simulate()
PROFILE_END( "los update" );
PROFILE_START("trigger update");
g_TriggerManager.Update( m_pTurnManager->GetTurnLength() );
g_TriggerManager.Update(time);
PROFILE_END("trigger udpate");
PROFILE_START( "turn manager update" );

View File

@ -44,6 +44,11 @@ public:
// Returns false if it can't keep up with the desired simulation rate.
bool Update(double frameTime);
// If the last Update couldn't keep up with the desired rate, ignore that
// and don't try to catch up when Update is called again. Will completely break
// synchronisation of sim time vs real time.
void DiscardMissedUpdates();
// Update the graphical representations of the simulation by the given time.
void Interpolate(double frameTime);

View File

@ -17,6 +17,7 @@
#include "lib/res/file/vfs.h"
#include "ps/CLogger.h"
#include "ps/DllLoader.h"
#include "ps/Profile.h"
using namespace AtlasMessage;
@ -201,6 +202,10 @@ bool BeginAtlas(const CmdLineArgs& args, const DllLoader& dll)
state.view->Render();
if (CProfileManager::IsInitialised())
g_Profiler.Frame();
double time = get_time();
if (recent_activity)
last_activity = time;

View File

@ -197,9 +197,14 @@ void ViewGame::Update(float frameLength)
ok = g_Game->Update(0.0, false); // don't add on any extra sim time
}
}
// Interpolate the graphics - we only want to do it once per visual frame,
// Interpolate the graphics - we only want to do this once per visual frame,
// not in every call to g_Game->Update
g_Game->GetSimulation()->Interpolate(actualFrameLength);
// If we still couldn't keep up, just drop the sim rate instead of building
// up a (potentially very large) backlog of required updates
g_Game->GetSimulation()->DiscardMissedUpdates();
}
// Cinematic motion should be independent of simulation update, so we can