# 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:
parent
54f381cc1b
commit
bdbb2bcb16
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -9,7 +9,6 @@
|
||||
#ifndef _SKELETONANIMDEF_H
|
||||
#define _SKELETONANIMDEF_H
|
||||
|
||||
#include "ps/CStr.h"
|
||||
#include "maths/Vector3D.h"
|
||||
#include "maths/Quaternion.h"
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
95
source/graphics/UnitAnimation.cpp
Normal file
95
source/graphics/UnitAnimation.cpp
Normal 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);
|
||||
}
|
28
source/graphics/UnitAnimation.h
Normal file
28
source/graphics/UnitAnimation.h
Normal 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__
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -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" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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" );
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user