AMV animation state system added.

This commit is contained in:
Kyzentun
2015-02-01 17:01:15 -07:00
parent 8cde91eb2e
commit 6570db599c
6 changed files with 534 additions and 19 deletions
+5
View File
@@ -4,6 +4,11 @@ The StepMania 5 Changelog covers all post-sm-ssc changes. For a list of changes
from StepMania 4 alpha 5 to sm-ssc v1.2.5, see Changelog_sm-ssc.txt.
________________________________________________________________________________
2015/02/01
----------
* [ActorMultiVertex] Animation state system added. AMVs can now have
animated textures controlled by states written in lua. [kyzentun]
2015/01/20
----------
* [ScreenSelect] ChoiceNames can be a lua command that is evaluated to get a
+17
View File
@@ -489,22 +489,39 @@
<Function name='SetTextureMode'/>
</Class>
<Class base='Actor' name='ActorMultiVertex'>
<Function name='AddState'/>
<Function name='AddQuadState'/>
<Function name='GetCurrDrawMode'/>
<Function name='GetCurrFirstToDraw'/>
<Function name='GetCurrNumToDraw'/>
<Function name='GetDestDrawMode'/>
<Function name='GetDestFirstToDraw'/>
<Function name='GetDestNumToDraw'/>
<Function name='GetNumStates'/>
<Function name='GetNumQuadStates'/>
<Function name='GetNumVertices'/>
<Function name='GetQuadState'/>
<Function name='GetSpline'/>
<Function name='GetState'/>
<Function name='GetStateData'/>
<Function name='GetTexture'/>
<Function name='GetUseAnimationState'/>
<Function name='LoadTexture'/>
<Function name='RemoveState'/>
<Function name='RemoveQuadState'/>
<Function name='SetAllStateDelays'/>
<Function name='SetDrawState'/>
<Function name='SetEffectMode'/>
<Function name='SetLineWidth'/>
<Function name='SetNumVertices'/>
<Function name='SetQuadState'/>
<Function name='SetSecondsIntoAnimation'/>
<Function name='SetState'/>
<Function name='SetStateData'/>
<Function name='SetStateProperties'/>
<Function name='SetTexture'/>
<Function name='SetTextureMode'/>
<Function name='SetUseAnimationState'/>
<Function name='SetVertex'/>
<Function name='SetVertices'/>
<Function name='SetVertsFromSplines'/>
+57
View File
@@ -1517,6 +1517,63 @@ save yourself some time, copy this for undocumented things:
</Function>
</Class>
<Class name='ActorMultiVertex'>
<Function name='AddQuadState' return='' arguments='int offset'>
The list of quad states is used to determine which animation state is used for each quad. The offset is added to the AMV's current state, and the resulting state is used.
</Function>
<Function name='AddState' return='' arguments='table state_data'>
Adds an animation state to the ActorMultiVertex. The state_data table must be like this:<br />
{{left, top, right, bottom}, delay}<br />
left, top, right, and bottom are pixel coordinates, starting at 0. If delay is 0 or negative, the state will last forever.
</Function>
<Function name='GetUseAnimationState' return= 'bool' arguments=''>
Returns whether the AMV uses the animation state.
</Function>
<Function name='SetUseAnimationState' return= '' arguments='bool use'>
Sets whether the AMV uses the animation state.<br />
This works best when using DrawMode_Quads.<br />
AMV's can have animated textures like sprites. Each state tells the AMV what part of the texture to use, and how long the state lasts.<br />
Use AddState to add a state onto the end, or SetStateProperties to set all the states at once, or SetState to set a single state.<br />
Each quad has its own offset that is added to the current state. Use AddQuadState to add to the list of quad states, or SetQuadState to set an existing quad state.
</Function>
<Function name='GetNumStates' return= 'int' arguments=''>
Returns the number of states the AMV has.
</Function>
<Function name='GetNumQuadStates' return= 'int' arguments=''>
Returns the number of quad states in the destination tween state for the AMV.
</Function>
<Function name='GetState' return= 'int' arguments=''>
Returns the id of the current state.
</Function>
<Function name='SetState' return= '' arguments='int id'>
Sets the current state.
</Function>
<Function name='GetQuadState' return= 'int' arguments='int id'>
Returns the offset of the requested quad state.
</Function>
<Function name='SetQuadState' return= '' arguments='int id, int offset'>
Sets the offset of the requested quad state.
</Function>
<Function name='GetStateData' return= 'table' arguments='int id'>
Returns a table containing the data for the requested state.
</Function>
<Function name='SetStateData' return= '' arguments='int id, table state_data'>
Sets the requested state to the data in state_data. Similar to AddState, but SetStateData only works on states that have already been added.
</Function>
<Function name='SetStateProperties' return= '' arguments='table {state_data, ...}'>
Each element of the table must be a state_data table, and is used to construct one state. The table as a whole is the entire list of all states for the AMV.
</Function>
<Function name='RemoveState' return= '' arguments='int id'>
Removes the requested state from the state list.
</Function>
<Function name='RemoveQuadState' return= '' arguments='int id'>
Removes the requested quad state from the quad state list.
</Function>
<Function name='SetAllStateDelays' return= '' arguments='float delay'>
Sets the delay for every state to delay.
</Function>
<Function name='SetSecondsIntoAnimation' return= '' arguments='float seconds'>
Sets how far into its animation the AMV is.
</Function>
<Function name='SetVertex' return='void' arguments='int index, table { table pos, table color, table textcoords }'>
Sets vertex number <code>index</code> with the properties provided. The tables of properties are each optional and can be provided in any order.
</Function>
+405
View File
@@ -15,6 +15,8 @@
#include "LuaManager.h"
#include "LocalizedString.h"
const float min_state_delay= 0.0001f;
static const char *DrawModeNames[] = {
"Quads",
"QuadStrip",
@@ -69,6 +71,10 @@ ActorMultiVertex::ActorMultiVertex()
_splines[i].redimension(3);
_splines[i].m_owned_by_actor= true;
}
_skip_next_update= true;
_use_animation_state= false;
_secs_into_state= 0.0f;
_cur_state= 0;
}
ActorMultiVertex::~ActorMultiVertex()
@@ -86,6 +92,11 @@ ActorMultiVertex::ActorMultiVertex( const ActorMultiVertex &cpy ):
CPY( _EffectMode );
CPY( _TextureMode );
CPY( _splines );
CPY(_skip_next_update);
CPY(_use_animation_state);
CPY(_secs_into_state);
CPY(_cur_state);
CPY(_states);
#undef CPY
if( cpy._Texture != NULL )
@@ -373,6 +384,199 @@ CubicSplineN* ActorMultiVertex::GetSpline(size_t i)
return &(_splines[i]);
}
void ActorMultiVertex::SetState(size_t i)
{
ASSERT(i < _states.size());
_cur_state= i;
_secs_into_state= 0.0f;
}
void ActorMultiVertex::SetAllStateDelays(float delay)
{
FOREACH(State, _states, s)
{
s->delay= delay;
}
}
float ActorMultiVertex::GetAnimationLengthSeconds() const
{
float tot= 0.0f;
FOREACH_CONST(State, _states, s)
{
tot+= s->delay;
}
return tot;
}
void ActorMultiVertex::SetSecondsIntoAnimation(float seconds)
{
SetState(0);
if(_Texture)
{
_Texture->SetPosition(seconds);
}
_secs_into_state= seconds;
UpdateAnimationState(true);
}
void ActorMultiVertex::UpdateAnimationState(bool force_update)
{
AMV_TweenState& dest= AMV_DestTweenState();
vector<RageSpriteVertex>& verts= dest.vertices;
vector<size_t>& qs= dest.quad_states;
if(!_use_animation_state || _states.empty() ||
dest._DrawMode == DrawMode_LineStrip || qs.empty())
{ return; }
bool state_changed= force_update;
if(_states.size() > 1)
{
while(_states[_cur_state].delay > min_state_delay &&
_secs_into_state + min_state_delay > _states[_cur_state].delay)
{
_secs_into_state-= _states[_cur_state].delay;
_cur_state= (_cur_state + 1) % _states.size();
state_changed= true;
}
}
if(state_changed)
{
size_t first= dest.FirstToDraw;
size_t last= first+dest.GetSafeNumToDraw(dest._DrawMode, dest.NumToDraw);
#define STATE_ID const size_t state_id= (_cur_state + qs[quad_id % qs.size()]) % _states.size();
switch(AMV_DestTweenState()._DrawMode)
{
case DrawMode_Quads:
for(size_t i= first; i < last; ++i)
{
const size_t quad_id= (i-first)/4;
STATE_ID;
switch((i-first)%4)
{
case 0:
verts[i].t.x= _states[state_id].rect.left;
verts[i].t.y= _states[state_id].rect.top;
break;
case 1:
verts[i].t.x= _states[state_id].rect.right;
verts[i].t.y= _states[state_id].rect.top;
break;
case 2:
verts[i].t.x= _states[state_id].rect.right;
verts[i].t.y= _states[state_id].rect.bottom;
break;
case 3:
verts[i].t.x= _states[state_id].rect.left;
verts[i].t.y= _states[state_id].rect.bottom;
break;
}
}
break;
case DrawMode_QuadStrip:
for(size_t i= first; i < last; ++i)
{
const size_t quad_id= (i-first)/2;
STATE_ID;
switch((i-first)%2)
{
case 0:
verts[i].t.x= _states[state_id].rect.left;
verts[i].t.y= _states[state_id].rect.top;
break;
case 1:
verts[i].t.x= _states[state_id].rect.left;
verts[i].t.y= _states[state_id].rect.bottom;
break;
}
}
break;
case DrawMode_Strip:
case DrawMode_Fan:
for(size_t i= first; i < last; ++i)
{
const size_t quad_id= (i-first);
STATE_ID;
verts[i].t.x= _states[state_id].rect.left;
verts[i].t.y= _states[state_id].rect.top;
}
break;
case DrawMode_Triangles:
for(size_t i= first; i < last; ++i)
{
const size_t quad_id= (i-first)/3;
STATE_ID;
switch((i-first)%3)
{
case 0:
verts[i].t.x= _states[state_id].rect.left;
verts[i].t.y= _states[state_id].rect.top;
break;
case 1:
verts[i].t.x= _states[state_id].rect.right;
verts[i].t.y= _states[state_id].rect.top;
break;
case 2:
verts[i].t.x= _states[state_id].rect.right;
verts[i].t.y= _states[state_id].rect.bottom;
break;
}
}
break;
case DrawMode_SymmetricQuadStrip:
for(size_t i= first; i < last; ++i)
{
const size_t quad_id= (i-first)/3;
STATE_ID;
switch((i-first)%3)
{
case 0:
case 2:
verts[i].t.x= _states[state_id].rect.left;
verts[i].t.y= _states[state_id].rect.top;
break;
case 1:
verts[i].t.x= _states[state_id].rect.right;
verts[i].t.y= _states[state_id].rect.top;
break;
}
}
break;
}
}
#undef STATE_ID
}
void ActorMultiVertex::EnableAnimation(bool bEnable)
{
bool bWasEnabled = m_bIsAnimating;
Actor::EnableAnimation(bEnable);
if(bEnable && !bWasEnabled)
{
_skip_next_update = true;
}
}
void ActorMultiVertex::Update(float fDelta)
{
Actor::Update(fDelta); // do tweening
const bool skip_this_movie_update= _skip_next_update;
_skip_next_update= false;
if(!m_bIsAnimating) { return; }
if(!_Texture) { return; }
float time_passed = GetEffectDeltaTime();
_secs_into_state += time_passed;
if(_secs_into_state < 0)
{
wrap(_secs_into_state, GetAnimationLengthSeconds());
}
UpdateAnimationState();
if(!skip_this_movie_update)
{
_Texture->DecodeSeconds(max(0, time_passed));
}
}
void ActorMultiVertex::SetCurrentTweenStart()
{
AMV_start= AMV_current;
@@ -719,6 +923,189 @@ public:
COMMON_RETURN_SELF;
}
DEFINE_METHOD(GetUseAnimationState, _use_animation_state);
static int SetUseAnimationState(T* p, lua_State *L)
{
p->_use_animation_state= BArg(1);
COMMON_RETURN_SELF;
}
static int GetNumStates(T* p, lua_State *L)
{
lua_pushnumber(L, p->GetNumStates());
return 1;
}
static void FillStateFromLua(lua_State *L, ActorMultiVertex::State& state,
RageTexture* tex, int index)
{
if(tex == NULL)
{
luaL_error(L, "The texture must be set before adding states.");
}
// State looks like this:
// {{left, top, right, bottom}, delay}
#define DATA_ERROR(i) \
if(!lua_istable(L, i)) \
{ \
luaL_error(L, "The state data must be in a table like this: {{left, top, right, bottom}, delay}"); \
}
#define SET_SIDE(i, side) \
lua_rawgeti(L, -1, i); \
state.side= FArg(-1); \
lua_pop(L, 1);
DATA_ERROR(index);
lua_rawgeti(L, index, 1);
DATA_ERROR(-1);
SET_SIDE(1, rect.left);
SET_SIDE(2, rect.top);
SET_SIDE(3, rect.right);
SET_SIDE(4, rect.bottom);
lua_pop(L, 1);
SET_SIDE(2, delay);
const float width_ratio= tex->GetImageToTexCoordsRatioX();
const float height_ratio= tex->GetImageToTexCoordsRatioY();
state.rect.left= state.rect.left * width_ratio;
state.rect.top= state.rect.top * height_ratio;
// Pixel centers are at .5, so add an extra pixel to the size to adjust.
state.rect.right= (state.rect.right * width_ratio) + width_ratio;
state.rect.bottom= (state.rect.bottom * height_ratio) + height_ratio;
#undef SET_SIDE
#undef DATA_ERROR
}
static int AddState(T* p, lua_State *L)
{
ActorMultiVertex::State s;
FillStateFromLua(L, s, p->GetTexture(), 1);
p->AddState(s);
COMMON_RETURN_SELF;
}
static size_t ValidStateIndex(T* p, lua_State *L, int pos)
{
int index= IArg(pos)-1;
if(index < 0 || static_cast<size_t>(index) >= p->GetNumStates())
{
luaL_error(L, "Invalid state index %d.", index+1);
}
return static_cast<size_t>(index);
}
static int RemoveState(T* p, lua_State *L)
{
p->RemoveState(ValidStateIndex(p, L, 1));
COMMON_RETURN_SELF;
}
static int GetState(T* p, lua_State *L)
{
lua_pushnumber(L, p->GetState()+1);
return 1;
}
static int SetState(T* p, lua_State *L)
{
p->SetState(ValidStateIndex(p, L, 1));
COMMON_RETURN_SELF;
}
static int GetStateData(T* p, lua_State *L)
{
RageTexture* tex= p->GetTexture();
if(tex == NULL)
{
luaL_error(L, "The texture must be set before adding states.");
}
const float width_pix= tex->GetImageToTexCoordsRatioX();
const float height_pix= tex->GetImageToTexCoordsRatioY();
const float width_ratio= 1.0f / tex->GetImageToTexCoordsRatioX();
const float height_ratio= 1.0f / tex->GetImageToTexCoordsRatioY();
const ActorMultiVertex::State& state=
p->GetStateData(ValidStateIndex(p, L, 1));
lua_createtable(L, 2, 0);
lua_createtable(L, 4, 0);
lua_pushnumber(L, state.rect.left * width_ratio);
lua_rawseti(L, -2, 1);
lua_pushnumber(L, state.rect.top * height_ratio);
lua_rawseti(L, -2, 2);
lua_pushnumber(L, (state.rect.right - width_pix) * width_ratio);
lua_rawseti(L, -2, 3);
lua_pushnumber(L, (state.rect.bottom + height_pix) * height_ratio);
lua_rawseti(L, -2, 4);
lua_rawseti(L, -2, 1);
lua_pushnumber(L, state.delay);
lua_rawseti(L, -2, 2);
return 1;
}
static int SetStateData(T* p, lua_State *L)
{
ActorMultiVertex::State& state= p->GetStateData(ValidStateIndex(p, L, 1));
FillStateFromLua(L, state, p->GetTexture(), 2);
COMMON_RETURN_SELF;
}
static int SetStateProperties(T* p, lua_State *L)
{
if(!lua_istable(L, 1))
{
luaL_error(L, "The states must be inside a table.");
}
RageTexture* tex= p->GetTexture();
if(tex == NULL)
{
luaL_error(L, "The texture must be set before adding states.");
}
vector<ActorMultiVertex::State> new_states;
size_t num_states= lua_objlen(L, 1);
new_states.resize(num_states);
for(size_t i= 0; i < num_states; ++i)
{
lua_rawgeti(L, 1, i);
FillStateFromLua(L, new_states[i], tex, -1);
lua_pop(L, 1);
}
p->SetStateProperties(new_states);
COMMON_RETURN_SELF;
}
static int SetAllStateDelays(T* p, lua_State *L)
{
p->SetAllStateDelays(FArg(1));
COMMON_RETURN_SELF;
}
DEFINE_METHOD(GetAnimationLengthSeconds, GetAnimationLengthSeconds());
static int SetSecondsIntoAnimation(T* p, lua_State *L)
{
p->SetSecondsIntoAnimation(FArg(1));
COMMON_RETURN_SELF;
}
static int GetNumQuadStates(T* p, lua_State *L)
{
lua_pushnumber(L, p->GetNumQuadStates());
return 1;
}
static size_t QuadStateIndex(T* p, lua_State *L, int pos)
{
int index= IArg(pos)-1;
if(index < 0 || static_cast<size_t>(index) >= p->GetNumQuadStates())
{
luaL_error(L, "Invalid state index %d.", index+1);
}
return static_cast<size_t>(index);
}
static int AddQuadState(T* p, lua_State *L)
{
p->AddQuadState(IArg(1)-1);
COMMON_RETURN_SELF;
}
static int RemoveQuadState(T* p, lua_State *L)
{
p->RemoveQuadState(QuadStateIndex(p, L, 1));
COMMON_RETURN_SELF;
}
static int GetQuadState(T* p, lua_State *L)
{
lua_pushnumber(L, p->GetQuadState(QuadStateIndex(p, L, 1))+1);
return 1;
}
static int SetQuadState(T* p, lua_State *L)
{
p->SetQuadState(QuadStateIndex(p, L, 1), IArg(1)-1);
COMMON_RETURN_SELF;
}
static int SetTexture( T* p, lua_State *L )
{
RageTexture *Texture = Luna<RageTexture>::check(L, 1);
@@ -763,6 +1150,24 @@ public:
ADD_METHOD( GetSpline );
ADD_METHOD( SetVertsFromSplines );
ADD_METHOD(GetUseAnimationState);
ADD_METHOD(SetUseAnimationState);
ADD_METHOD(GetNumStates);
ADD_METHOD(GetNumQuadStates);
ADD_METHOD(AddState);
ADD_METHOD(RemoveState);
ADD_METHOD(GetState);
ADD_METHOD(SetState);
ADD_METHOD(GetStateData);
ADD_METHOD(SetStateData);
ADD_METHOD(SetStateProperties);
ADD_METHOD(SetAllStateDelays);
ADD_METHOD(SetSecondsIntoAnimation);
ADD_METHOD(AddQuadState);
ADD_METHOD(RemoveQuadState);
ADD_METHOD(GetQuadState);
ADD_METHOD(SetQuadState);
// Copy from RageTexture
ADD_METHOD( SetTexture );
ADD_METHOD( GetTexture );
+42 -1
View File
@@ -38,7 +38,7 @@ public:
struct AMV_TweenState
{
AMV_TweenState(): _DrawMode(DrawMode_Invalid), FirstToDraw(0),
AMV_TweenState(): _DrawMode(DrawMode_Invalid), FirstToDraw(0),
NumToDraw(-1), line_width(1.0f)
{}
static void MakeWeightedAverage(AMV_TweenState& average_out, const AMV_TweenState& ts1, const AMV_TweenState& ts2, float percent_between);
@@ -49,6 +49,7 @@ public:
int GetSafeNumToDraw( DrawMode dm, int num ) const;
vector<RageSpriteVertex> vertices;
vector<size_t> quad_states;
DrawMode _DrawMode;
int FirstToDraw;
@@ -67,6 +68,8 @@ public:
}
const AMV_TweenState& AMV_DestTweenState() const { return const_cast<ActorMultiVertex*>(this)->AMV_DestTweenState(); }
virtual void EnableAnimation(bool bEnable);
virtual void Update(float fDelta);
virtual bool EarlyAbortDraw() const;
virtual void DrawPrimitives();
virtual void DrawInternal( const AMV_TweenState *TS );
@@ -111,6 +114,39 @@ public:
void SetVertsFromSplines();
CubicSplineN* GetSpline(size_t i);
struct State
{
RectF rect;
float delay;
};
int GetNumStates() const { return _states.size(); }
void AddState(const State& new_state) { _states.push_back(new_state); }
void RemoveState(size_t i)
{ ASSERT(i < _states.size()); _states.erase(_states.begin()+i); }
size_t GetState() { return _cur_state; }
State& GetStateData(size_t i)
{ ASSERT(i < _states.size()); return _states[i]; }
void SetStateData(size_t i, const State& s)
{ ASSERT(i < _states.size()); _states[i]= s; }
void SetStateProperties(const vector<State>& new_states)
{ _states= new_states; SetState(0); }
void SetState(size_t i);
void SetAllStateDelays(float delay);
float GetAnimationLengthSeconds() const;
void SetSecondsIntoAnimation(float seconds);
void UpdateAnimationState(bool force_update= false);
size_t GetNumQuadStates() const
{ return AMV_DestTweenState().quad_states.size(); }
void AddQuadState(size_t s)
{ AMV_DestTweenState().quad_states.push_back(s); }
void RemoveQuadState(size_t i)
{ AMV_DestTweenState().quad_states.erase(AMV_DestTweenState().quad_states.begin()+i); }
size_t GetQuadState(size_t i)
{ return AMV_DestTweenState().quad_states[i]; }
void SetQuadState(size_t i, size_t s)
{ AMV_DestTweenState().quad_states[i]= s; }
bool _use_animation_state;
virtual void PushSelf( lua_State *L );
private:
@@ -130,6 +166,11 @@ private:
// Four splines for controlling vert positions, because quads drawmode
// requires four. -Kyz
vector<CubicSplineN> _splines;
bool _skip_next_update;
float _secs_into_state;
size_t _cur_state;
vector<State> _states;
};
/**
+8 -18
View File
@@ -186,11 +186,6 @@ void Sprite::LoadFromNode( const XNode* pNode )
{
newState.fDelay = 0.1f;
}
if(newState.fDelay <= min_state_delay)
{
LuaHelpers::ReportScriptErrorFmt("%s: State #%i has near-zero delay.", ActorUtil::GetWhere(pNode).c_str(), i+1);
newState.fDelay= 0.1f;
}
pFrame->GetAttrValue( "Frame", iFrameIndex );
if( iFrameIndex >= m_pTexture->GetNumFrames() )
@@ -362,7 +357,12 @@ void Sprite::UpdateAnimationState()
// We already know what's going to show.
if( m_States.size() > 1 )
{
while( m_fSecsIntoState+min_state_delay > m_States[m_iCurState].fDelay ) // it's time to switch frames
// UpdateAnimationState changed to not loop forever on negative state
// delay. This allows a state to last forever when it is reached, so
// the animation has a built-in ending point. -Kyz
while(m_States[m_iCurState].fDelay > min_state_delay &&
m_fSecsIntoState+min_state_delay > m_States[m_iCurState].fDelay)
// it's time to switch frames
{
// increment frame and reset the counter
m_fSecsIntoState -= m_States[m_iCurState].fDelay; // leave the left over time for the next frame
@@ -1047,15 +1047,6 @@ void Sprite::AddImageCoords( float fX, float fY )
class LunaSprite: public Luna<Sprite>
{
public:
static float valid_state_delay(lua_State* L, int i)
{
float delay= FArg(i);
if(delay <= min_state_delay)
{
luaL_error(L, "State delay cannot be less than or equal to zero.");
}
return delay;
}
static int Load( T* p, lua_State *L )
{
if( lua_isnil(L, 1) )
@@ -1151,7 +1142,7 @@ public:
lua_getfield(L, -1, "Delay");
if(lua_isnumber(L, -1))
{
new_state.fDelay= valid_state_delay(L, -1);
new_state.fDelay= FArg(-1);
}
lua_pop(L, 1);
RectF r= new_state.rect;
@@ -1216,8 +1207,7 @@ public:
static int GetNumStates( T* p, lua_State *L ) { lua_pushnumber( L, p->GetNumStates() ); return 1; }
static int SetAllStateDelays( T* p, lua_State *L )
{
float delay= valid_state_delay(L, -1);
p->SetAllStateDelays(delay);
p->SetAllStateDelays(FArg(-1));
COMMON_RETURN_SELF;
}