#include "global.h" #include #include "ActorMultiVertex.h" #include "RageTextureManager.h" #include "XmlFile.h" #include "RageLog.h" #include "RageDisplay.h" #include "RageTexture.h" #include "RageTimer.h" #include "RageUtil.h" #include "ActorUtil.h" #include "LuaBinding.h" #include "LuaManager.h" #include "LocalizedString.h" #include const float min_state_delay= 0.0001f; static const char *DrawModeNames[] = { "Quads", "QuadStrip", "Fan", "Strip", "Triangles", "LineStrip", "SymmetricQuadStrip", }; static const int DrawModeVertexGroupSizes[] = { 4, // Quads 2, // QuadStrip 1, // Fan 1, // Strip 3, // Triangles 1, // LineStrip 3, // SymmetricQuadStrip 1, // NUM_DrawMode 1, // DrawMode_Invalid }; static const int DrawModeMinimumToDraw[] = { 4, // Quads 4, // QuadStrip 3, // Fan 3, // Strip 3, // Triangles 2, // LineStrip 6, // SymmetricQuadStrip 1, // NUM_DrawMode 1, // DrawMode_Invalid }; XToString( DrawMode ); XToLocalizedString( DrawMode ); LuaXType( DrawMode ); REGISTER_ACTOR_CLASS( ActorMultiVertex ); ActorMultiVertex::ActorMultiVertex() { // Use blank texture by default. RageTextureID ID = TEXTUREMAN->GetDefaultTextureID(); _Texture = TEXTUREMAN->LoadTexture( ID ); _EffectMode = EffectMode_Normal; _TextureMode = TextureMode_Modulate; _splines.resize(num_vert_splines); for(size_t i= 0; i < num_vert_splines; ++i) { _splines[i].redimension(3); _splines[i].m_owned_by_actor= true; } _skip_next_update= true; _decode_movie= true; _use_animation_state= false; _secs_into_state= 0.0f; _cur_state= 0; } ActorMultiVertex::~ActorMultiVertex() { UnloadTexture(); } ActorMultiVertex::ActorMultiVertex( const ActorMultiVertex &cpy ): Actor( cpy ) { #define CPY(a) a = cpy.a CPY( AMV_Tweens ); CPY( AMV_current ); CPY( AMV_start ); 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 != nullptr ) { _Texture = TEXTUREMAN->CopyTexture( cpy._Texture ); } else { _Texture = nullptr; } } void ActorMultiVertex::LoadFromNode( const XNode* Node ) { RString path; Node->GetAttrValue( "Texture", path ); if( !path.empty() && !TEXTUREMAN->IsTextureRegistered( RageTextureID(path) ) ) { ActorUtil::GetAttrPath( Node, "Texture", path ); } if( !path.empty() ) { LoadFromTexture( path ); } Actor::LoadFromNode( Node ); } void ActorMultiVertex::SetTexture( RageTexture *Texture ) { if( _Texture != Texture ) { UnloadTexture(); _Texture = Texture; } } void ActorMultiVertex::LoadFromTexture( RageTextureID ID ) { RageTexture *Texture = nullptr; if( _Texture && _Texture->GetID() == ID ) { return; } Texture = TEXTUREMAN->LoadTexture( ID ); SetTexture( Texture ); } void ActorMultiVertex::UnloadTexture() { if( _Texture != nullptr ) { TEXTUREMAN->UnloadTexture( _Texture ); _Texture = nullptr; } } void ActorMultiVertex::SetNumVertices( size_t n ) { if( n == 0 ) { for( size_t i = 0; i < AMV_Tweens.size(); ++i ) { AMV_Tweens[i].vertices.clear(); } AMV_current.vertices.clear(); AMV_start.vertices.clear(); } else { for( size_t i = 0; i < AMV_Tweens.size(); ++i ) { AMV_Tweens[i].vertices.resize( n ); } AMV_current.vertices.resize( n ); AMV_start.vertices.resize( n ); } } void ActorMultiVertex::AddVertex() { for( size_t i = 0; i < AMV_Tweens.size(); ++i ) { AMV_Tweens[i].vertices.push_back( RageSpriteVertex() ); } AMV_current.vertices.push_back( RageSpriteVertex() ); AMV_start.vertices.push_back( RageSpriteVertex() ); } void ActorMultiVertex::AddVertices( int Add ) { int size = AMV_DestTweenState().vertices.size(); size += Add; for( size_t i = 0; i < AMV_Tweens.size(); ++i ) { AMV_Tweens[i].vertices.resize( size ); } AMV_current.vertices.resize( size ); AMV_start.vertices.resize( size ); } void ActorMultiVertex::SetVertexPos( int index, float x, float y, float z ) { AMV_DestTweenState().vertices[index].p = RageVector3( x, y, z ); } void ActorMultiVertex::SetVertexColor( int index, RageColor c ) { AMV_DestTweenState().vertices[index].c = c; } void ActorMultiVertex::SetVertexCoords( int index, float TexCoordX, float TexCoordY ) { AMV_DestTweenState().vertices[index].t = RageVector2( TexCoordX, TexCoordY ); } void ActorMultiVertex::DrawPrimitives() { Actor::SetGlobalRenderStates(); // set Actor-specified render states DISPLAY->ClearAllTextures(); DISPLAY->SetTexture( TextureUnit_1, _Texture->GetTexHandle() ); Actor::SetTextureRenderStates(); DISPLAY->SetEffectMode( _EffectMode ); // set temporary diffuse and glow static AMV_TweenState TS; AMV_TempState = &TS; TS = AMV_current; // skip if no change or fully transparent if( m_pTempState->diffuse[0] != RageColor(1, 1, 1, 1) && m_pTempState->diffuse[0].a > 0 ) { for( size_t i=0; i < TS.vertices.size(); i++ ) { // RageVColor uses a uint8_t for each channel. 0-255. // RageColor uses a float. 0-1. // So each channel of the RageVColor needs to be converted to a float, // multiplied by the channel from the RageColor, then the result // converted to uint8_t. If implicit conversion is allowed to happen, // sometimes the compiler decides to turn the RageColor into a uint8_t, // which makes any value other than 1 into 0. Thus, the explicit // conversions. -Kyz #define MULT_COLOR_ELEMENTS(color_a, color_b) \ color_a= static_cast(static_cast(color_a) * color_b); // RageVColor * RageColor MULT_COLOR_ELEMENTS(TS.vertices[i].c.b, m_pTempState->diffuse[0].b); MULT_COLOR_ELEMENTS(TS.vertices[i].c.r, m_pTempState->diffuse[0].r); MULT_COLOR_ELEMENTS(TS.vertices[i].c.g, m_pTempState->diffuse[0].g); MULT_COLOR_ELEMENTS(TS.vertices[i].c.a, m_pTempState->diffuse[0].a); #undef MULT_COLOR_ELEMENTS } } // Draw diffuse pass. if( m_pTempState->diffuse[0].a > 0 ) { DISPLAY->SetTextureMode( TextureUnit_1, _TextureMode ); DrawInternal( AMV_TempState ); } // Draw the glow pass if( m_pTempState->glow.a > 0 ) { for( size_t i=0; i < TS.vertices.size(); i++ ) { TS.vertices[i].c = m_pTempState->glow; } DISPLAY->SetTextureMode( TextureUnit_1, TextureMode_Glow ); DrawInternal( AMV_TempState ); } } void ActorMultiVertex::DrawInternal( const AMV_TweenState *TS ) { int FirstToDraw = TS->FirstToDraw; int NumToDraw = TS->GetSafeNumToDraw(TS->_DrawMode, TS->NumToDraw); if( NumToDraw == 0 ) { // Nothing to draw. return; } switch( TS->_DrawMode ) { case DrawMode_Quads: { DISPLAY->DrawQuads( &TS->vertices[FirstToDraw], NumToDraw ); break; } case DrawMode_QuadStrip: { DISPLAY->DrawQuadStrip( &TS->vertices[FirstToDraw], NumToDraw ); break; } case DrawMode_Fan: { DISPLAY->DrawFan( &TS->vertices[FirstToDraw], NumToDraw ); break; } case DrawMode_Strip: { DISPLAY->DrawStrip( &TS->vertices[FirstToDraw], NumToDraw ); break; } case DrawMode_Triangles: { DISPLAY->DrawTriangles( &TS->vertices[FirstToDraw], NumToDraw ); break; } case DrawMode_LineStrip: { DISPLAY->DrawLineStrip( &TS->vertices[FirstToDraw], NumToDraw, TS->line_width ); break; } case DrawMode_SymmetricQuadStrip: { DISPLAY->DrawSymmetricQuadStrip( &TS->vertices[FirstToDraw], NumToDraw ); break; } default: break; } DISPLAY->SetEffectMode( EffectMode_Normal ); } bool ActorMultiVertex::EarlyAbortDraw() const { if( AMV_current.FirstToDraw >= (int) AMV_current.vertices.size() || AMV_current._DrawMode >= NUM_DrawMode ) { return true; } return false; } void ActorMultiVertex::SetVertsFromSplinesInternal(size_t num_splines, size_t offset) { std::vector& verts= AMV_DestTweenState().vertices; size_t first= AMV_DestTweenState().FirstToDraw + offset; size_t num_verts= AMV_DestTweenState().GetSafeNumToDraw(AMV_DestTweenState()._DrawMode, AMV_DestTweenState().NumToDraw) - offset; std::vector tper(num_splines, 0.0f); float num_parts= (static_cast(num_verts) / static_cast(num_splines)) - 1.0f; for(size_t i= 0; i < num_splines; ++i) { tper[i]= _splines[i].get_max_t() / num_parts; } for(size_t v= 0; v < num_verts; ++v) { std::vector pos; const int spi= v%num_splines; float part= static_cast(v/num_splines); _splines[spi].evaluate(part * tper[spi], pos); verts[v+first].p.x= pos[0]; verts[v+first].p.y= pos[1]; verts[v+first].p.z= pos[2]; } } void ActorMultiVertex::SetVertsFromSplines() { if(AMV_DestTweenState().vertices.empty()) { return; } switch(AMV_DestTweenState()._DrawMode) { case DrawMode_Quads: SetVertsFromSplinesInternal(4, 0); break; case DrawMode_QuadStrip: case DrawMode_Strip: SetVertsFromSplinesInternal(2, 0); break; case DrawMode_Fan: // Skip the first vert because it is the center of the fan. -Kyz SetVertsFromSplinesInternal(1, 1); break; case DrawMode_Triangles: case DrawMode_SymmetricQuadStrip: SetVertsFromSplinesInternal(3, 0); break; case DrawMode_LineStrip: SetVertsFromSplinesInternal(1, 0); break; default: break; } } CubicSplineN* ActorMultiVertex::GetSpline(size_t i) { ASSERT(i < num_vert_splines); 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) { for (State &s : _states) { s.delay= delay; } } float ActorMultiVertex::GetAnimationLengthSeconds() const { auto calcDelay = [](float total, State const &s) { return total + s.delay; }; return std::accumulate(_states.begin(), _states.end(), 0.f, calcDelay); } 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(); std::vector& verts= dest.vertices; std::vector& 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; default: 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 && _decode_movie) { _Texture->DecodeSeconds(std::max(0.0f, time_passed)); } } void ActorMultiVertex::SetCurrentTweenStart() { AMV_start= AMV_current; } void ActorMultiVertex::EraseHeadTween() { AMV_current= AMV_Tweens[0]; AMV_Tweens.erase(AMV_Tweens.begin()); } void ActorMultiVertex::UpdatePercentThroughTween( float PercentThroughTween ) { AMV_TweenState::MakeWeightedAverage( AMV_current, AMV_start, AMV_Tweens[0], PercentThroughTween ); } void ActorMultiVertex::BeginTweening( float time, ITween *pTween ) { Actor::BeginTweening( time, pTween ); if( AMV_Tweens.size() >= 1 ) // if there was already a TS on the stack { AMV_Tweens.push_back( AMV_Tweens.back() ); } else { AMV_Tweens.push_back( AMV_current ); } } void ActorMultiVertex::StopTweening() { AMV_Tweens.clear(); Actor::StopTweening(); } void ActorMultiVertex::FinishTweening() { if( !AMV_Tweens.empty() ) { AMV_current = AMV_DestTweenState(); } Actor::FinishTweening(); } void ActorMultiVertex::AMV_TweenState::SetDrawState( DrawMode dm, int first, int num ) { if(first >= (int)vertices.size() && vertices.size() > 0) { LuaHelpers::ReportScriptErrorFmt("ActorMultiVertex:SetDrawState: FirstToDraw > vertices.size(), %d > %u", FirstToDraw + 1, (unsigned int)vertices.size() ); return; } int safe_num= GetSafeNumToDraw( dm, num ); if( num != safe_num && num != -1 ) { LuaHelpers::ReportScriptErrorFmt("ActorMultiVertex:SetDrawState: NumToDraw %d is not valid for %u vertices with DrawMode %s", num, (unsigned int)vertices.size(), DrawModeNames[dm] ); return; } _DrawMode= dm; FirstToDraw= first; NumToDraw= num; } void ActorMultiVertex::AMV_TweenState::MakeWeightedAverage(AMV_TweenState& average_out, const AMV_TweenState& ts1, const AMV_TweenState& ts2, float percent_between) { average_out.line_width= lerp(percent_between, ts1.line_width, ts2.line_width); for(size_t v= 0; v < average_out.vertices.size(); ++v) { WeightedAvergeOfRSVs(average_out.vertices[v], ts1.vertices[v], ts2.vertices[v], percent_between); } } int ActorMultiVertex::AMV_TweenState::GetSafeNumToDraw( DrawMode dm, int num ) const { int max = vertices.size() - FirstToDraw; // NumToDraw == -1 draws all vertices if( num == -1 || num > max ) { num = max; } num -= ( num % DrawModeVertexGroupSizes[dm]); if( num < DrawModeMinimumToDraw[dm]) { num = 0; } return num; } // lua start #include "LuaBinding.h" /** @brief Allow Lua to have access to the ActorMultiVertex. */ class LunaActorMultiVertex: public Luna { public: static int SetNumVertices( T* p, lua_State *L ) { p->SetNumVertices( IArg(1) ); COMMON_RETURN_SELF; } static int GetNumVertices( T* p, lua_State *L ) { lua_pushnumber( L, p->GetNumVertices() ); return 1; } static void SetVertexFromStack(T* p, lua_State* L, size_t VertexIndex, int DataStackIndex) { // Use the number of arguments to determine which property a table is for if(lua_type(L, DataStackIndex) != LUA_TTABLE) { LuaHelpers::ReportScriptErrorFmt("ActorMultiVertex::SetVertex: non-table parameter supplied. Table of tables of vertex data expected."); return; } size_t NumDataParts = lua_objlen(L, DataStackIndex); for(size_t i = 0; i < NumDataParts; ++i) { lua_pushnumber(L, i+1); lua_gettable(L, DataStackIndex); int DataPieceIndex = lua_gettop(L); size_t DataPieceElements = lua_objlen(L, DataPieceIndex); if(lua_type(L, DataPieceIndex) != LUA_TTABLE) { LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex::SetVertex: non-table parameter %u supplied inside table of parameters, table expected.", (unsigned int)i ); return; } int pushes = 1; if(DataPieceElements == 2) { pushes += 2; lua_rawgeti(L, DataPieceIndex, 1); float x= FArg(-1); lua_rawgeti(L, DataPieceIndex, 2); float y= FArg(-1); p->SetVertexCoords(VertexIndex, x, y); } else if(DataPieceElements == 3) { pushes += 3; lua_rawgeti(L, DataPieceIndex, 1); float x= FArg(-1); lua_rawgeti(L, DataPieceIndex, 2); float y= FArg(-1); lua_rawgeti(L, DataPieceIndex, 3); float z= FArg(-1); p->SetVertexPos(VertexIndex, x, y, z); } else if(DataPieceElements == 4) { // RageColor pops the things it pushes onto the stack, so we don't need to. RageColor c; // Does not use FromStackCompat because we are not compatible with passing a color in non-table form. c.FromStack(L, DataPieceIndex); p->SetVertexColor(VertexIndex, c); } else { LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex::SetVertex: Parameter %u has %u elements supplied. 2, 3, or 4 expected.", (unsigned int)i, (unsigned int)DataPieceElements ); } // Avoid a stack underflow by only popping the amount we pushed. lua_pop(L, pushes); } return; } static int SetVertex(T* p, lua_State* L) { // Indices from Lua are one-indexed. -1 to adjust. int Index = IArg(1)-1; if( Index < 0 ) { LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex::SetVertex: index %d provided, cannot set Index < 1", Index+1 ); COMMON_RETURN_SELF; } else if( Index == (int) p->GetNumVertices() ) { p->AddVertices( 1 ); } else if( Index > (int) p->GetNumVertices() ) { LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex::SetVertex: Cannot set vertex %d if there is no vertex %d, only %u vertices.", Index+1 , Index, (unsigned int)p->GetNumVertices() ); COMMON_RETURN_SELF; } SetVertexFromStack(p, L, Index, lua_gettop(L)); COMMON_RETURN_SELF; } static int SetVertices(T* p, lua_State* L) { int First = 0; int StackIndex = lua_gettop(L); // Allow the user to just pass a table without specifying a starting point. if(lua_type(L, 1) == LUA_TNUMBER) { // Indices from Lua are one-indexed. -1 to adjust. First = IArg(1)-1; if( First < 0 ) { LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex::SetVertices: index %d provided, cannot set Index < 1", First+1 ); COMMON_RETURN_SELF; } } int Last = First + lua_objlen(L, StackIndex ); if( Last > (int) p->GetNumVertices()) { p->AddVertices( Last - p->GetNumVertices() ); } for(int n = First; n < Last; ++n) { lua_pushnumber(L, n-First+1); lua_gettable(L, StackIndex); SetVertexFromStack(p, L, n, lua_gettop(L)); lua_pop(L, 1); } COMMON_RETURN_SELF; } static int SetEffectMode( T* p, lua_State *L ) { EffectMode em = Enum::Check(L, 1); p->SetEffectMode( em ); COMMON_RETURN_SELF; } static int SetTextureMode( T* p, lua_State *L ) { TextureMode tm = Enum::Check(L, 1); p->SetTextureMode( tm ); COMMON_RETURN_SELF; } static int SetLineWidth( T* p, lua_State *L ) { float Width = FArg(1); if( Width < 0 ) { LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex::SetLineWidth: cannot set negative width." ); COMMON_RETURN_SELF; } p->SetLineWidth(Width); COMMON_RETURN_SELF; } static int SetDrawState( T* p, lua_State* L ) { DrawMode dm= p->GetDestDrawMode(); int first= p->GetDestFirstToDraw(); int num= p->GetDestNumToDraw(); int ArgsIndex= 1; if( !lua_istable(L, ArgsIndex) ) { LuaHelpers::ReportScriptErrorFmt( "ActorMultiVertex:SetDrawState: Table expected, something else recieved. Doing nothing."); COMMON_RETURN_SELF; } // Fetch the draw mode, if provided. lua_getfield(L, ArgsIndex, "Mode"); if( !lua_isnil(L, -1) ) { dm= Enum::Check(L, -1); } lua_pop(L, 1); // Fetch FirstToDraw, if provided. lua_getfield(L, ArgsIndex, "First"); if( !lua_isnil(L, -1) ) { // Indices from Lua are one-indexed. -1 to adjust. first= IArg(-1)-1; } lua_pop(L, 1); // Fetch NumToDraw, if provided. lua_getfield(L, ArgsIndex, "Num"); if( !lua_isnil(L, -1) ) { num= IArg(-1); } lua_pop(L, 1); p->SetDrawState(dm, first, num); COMMON_RETURN_SELF; } static int GetDestDrawMode( T* p, lua_State* L ) { Enum::Push(L, p->GetDestDrawMode()); return 1; } static int GetDestFirstToDraw( T* p, lua_State* L ) { // Indices in Lua are one-indexed. +1 to adjust. lua_pushnumber(L, p->GetDestFirstToDraw()+1); return 1; } static int GetDestNumToDraw( T* p, lua_State* L ) { lua_pushnumber(L, p->GetDestNumToDraw()); return 1; } static int GetCurrDrawMode( T* p, lua_State* L ) { Enum::Push(L, p->GetCurrDrawMode()); return 1; } static int GetCurrFirstToDraw( T* p, lua_State* L ) { // Indices in Lua are one-indexed. +1 to adjust. lua_pushnumber(L, p->GetCurrFirstToDraw()+1); return 1; } static int GetCurrNumToDraw( T* p, lua_State* L ) { lua_pushnumber(L, p->GetCurrNumToDraw()); return 1; } static int LoadTexture( T* p, lua_State *L ) { if( lua_isnil(L, 1) ) { p->UnloadTexture(); p->LoadFromTexture(TEXTUREMAN->GetDefaultTextureID()); } else { RageTextureID ID( SArg(1) ); TEXTUREMAN->DisableOddDimensionWarning(); p->LoadFromTexture( ID ); TEXTUREMAN->EnableOddDimensionWarning(); } COMMON_RETURN_SELF; } static int GetSpline(T* p, lua_State* L) { size_t i= static_cast(IArg(1)-1); if(i >= ActorMultiVertex::num_vert_splines) { luaL_error(L, "Spline index must be greater than 0 and less than or equal to %zu.", ActorMultiVertex::num_vert_splines); } p->GetSpline(i)->PushSelf(L); return 1; } static int SetVertsFromSplines(T* p, lua_State* L) { p->SetVertsFromSplines(); 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 == nullptr) { 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(index) >= p->GetNumStates()) { luaL_error(L, "Invalid state index %d.", index+1); } return static_cast(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 == nullptr) { 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 == nullptr) { luaL_error(L, "The texture must be set before adding states."); } std::vector 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+1); 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(index) >= p->GetNumQuadStates()) { luaL_error(L, "Invalid state index %d.", index+1); } return static_cast(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(2)-1); COMMON_RETURN_SELF; } static int ForceStateUpdate(T* p, lua_State *L) { p->UpdateAnimationState(true); COMMON_RETURN_SELF; } DEFINE_METHOD(GetDecodeMovie, _decode_movie); static int SetDecodeMovie(T* p, lua_State *L) { p->_decode_movie= BArg(1); COMMON_RETURN_SELF; } static int SetTexture( T* p, lua_State *L ) { RageTexture *Texture = Luna::check(L, 1); Texture = TEXTUREMAN->CopyTexture( Texture ); p->SetTexture( Texture ); COMMON_RETURN_SELF; } static int GetTexture(T* p, lua_State *L) { RageTexture *texture = p->GetTexture(); if(texture != nullptr) { texture->PushSelf(L); } else { lua_pushnil(L); } return 1; } LunaActorMultiVertex() { ADD_METHOD( SetVertex ); ADD_METHOD( SetVertices ); ADD_METHOD( SetEffectMode ); ADD_METHOD( SetTextureMode ); ADD_METHOD( SetLineWidth ); ADD_METHOD( SetDrawState ); ADD_METHOD( SetNumVertices ); ADD_METHOD( GetNumVertices ); ADD_METHOD( GetDestDrawMode ); ADD_METHOD( GetDestFirstToDraw ); ADD_METHOD( GetDestNumToDraw ); ADD_METHOD( GetCurrDrawMode ); ADD_METHOD( GetCurrFirstToDraw ); ADD_METHOD( GetCurrNumToDraw ); 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); ADD_METHOD(ForceStateUpdate); ADD_METHOD(GetDecodeMovie); ADD_METHOD(SetDecodeMovie); // Copy from RageTexture ADD_METHOD( SetTexture ); ADD_METHOD( GetTexture ); // Load from file path ADD_METHOD( LoadTexture ); } }; LUA_REGISTER_DERIVED_CLASS( ActorMultiVertex, Actor ) /* * (c) 2014 Matthew Gardner and Eric Reese * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons to * whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */