From b1b55a42f6fd8c2b4b6f515c19ac06c09013d7e6 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sun, 28 Apr 2013 17:23:38 -0400 Subject: [PATCH] Wait...const did work? Have to fix the other. --- src/Sprite.cpp | 2242 ++++++++++++++++++++++++------------------------ 1 file changed, 1120 insertions(+), 1122 deletions(-) diff --git a/src/Sprite.cpp b/src/Sprite.cpp index 21db165257..6fc061aff1 100644 --- a/src/Sprite.cpp +++ b/src/Sprite.cpp @@ -1,1122 +1,1120 @@ -#include "global.h" -#include - -#include "Sprite.h" -#include "RageTextureManager.h" -#include "XmlFile.h" -#include "RageLog.h" -#include "RageDisplay.h" -#include "RageTexture.h" -#include "RageUtil.h" -#include "ActorUtil.h" -#include "arch/Dialog/Dialog.h" -#include "Foreach.h" -#include "LuaBinding.h" -#include "LuaManager.h" - -REGISTER_ACTOR_CLASS( Sprite ); - - -Sprite::Sprite() -{ - m_pTexture = NULL; - m_iCurState = 0; - m_fSecsIntoState = 0.0f; - m_bUsingCustomTexCoords = false; - m_bSkipNextUpdate = true; - m_EffectMode = EffectMode_Normal; - - m_fRememberedClipWidth = -1; - m_fRememberedClipHeight = -1; - - m_fTexCoordVelocityX = 0; - m_fTexCoordVelocityY = 0; -} - - -Sprite::~Sprite() -{ - UnloadTexture(); -} - -Sprite::Sprite( const Sprite &cpy ): - Actor( cpy ) -{ -#define CPY(a) a = cpy.a - CPY( m_States ); - CPY( m_iCurState ); - CPY( m_fSecsIntoState ); - CPY( m_bUsingCustomTexCoords ); - CPY( m_bSkipNextUpdate ); - CPY( m_EffectMode ); - memcpy( m_CustomTexCoords, cpy.m_CustomTexCoords, sizeof(m_CustomTexCoords) ); - CPY( m_fRememberedClipWidth ); - CPY( m_fRememberedClipHeight ); - CPY( m_fTexCoordVelocityX ); - CPY( m_fTexCoordVelocityY ); -#undef CPY - - if( cpy.m_pTexture != NULL ) - m_pTexture = TEXTUREMAN->CopyTexture( cpy.m_pTexture ); - else - m_pTexture = NULL; -} - -void Sprite::InitState() -{ - Actor::InitState(); - m_iCurState = 0; - m_fSecsIntoState = 0.0f; - m_bSkipNextUpdate = true; -} - -void Sprite::SetAllStateDelays(float fDelay) -{ - for(unsigned int i=0;iGetAttrValue( "Texture", sPath ); - if( !sPath.empty() && !TEXTUREMAN->IsTextureRegistered( RageTextureID(sPath) ) ) - ActorUtil::GetAttrPath( pNode, "Texture", sPath ); - - if( !sPath.empty() ) - { - // Load the texture - LoadFromTexture( sPath ); - - LoadStatesFromTexture(); - - // Read in frames and delays from the sprite file, - // overwriting the states that LoadFromTexture created. - // If the .sprite file doesn't define any states, leave - // frames and delays created during LoadFromTexture(). - vector aStates; - - const XNode *pFrames = pNode->GetChild( "Frames" ); - if( pFrames != NULL ) - { - /* All attributes are optional. If Frame is omitted, use the previous state's - * frame (or 0 if the first). - * Frames = { - * { Delay=1.0f; Frame=0; { x=0, y=0 }, { x=1, y=1 } }; - * } - */ - int iFrameIndex = 0; - for( int i=0; true; i++ ) - { - const XNode *pFrame = pFrames->GetChild( ssprintf("%i", i+1) ); // +1 for Lua's arrays - if( pFrame == NULL ) - break; - - State newState; - if( !pFrame->GetAttrValue("Delay", newState.fDelay) ) - newState.fDelay = 0.1f; - - pFrame->GetAttrValue( "Frame", iFrameIndex ); - if( iFrameIndex >= m_pTexture->GetNumFrames() ) - RageException::Throw( "%s: State #%i is frame %d, but the texture \"%s\" only has %d frames", - ActorUtil::GetWhere(pNode).c_str(), i, iFrameIndex, sPath.c_str(), m_pTexture->GetNumFrames() ); - newState.rect = *m_pTexture->GetTextureCoordRect( iFrameIndex ); - - const XNode *pPoints[2] = { pFrame->GetChild( "1" ), pFrame->GetChild( "2" ) }; - if( pPoints[0] != NULL && pPoints[1] != NULL ) - { - RectF r = newState.rect; - - float fX = 1.0f, fY = 1.0f; - pPoints[0]->GetAttrValue( "x", fX ); - pPoints[0]->GetAttrValue( "y", fY ); - newState.rect.left = SCALE( fX, 0.0f, 1.0f, r.left, r.right ); - newState.rect.top = SCALE( fY, 0.0f, 1.0f, r.top, r.bottom ); - - pPoints[1]->GetAttrValue( "x", fX ); - pPoints[1]->GetAttrValue( "y", fY ); - newState.rect.right = SCALE( fX, 0.0f, 1.0f, r.left, r.right ); - newState.rect.bottom = SCALE( fY, 0.0f, 1.0f, r.top, r.bottom ); - } - - aStates.push_back( newState ); - } - } - else for( int i=0; true; i++ ) - { - // deprecated - RString sFrameKey = ssprintf( "Frame%04d", i ); - RString sDelayKey = ssprintf( "Delay%04d", i ); - State newState; - - int iFrameIndex; - if( !pNode->GetAttrValue(sFrameKey, iFrameIndex) ) - break; - if( iFrameIndex >= m_pTexture->GetNumFrames() ) - RageException::Throw( "%s: %s is %d, but the texture \"%s\" only has %d frames", - ActorUtil::GetWhere(pNode).c_str(), sFrameKey.c_str(), iFrameIndex, sPath.c_str(), m_pTexture->GetNumFrames() ); - - newState.rect = *m_pTexture->GetTextureCoordRect( iFrameIndex ); - - if( !pNode->GetAttrValue(sDelayKey, newState.fDelay) ) - break; - - aStates.push_back( newState ); - } - - if( !aStates.empty() ) - { - m_States = aStates; - Sprite::m_size.x = aStates[0].rect.GetWidth() / m_pTexture->GetSourceToTexCoordsRatioX(); - Sprite::m_size.y = aStates[0].rect.GetHeight() / m_pTexture->GetSourceToTexCoordsRatioY(); - } - } - - Actor::LoadFromNode( pNode ); -} - -void Sprite::UnloadTexture() -{ - if( m_pTexture != NULL ) // If there was a previous bitmap... - { - TEXTUREMAN->UnloadTexture( m_pTexture ); // Unload it. - m_pTexture = NULL; - - /* Make sure we're reset to frame 0, so if we're reused, we aren't left - * on a frame number that may be greater than the number of frames in - * the newly loaded image. */ - SetState( 0 ); - } -} - -void Sprite::EnableAnimation( bool bEnable ) -{ - bool bWasEnabled = m_bIsAnimating; - Actor::EnableAnimation( bEnable ); - - if( bEnable && !bWasEnabled ) - { - /* When we start animating a movie, we need to discard the first update; send - * 0 instead of the passed time. This is for two reasons: - * - * First, and most fundamentally, the time we'll receive on the next update - * represents time that passed *before* the movie was started. For example, - * 1: 20ms passes; 2: EnableAnimation(true); 3: Update(.020). We don't want - * to send that 20ms to the texture; on the first update, the movie's time - * should be 0. - * - * Second, we don't receive Update() calls if we're in a BGAnimation that - * doesn't have focus. It looks like 1: EnableAnimation(false); 2: 30 seconds - * pass; 3: EnableAnimation(true); 4: Update(30). We must be sure not to send - * that long 30-second update to the movie. - * - * (detail: the timestamps here are actually coming from GetEffectDeltaTime()) - */ - m_bSkipNextUpdate = true; - } -} - -void Sprite::SetTexture( RageTexture *pTexture ) -{ - ASSERT( pTexture != NULL ); - - if( m_pTexture != pTexture ) - { - UnloadTexture(); - m_pTexture = pTexture; - } - - ASSERT( m_pTexture->GetTextureWidth() >= 0 ); - ASSERT( m_pTexture->GetTextureHeight() >= 0 ); - - // the size of the sprite is the size of the image before it was scaled - Sprite::m_size.x = (float)m_pTexture->GetSourceFrameWidth(); - Sprite::m_size.y = (float)m_pTexture->GetSourceFrameHeight(); - - // apply clipping (if any) - if( m_fRememberedClipWidth != -1 && m_fRememberedClipHeight != -1 ) - ScaleToClipped( m_fRememberedClipWidth, m_fRememberedClipHeight ); - - // Load default states if we haven't before. - if( m_States.empty() ) - LoadStatesFromTexture(); -} - -void Sprite::LoadFromTexture( RageTextureID ID ) -{ - // LOG->Trace( "Sprite::LoadFromTexture( %s )", ID.filename.c_str() ); - - RageTexture *pTexture = NULL; - if( m_pTexture && m_pTexture->GetID() == ID ) - pTexture = m_pTexture; - else - pTexture = TEXTUREMAN->LoadTexture( ID ); - - SetTexture( pTexture ); -} - -void Sprite::LoadStatesFromTexture() -{ - // Assume the frames of this animation play in sequential order with 0.1 second delay. - m_States.clear(); - - if( m_pTexture == NULL ) - { - State newState; - newState.fDelay = 0.1f; - newState.rect = RectF( 0, 0, 1, 1 ); - m_States.push_back( newState ); - return; - } - - for( int i=0; iGetNumFrames(); ++i ) - { - State newState; - newState.fDelay = 0.1f; - newState.rect = *m_pTexture->GetTextureCoordRect( i ); - m_States.push_back( newState ); - } -} - -void Sprite::UpdateAnimationState() -{ - // Don't bother with state switching logic if there's only one state. - // We already know what's going to show. - if( m_States.size() > 1 ) - { - while( m_fSecsIntoState+0.0001f > 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 - m_iCurState = (m_iCurState+1) % m_States.size(); - } - } -} - -/* We treat frame animation and movie animation slightly differently. - * - * Sprite animation is always tied directly to the effect timer. If you pause - * animation, wait a while and restart it, sprite animations will snap back to - * the effect timer. This allows sprites to animate to the beat in BGAnimations, - * where they might be disabled for a while. - * - * Movies don't do this; if you pause a movie, wait a while and restart it, - * it'll pick up where it left off. We may have a lot of movies loaded, so - * it's too expensive to decode movies that aren't being displayed. Movies - * also don't loop when the effect timer loops. - * - * (I'd like to handle sprite and movie animation as consistently as possible; - * the above is just documentation of current practice.) -glenn */ -// todo: see if "current" practice is just that. -aj -void Sprite::Update( float fDelta ) -{ - Actor::Update( fDelta ); // do tweening - - const bool bSkipThisMovieUpdate = m_bSkipNextUpdate; - m_bSkipNextUpdate = false; - - if( !m_bIsAnimating ) - return; - - if( !m_pTexture ) // no texture, nothing to animate - return; - - float fTimePassed = GetEffectDeltaTime(); - m_fSecsIntoState += fTimePassed; - - if( m_fSecsIntoState < 0 ) - wrap( m_fSecsIntoState, GetAnimationLengthSeconds() ); - - UpdateAnimationState(); - - // If the texture is a movie, decode frames. - if( !bSkipThisMovieUpdate ) - m_pTexture->DecodeSeconds( max(0, fTimePassed) ); - - // update scrolling - if( m_fTexCoordVelocityX != 0 || m_fTexCoordVelocityY != 0 ) - { - float fTexCoords[8]; - Sprite::GetActiveTextureCoords( fTexCoords ); - - // top left, bottom left, bottom right, top right - fTexCoords[0] += fDelta*m_fTexCoordVelocityX; - fTexCoords[1] += fDelta*m_fTexCoordVelocityY; - fTexCoords[2] += fDelta*m_fTexCoordVelocityX; - fTexCoords[3] += fDelta*m_fTexCoordVelocityY; - fTexCoords[4] += fDelta*m_fTexCoordVelocityX; - fTexCoords[5] += fDelta*m_fTexCoordVelocityY; - fTexCoords[6] += fDelta*m_fTexCoordVelocityX; - fTexCoords[7] += fDelta*m_fTexCoordVelocityY; - - /* When wrapping, avoid gradual loss of precision and sending - * unreasonably large texture coordinates to the renderer by pushing - * texture coordinates back to 0. As long as we adjust all four - * coordinates by the same amount, this won't be visible. */ - if( m_bTextureWrapping ) - { - const float fXAdjust = floorf( fTexCoords[0] ); - const float fYAdjust = floorf( fTexCoords[1] ); - fTexCoords[0] -= fXAdjust; - fTexCoords[2] -= fXAdjust; - fTexCoords[4] -= fXAdjust; - fTexCoords[6] -= fXAdjust; - fTexCoords[1] -= fYAdjust; - fTexCoords[3] -= fYAdjust; - fTexCoords[5] -= fYAdjust; - fTexCoords[7] -= fYAdjust; - } - - Sprite::SetCustomTextureCoords( fTexCoords ); - } -} - -void TexCoordArrayFromRect( float fImageCoords[8], const RectF &rect ) -{ - fImageCoords[0] = rect.left; fImageCoords[1] = rect.top; // top left - fImageCoords[2] = rect.left; fImageCoords[3] = rect.bottom; // bottom left - fImageCoords[4] = rect.right; fImageCoords[5] = rect.bottom; // bottom right - fImageCoords[6] = rect.right; fImageCoords[7] = rect.top; // top right -} - -void Sprite::DrawTexture( const TweenState *state ) -{ - Actor::SetGlobalRenderStates(); // set Actor-specified render states - - RectF crop = state->crop; - // bail if cropped all the way - if( crop.left + crop.right >= 1 || - crop.top + crop.bottom >= 1 ) - return; - - // use m_temp_* variables to draw the object - RectF quadVerticies; - quadVerticies.left = -m_size.x/2.0f; - quadVerticies.right = +m_size.x/2.0f; - quadVerticies.top = -m_size.y/2.0f; - quadVerticies.bottom = +m_size.y/2.0f; - - /* Don't draw anything outside of the texture's image area. Texels outside - * of the image area aren't guaranteed to be initialized. */ - /* HACK: Clamp the crop values. It would be more accurate to clip the - * vertices so that the diffuse value is adjusted. */ - CLAMP( crop.left, 0, 1 ); - CLAMP( crop.right, 0, 1 ); - CLAMP( crop.top, 0, 1 ); - CLAMP( crop.bottom, 0, 1 ); - - RectF croppedQuadVerticies = quadVerticies; -#define IF_CROP_POS(side,opp_side) \ - if(state->crop.side!=0) \ - croppedQuadVerticies.side = \ - SCALE( crop.side, 0.f, 1.f, quadVerticies.side, quadVerticies.opp_side ) - IF_CROP_POS( left, right ); - IF_CROP_POS( top, bottom ); - IF_CROP_POS( right, left ); - IF_CROP_POS( bottom, top ); - - static RageSpriteVertex v[4]; - v[0].p = RageVector3( croppedQuadVerticies.left, croppedQuadVerticies.top, 0 ); // top left - v[1].p = RageVector3( croppedQuadVerticies.left, croppedQuadVerticies.bottom, 0 ); // bottom left - v[2].p = RageVector3( croppedQuadVerticies.right, croppedQuadVerticies.bottom, 0 ); // bottom right - v[3].p = RageVector3( croppedQuadVerticies.right, croppedQuadVerticies.top, 0 ); // top right - - DISPLAY->ClearAllTextures(); - DISPLAY->SetTexture( TextureUnit_1, m_pTexture? m_pTexture->GetTexHandle():0 ); - - // Must call this after setting the texture or else texture - // parameters have no effect. - Actor::SetTextureRenderStates(); // set Actor-specified render states - DISPLAY->SetEffectMode( m_EffectMode ); - - if( m_pTexture ) - { - float f[8]; - GetActiveTextureCoords( f ); - - if( state->crop.left || state->crop.right || state->crop.top || state->crop.bottom ) - { - RageVector2 texCoords[4] = { - RageVector2( f[0], f[1] ), // top left - RageVector2( f[2], f[3] ), // bottom left - RageVector2( f[4], f[5] ), // bottom right - RageVector2( f[6], f[7] ) // top right - }; - - for( int i = 0; i < 4; ++i ) - { - RageSpriteVertex *pVert = &v[i]; - - float fTopX = SCALE( pVert->p.x, quadVerticies.left, quadVerticies.right, texCoords[0].x, texCoords[3].x ); - float fBottomX = SCALE( pVert->p.x, quadVerticies.left, quadVerticies.right, texCoords[1].x, texCoords[2].x ); - pVert->t.x = SCALE( pVert->p.y, quadVerticies.top, quadVerticies.bottom, fTopX, fBottomX ); - - float fLeftY = SCALE( pVert->p.y, quadVerticies.top, quadVerticies.bottom, texCoords[0].y, texCoords[1].y ); - float fRightY = SCALE( pVert->p.y, quadVerticies.top, quadVerticies.bottom, texCoords[3].y, texCoords[2].y ); - pVert->t.y = SCALE( pVert->p.x, quadVerticies.left, quadVerticies.right, fLeftY, fRightY ); - } - } - else - { - v[0].t = RageVector2( f[0], f[1] ); // top left - v[1].t = RageVector2( f[2], f[3] ); // bottom left - v[2].t = RageVector2( f[4], f[5] ); // bottom right - v[3].t = RageVector2( f[6], f[7] ); // top right - } - } - else - { - // Just make sure we don't throw NaN/INF at the renderer: - for( unsigned i = 0; i < 4; ++i ) - v[i].t.x = v[i].t.y = 0; - } - - // Draw if we're not fully transparent - if( state->diffuse[0].a > 0 || - state->diffuse[1].a > 0 || - state->diffuse[2].a > 0 || - state->diffuse[3].a > 0 ) - { - DISPLAY->SetTextureMode( TextureUnit_1, TextureMode_Modulate ); - - // render the shadow - if( m_fShadowLengthX != 0 || m_fShadowLengthY != 0 ) - { - DISPLAY->PushMatrix(); - DISPLAY->TranslateWorld( m_fShadowLengthX, m_fShadowLengthY, 0 ); // shift by 5 units - RageColor c = m_ShadowColor; - c.a *= state->diffuse[0].a; - v[0].c = v[1].c = v[2].c = v[3].c = c; // semi-transparent black - DISPLAY->DrawQuad( v ); - DISPLAY->PopMatrix(); - } - - // render the diffuse pass - v[0].c = state->diffuse[0]; // top left - v[1].c = state->diffuse[2]; // bottom left - v[2].c = state->diffuse[3]; // bottom right - v[3].c = state->diffuse[1]; // top right - DISPLAY->DrawQuad( v ); - } - - // render the glow pass - if( state->glow.a > 0.0001f ) - { - DISPLAY->SetTextureMode( TextureUnit_1, TextureMode_Glow ); - v[0].c = v[1].c = v[2].c = v[3].c = state->glow; - DISPLAY->DrawQuad( v ); - } - DISPLAY->SetEffectMode( EffectMode_Normal ); -} - -bool Sprite::EarlyAbortDraw() const -{ - return m_pTexture == NULL; -} - -void Sprite::DrawPrimitives() -{ - if( m_pTempState->fade.top > 0 || - m_pTempState->fade.bottom > 0 || - m_pTempState->fade.left > 0 || - m_pTempState->fade.right > 0 ) - { - // We're fading the edges. - const RectF &FadeDist = m_pTempState->fade; - - // Actual size of the fade on each side: - RectF FadeSize = FadeDist; - - // If the cropped size is less than the fade distance in either dimension, clamp. - const float HorizRemaining = 1.0f - (m_pTempState->crop.left + m_pTempState->crop.right); - if( FadeDist.left+FadeDist.right > 0 && - HorizRemaining < FadeDist.left+FadeDist.right ) - { - const float LeftPercent = FadeDist.left/(FadeDist.left+FadeDist.right); - FadeSize.left = LeftPercent * HorizRemaining; - FadeSize.right = (1.0f-LeftPercent) * HorizRemaining; - } - - const float VertRemaining = 1.0f - (m_pTempState->crop.top + m_pTempState->crop.bottom); - if( FadeDist.top+FadeDist.bottom > 0 && - VertRemaining < FadeDist.top+FadeDist.bottom ) - { - const float TopPercent = FadeDist.top/(FadeDist.top+FadeDist.bottom); - FadeSize.top = TopPercent * VertRemaining; - FadeSize.bottom = (1.0f-TopPercent) * VertRemaining; - } - - // Alpha value of the un-faded side of each fade rect: - const float RightAlpha = SCALE( FadeSize.right, FadeDist.right, 0, 1, 0 ); - const float LeftAlpha = SCALE( FadeSize.left, FadeDist.left, 0, 1, 0 ); - const float TopAlpha = SCALE( FadeSize.top, FadeDist.top, 0, 1, 0 ); - const float BottomAlpha = SCALE( FadeSize.bottom, FadeDist.bottom, 0, 1, 0 ); - - // Draw the inside: - TweenState ts = *m_pTempState; - ts.crop.left += FadeDist.left; - ts.crop.right += FadeDist.right; - ts.crop.top += FadeDist.top; - ts.crop.bottom += FadeDist.bottom; - DrawTexture( &ts ); - - if( FadeSize.left > 0.001f ) - { - // Draw the left: - ts.crop = m_pTempState->crop; // restore - ts.crop.right = 1 - (ts.crop.left + FadeSize.left); - ts.crop.top += FadeDist.top; // lop off the corner if fading both x and y - ts.crop.bottom += FadeDist.bottom; - - ts.diffuse[0] = m_pTempState->diffuse[0]; // top left -> top left - ts.diffuse[2] = m_pTempState->diffuse[2]; // bottom left -> bottom left - ts.diffuse[3] = m_pTempState->diffuse[2]; // bottom left -> bottom right - ts.diffuse[1] = m_pTempState->diffuse[0]; // top left -> top right - ts.diffuse[0].a = 0; // top left - ts.diffuse[2].a = 0; // bottom left - ts.diffuse[3].a *= LeftAlpha; // bottom right - ts.diffuse[1].a *= LeftAlpha; // top right - DrawTexture( &ts ); - } - - if( FadeSize.right > 0.001f ) - { - // Draw the right: - ts.crop = m_pTempState->crop; // restore - ts.crop.left = 1 - (ts.crop.right + FadeSize.right); - ts.crop.top += FadeDist.top; - ts.crop.bottom += FadeDist.bottom; - - ts.diffuse[0] = m_pTempState->diffuse[1]; // top right -> top left - ts.diffuse[2] = m_pTempState->diffuse[3]; // bottom right -> bottom left - ts.diffuse[3] = m_pTempState->diffuse[3]; // bottom right -> bottom right - ts.diffuse[1] = m_pTempState->diffuse[1]; // top right -> top right - ts.diffuse[0].a *= RightAlpha; // top left - ts.diffuse[2].a *= RightAlpha; // bottom left - ts.diffuse[3].a = 0; // bottom right - ts.diffuse[1].a = 0; // top right - - DrawTexture( &ts ); - } - - if( FadeSize.top > 0.001f ) - { - // Draw the top: - ts.crop = m_pTempState->crop; // restore - ts.crop.bottom = 1 - (ts.crop.top + FadeSize.top); - ts.crop.left += FadeDist.left; - ts.crop.right += FadeDist.right; - - ts.diffuse[0] = m_pTempState->diffuse[0]; // top left -> top left - ts.diffuse[2] = m_pTempState->diffuse[0]; // top left -> bottom left - ts.diffuse[3] = m_pTempState->diffuse[1]; // top right -> bottom right - ts.diffuse[1] = m_pTempState->diffuse[1]; // top right -> top right - ts.diffuse[0].a = 0; // top left - ts.diffuse[2].a *= TopAlpha; // bottom left - ts.diffuse[3].a *= TopAlpha; // bottom right - ts.diffuse[1].a = 0; // top right - - DrawTexture( &ts ); - } - - if( FadeSize.bottom > 0.001f ) - { - // Draw the bottom: - ts.crop = m_pTempState->crop; // restore - ts.crop.top = 1 - (ts.crop.bottom + FadeSize.bottom); - ts.crop.left += FadeDist.left; - ts.crop.right += FadeDist.right; - - ts.diffuse[0] = m_pTempState->diffuse[2]; // bottom left -> top left - ts.diffuse[2] = m_pTempState->diffuse[2]; // bottom left -> bottom left - ts.diffuse[3] = m_pTempState->diffuse[3]; // bottom right -> bottom right - ts.diffuse[1] = m_pTempState->diffuse[3]; // bottom right -> top right - ts.diffuse[0].a *= BottomAlpha; // top left - ts.diffuse[2].a = 0; // bottom left - ts.diffuse[3].a = 0; // bottom right - ts.diffuse[1].a *= BottomAlpha; // top right - DrawTexture( &ts ); - } - } - else - { - DrawTexture( m_pTempState ); - } -} - - -int Sprite::GetNumStates() const -{ - return m_States.size(); -} - -void Sprite::SetState( int iNewState ) -{ - /* - * This assert will likely trigger if the "missing" theme element graphic - * is loaded in place of a multi-frame sprite. We want to know about these - * problems in debug builds, but they're not fatal. - */ - - // Never warn about setting state 0. - if( iNewState != 0 && (iNewState < 0 || iNewState >= (int)m_States.size()) ) - { - // Don't warn about number of states in "_blank" or "_missing". - if( !m_pTexture || (m_pTexture->GetID().filename.find("_blank") == string::npos && - m_pTexture->GetID().filename.find("_missing") == string::npos) ) - { - RString sError; - if( m_pTexture ) - sError = ssprintf("A Sprite '%s' (\"%s\") tried to set state to frame %d, but it has only %u frames.", - /* - * Using the state directly tends to give you an error message like "tried to set frame 6 of 6" - * which is very confusing if you don't know that one is 0-indexed and the other is 1-indexed. - * - Colby - */ - m_pTexture->GetID().filename.c_str(), this->m_sName.c_str(), iNewState+1, unsigned(m_States.size())); - else - sError = ssprintf("A Sprite (\"%s\") tried to set state index %d, but no texture is loaded.", - this->m_sName.c_str(), iNewState ); - Dialog::OK( sError, "SPRITE_INVALID_FRAME" ); - } - } - - CLAMP(iNewState, 0, (int)m_States.size()-1); - m_iCurState = iNewState; - m_fSecsIntoState = 0.0f; -} - -float Sprite::GetAnimationLengthSeconds() const -{ - float fTotal = 0; - FOREACH_CONST( State, m_States, s ) - fTotal += s->fDelay; - return fTotal; -} - -void Sprite::SetSecondsIntoAnimation( float fSeconds ) -{ - SetState( 0 ); // rewind to the first state - if( m_pTexture ) - m_pTexture->SetPosition( fSeconds ); - m_fSecsIntoState = fSeconds; - UpdateAnimationState(); -} - -RString Sprite::GetTexturePath() const -{ - if( m_pTexture==NULL ) - return RString(); - - return m_pTexture->GetID().filename; -} - -void Sprite::SetCustomTextureRect( const RectF &new_texcoord_frect ) -{ - m_bUsingCustomTexCoords = true; - m_bTextureWrapping = true; - TexCoordArrayFromRect( m_CustomTexCoords, new_texcoord_frect ); -} - -void Sprite::SetCustomTextureCoords( float fTexCoords[8] ) // order: top left, bottom left, bottom right, top right -{ - m_bUsingCustomTexCoords = true; - m_bTextureWrapping = true; - for( int i=0; i<8; i++ ) - m_CustomTexCoords[i] = fTexCoords[i]; -} - -void Sprite::SetCustomImageRect( RectF rectImageCoords ) -{ - // Convert to a rectangle in texture coordinate space. - rectImageCoords.left *= m_pTexture->GetImageWidth() / (float)m_pTexture->GetTextureWidth(); - rectImageCoords.right *= m_pTexture->GetImageWidth() / (float)m_pTexture->GetTextureWidth(); - rectImageCoords.top *= m_pTexture->GetImageHeight() / (float)m_pTexture->GetTextureHeight(); - rectImageCoords.bottom *= m_pTexture->GetImageHeight() / (float)m_pTexture->GetTextureHeight(); - - SetCustomTextureRect( rectImageCoords ); -} - -void Sprite::SetCustomImageCoords( float fImageCoords[8] ) // order: top left, bottom left, bottom right, top right -{ - // convert image coords to texture coords in place - for( int i=0; i<8; i+=2 ) - { - fImageCoords[i+0] *= m_pTexture->GetImageWidth() / (float)m_pTexture->GetTextureWidth(); - fImageCoords[i+1] *= m_pTexture->GetImageHeight() / (float)m_pTexture->GetTextureHeight(); - } - - SetCustomTextureCoords( fImageCoords ); -} - -const RectF *Sprite::GetCurrentTextureCoordRect() const -{ - return GetTextureCoordRectForState( m_iCurState ); -} - -const RectF *Sprite::GetTextureCoordRectForState( int iState ) const -{ - ASSERT_M( iState < (int) m_States.size(), ssprintf("%d, %d", int(iState), int(m_States.size())) ); - - return &m_States[iState].rect; -} - -/* If we're using custom coordinates, return them; otherwise return the - * coordinates for the current state. */ -void Sprite::GetActiveTextureCoords( float fTexCoordsOut[8] ) const -{ - if(m_bUsingCustomTexCoords) - { - // GetCustomTextureCoords - for( int i=0; i<8; i++ ) - fTexCoordsOut[i] = m_CustomTexCoords[i]; - } - else - { - // GetCurrentTextureCoords - const RectF *pTexCoordRect = GetCurrentTextureCoordRect(); - TexCoordArrayFromRect( fTexCoordsOut, *pTexCoordRect ); - } -} - - -void Sprite::StopUsingCustomCoords() -{ - m_bUsingCustomTexCoords = false; -} - -void Sprite::SetTexCoordVelocity(float fVelX, float fVelY) -{ - m_fTexCoordVelocityX = fVelX; - m_fTexCoordVelocityY = fVelY; -} - -void Sprite::ScaleToClipped( float fWidth, float fHeight ) -{ - m_fRememberedClipWidth = fWidth; - m_fRememberedClipHeight = fHeight; - - if( !m_pTexture ) - return; - - float fScaleFudgePercent = 0.15f; // scale up to this amount in one dimension to avoid clipping. - - // save the original X and Y. We're going to restore them later. - float fOriginalX = GetX(); - float fOriginalY = GetY(); - - if( fWidth != -1 && fHeight != -1 ) - { - // this is probably a background graphic or something not intended to be a CroppedSprite - Sprite::StopUsingCustomCoords(); - - // first find the correct zoom - Sprite::ScaleToCover( RectF(0, 0, fWidth, fHeight) ); - // find which dimension is larger - bool bXDimNeedsToBeCropped = GetZoomedWidth() > fWidth+0.01; - - if( bXDimNeedsToBeCropped ) // crop X - { - float fPercentageToCutOff = (this->GetZoomedWidth() - fWidth) / this->GetZoomedWidth(); - fPercentageToCutOff = max( fPercentageToCutOff-fScaleFudgePercent, 0 ); - float fPercentageToCutOffEachSide = fPercentageToCutOff / 2; - - // generate a rectangle with new texture coordinates - RectF fCustomImageRect( - fPercentageToCutOffEachSide, - 0, - 1 - fPercentageToCutOffEachSide, - 1 ); - SetCustomImageRect( fCustomImageRect ); - } - else // crop Y - { - float fPercentageToCutOff = (this->GetZoomedHeight() - fHeight) / this->GetZoomedHeight(); - fPercentageToCutOff = max( fPercentageToCutOff-fScaleFudgePercent, 0 ); - float fPercentageToCutOffEachSide = fPercentageToCutOff / 2; - - // generate a rectangle with new texture coordinates - RectF fCustomImageRect( - 0, - fPercentageToCutOffEachSide, - 1, - 1 - fPercentageToCutOffEachSide ); - SetCustomImageRect( fCustomImageRect ); - } - m_size = RageVector2( fWidth, fHeight ); - SetZoom( 1 ); - } - - // restore original XY - SetXY( fOriginalX, fOriginalY ); -} - - -// magic hurr -// This code should either be removed or refactored in the future -aj -void Sprite::CropTo( float fWidth, float fHeight ) -{ - m_fRememberedClipWidth = fWidth; - m_fRememberedClipHeight = fHeight; - - if( !m_pTexture ) - return; - - // save the original X&Y. We're going to restore them later. - float fOriginalX = GetX(); - float fOriginalY = GetY(); - - if( fWidth != -1 && fHeight != -1 ) - { - // this is probably a background graphic or something not intended to be a CroppedSprite - Sprite::StopUsingCustomCoords(); - - // first find the correct zoom - Sprite::ScaleToCover( RectF(0, 0, fWidth, fHeight) ); - // find which dimension is larger - bool bXDimNeedsToBeCropped = GetZoomedWidth() > fWidth+0.01; - - if( bXDimNeedsToBeCropped ) // crop X - { - float fPercentageToCutOff = (this->GetZoomedWidth() - fWidth) / this->GetZoomedWidth(); - float fPercentageToCutOffEachSide = fPercentageToCutOff / 2; - - // generate a rectangle with new texture coordinates - RectF fCustomImageRect( - fPercentageToCutOffEachSide, - 0, - 1 - fPercentageToCutOffEachSide, - 1 ); - SetCustomImageRect( fCustomImageRect ); - } - else // crop Y - { - float fPercentageToCutOff = (this->GetZoomedHeight() - fHeight) / this->GetZoomedHeight(); - float fPercentageToCutOffEachSide = fPercentageToCutOff / 2; - - // generate a rectangle with new texture coordinates - RectF fCustomImageRect( - 0, - fPercentageToCutOffEachSide, - 1, - 1 - fPercentageToCutOffEachSide ); - SetCustomImageRect( fCustomImageRect ); - } - m_size = RageVector2( fWidth, fHeight ); - SetZoom( 1 ); - } - - // restore original XY - SetXY( fOriginalX, fOriginalY ); -} -// end magic - -void Sprite::StretchTexCoords( float fX, float fY ) -{ - float fTexCoords[8]; - GetActiveTextureCoords( fTexCoords ); - - for( int j=0; j<8; j+=2 ) - { - fTexCoords[j ] += fX; - fTexCoords[j+1] += fY; - } - - SetCustomTextureCoords( fTexCoords ); -} - -void Sprite::AddImageCoords( float fX, float fY ) -{ - float fTexCoords[8]; - GetActiveTextureCoords( fTexCoords ); - - for( int j=0; j<8; j+=2 ) - { - fTexCoords[j ] += fX / (float)m_pTexture->GetTextureWidth(); - fTexCoords[j+1] += fY / (float)m_pTexture->GetTextureHeight(); - } - - SetCustomTextureCoords( fTexCoords ); -} - - -// lua start -#include "LuaBinding.h" - -/** @brief Allow Lua to have access to the Sprite. */ -class LunaSprite: public Luna -{ -public: - static int Load( T* p, lua_State *L ) - { - if( lua_isnil(L, 1) ) - { - p->UnloadTexture(); - } - else - { - RageTextureID ID( SArg(1) ); - p->Load( ID ); - } - return 0; - } - static int LoadBackground( T* p, lua_State *L ) - { - RageTextureID ID( SArg(1) ); - TEXTUREMAN->DisableOddDimensionWarning(); - p->Load( Sprite::SongBGTexture(ID) ); - TEXTUREMAN->EnableOddDimensionWarning(); - return 1; - } - static int LoadBanner( T* p, lua_State *L ) - { - RageTextureID ID( SArg(1) ); - TEXTUREMAN->DisableOddDimensionWarning(); - p->Load( Sprite::SongBannerTexture(ID) ); - TEXTUREMAN->EnableOddDimensionWarning(); - return 1; - } - - /* Commands that go in the tweening queue: - * Commands that take effect immediately (ignoring the tweening queue): */ - static int customtexturerect( T* p, lua_State *L ) { p->SetCustomTextureRect( RectF(FArg(1),FArg(2),FArg(3),FArg(4)) ); return 0; } - static int SetCustomImageRect( T* p, lua_State *L ) { p->SetCustomImageRect( RectF(FArg(1),FArg(2),FArg(3),FArg(4)) ); return 0; } - static int texcoordvelocity( T* p, lua_State *L ) { p->SetTexCoordVelocity( FArg(1),FArg(2) ); return 0; } - static int scaletoclipped( T* p, lua_State *L ) { p->ScaleToClipped( FArg(1),FArg(2) ); return 0; } - static int CropTo( T* p, lua_State *L ) { p->CropTo( FArg(1),FArg(2) ); return 0; } - static int stretchtexcoords( T* p, lua_State *L ) { p->StretchTexCoords( FArg(1),FArg(2) ); return 0; } - static int addimagecoords( T* p, lua_State *L ) { p->AddImageCoords( FArg(1),FArg(2) ); return 0; } - static int setstate( T* p, lua_State *L ) { p->SetState( IArg(1) ); return 0; } - static int GetState( T* p, lua_State *L ) { lua_pushnumber( L, p->GetState() ); return 1; } - static int GetAnimationLengthSeconds( T* p, lua_State *L ) { lua_pushnumber( L, p->GetAnimationLengthSeconds() ); return 1; } - static int SetSecondsIntoAnimation( T* p, lua_State *L ) { p->SetSecondsIntoAnimation(FArg(0)); return 0; } - static int SetTexture( T* p, lua_State *L ) - { - RageTexture *pTexture = Luna::check(L, 1); - pTexture = TEXTUREMAN->CopyTexture( pTexture ); - p->SetTexture( pTexture ); - return 0; - } - static int GetTexture( T* p, lua_State *L ) - { - RageTexture *pTexture = p->GetTexture(); - if( pTexture != NULL ) - pTexture->PushSelf(L); - else - lua_pushnil( L ); - return 1; - } - static int SetEffectMode( T* p, lua_State *L ) - { - EffectMode em = Enum::Check(L, 1); - p->SetEffectMode( em ); - return 0; - } - static int GetNumStates( T* p, lua_State *L ) { lua_pushnumber( L, p->GetNumStates() ); return 1; } - static int SetAllStateDelays( T* p, lua_State *L ) { p->SetAllStateDelays(FArg(1)); return 0; } - - LunaSprite() - { - ADD_METHOD( Load ); - ADD_METHOD( LoadBanner ); - ADD_METHOD( LoadBackground ); - ADD_METHOD( customtexturerect ); - ADD_METHOD( SetCustomImageRect ); - ADD_METHOD( texcoordvelocity ); - ADD_METHOD( scaletoclipped ); - ADD_METHOD( CropTo ); - ADD_METHOD( stretchtexcoords ); - ADD_METHOD( addimagecoords ); - ADD_METHOD( setstate ); - ADD_METHOD( GetState ); - ADD_METHOD( GetAnimationLengthSeconds ); - ADD_METHOD( SetSecondsIntoAnimation ); - ADD_METHOD( SetTexture ); - ADD_METHOD( GetTexture ); - ADD_METHOD( SetEffectMode ); - ADD_METHOD( GetNumStates ); - ADD_METHOD( SetAllStateDelays ); - } -}; - -LUA_REGISTER_DERIVED_CLASS( Sprite, Actor ) -// lua end - -/* - * (c) 2001-2004 Chris Danford - * 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. - */ +#include "global.h" +#include + +#include "Sprite.h" +#include "RageTextureManager.h" +#include "XmlFile.h" +#include "RageLog.h" +#include "RageDisplay.h" +#include "RageTexture.h" +#include "RageUtil.h" +#include "ActorUtil.h" +#include "arch/Dialog/Dialog.h" +#include "Foreach.h" +#include "LuaBinding.h" +#include "LuaManager.h" +#include + +REGISTER_ACTOR_CLASS( Sprite ); + + +Sprite::Sprite() +{ + m_pTexture = NULL; + m_iCurState = 0; + m_fSecsIntoState = 0.0f; + m_bUsingCustomTexCoords = false; + m_bSkipNextUpdate = true; + m_EffectMode = EffectMode_Normal; + + m_fRememberedClipWidth = -1; + m_fRememberedClipHeight = -1; + + m_fTexCoordVelocityX = 0; + m_fTexCoordVelocityY = 0; +} + + +Sprite::~Sprite() +{ + UnloadTexture(); +} + +Sprite::Sprite( const Sprite &cpy ): + Actor( cpy ) +{ +#define CPY(a) a = cpy.a + CPY( m_States ); + CPY( m_iCurState ); + CPY( m_fSecsIntoState ); + CPY( m_bUsingCustomTexCoords ); + CPY( m_bSkipNextUpdate ); + CPY( m_EffectMode ); + memcpy( m_CustomTexCoords, cpy.m_CustomTexCoords, sizeof(m_CustomTexCoords) ); + CPY( m_fRememberedClipWidth ); + CPY( m_fRememberedClipHeight ); + CPY( m_fTexCoordVelocityX ); + CPY( m_fTexCoordVelocityY ); +#undef CPY + + if( cpy.m_pTexture != NULL ) + m_pTexture = TEXTUREMAN->CopyTexture( cpy.m_pTexture ); + else + m_pTexture = NULL; +} + +void Sprite::InitState() +{ + Actor::InitState(); + m_iCurState = 0; + m_fSecsIntoState = 0.0f; + m_bSkipNextUpdate = true; +} + +void Sprite::SetAllStateDelays(float fDelay) +{ + for(unsigned int i=0;iGetAttrValue( "Texture", sPath ); + if( !sPath.empty() && !TEXTUREMAN->IsTextureRegistered( RageTextureID(sPath) ) ) + ActorUtil::GetAttrPath( pNode, "Texture", sPath ); + + if( !sPath.empty() ) + { + // Load the texture + LoadFromTexture( sPath ); + + LoadStatesFromTexture(); + + // Read in frames and delays from the sprite file, + // overwriting the states that LoadFromTexture created. + // If the .sprite file doesn't define any states, leave + // frames and delays created during LoadFromTexture(). + vector aStates; + + const XNode *pFrames = pNode->GetChild( "Frames" ); + if( pFrames != NULL ) + { + /* All attributes are optional. If Frame is omitted, use the previous state's + * frame (or 0 if the first). + * Frames = { + * { Delay=1.0f; Frame=0; { x=0, y=0 }, { x=1, y=1 } }; + * } + */ + int iFrameIndex = 0; + for( int i=0; true; i++ ) + { + const XNode *pFrame = pFrames->GetChild( ssprintf("%i", i+1) ); // +1 for Lua's arrays + if( pFrame == NULL ) + break; + + State newState; + if( !pFrame->GetAttrValue("Delay", newState.fDelay) ) + newState.fDelay = 0.1f; + + pFrame->GetAttrValue( "Frame", iFrameIndex ); + if( iFrameIndex >= m_pTexture->GetNumFrames() ) + RageException::Throw( "%s: State #%i is frame %d, but the texture \"%s\" only has %d frames", + ActorUtil::GetWhere(pNode).c_str(), i, iFrameIndex, sPath.c_str(), m_pTexture->GetNumFrames() ); + newState.rect = *m_pTexture->GetTextureCoordRect( iFrameIndex ); + + const XNode *pPoints[2] = { pFrame->GetChild( "1" ), pFrame->GetChild( "2" ) }; + if( pPoints[0] != NULL && pPoints[1] != NULL ) + { + RectF r = newState.rect; + + float fX = 1.0f, fY = 1.0f; + pPoints[0]->GetAttrValue( "x", fX ); + pPoints[0]->GetAttrValue( "y", fY ); + newState.rect.left = SCALE( fX, 0.0f, 1.0f, r.left, r.right ); + newState.rect.top = SCALE( fY, 0.0f, 1.0f, r.top, r.bottom ); + + pPoints[1]->GetAttrValue( "x", fX ); + pPoints[1]->GetAttrValue( "y", fY ); + newState.rect.right = SCALE( fX, 0.0f, 1.0f, r.left, r.right ); + newState.rect.bottom = SCALE( fY, 0.0f, 1.0f, r.top, r.bottom ); + } + + aStates.push_back( newState ); + } + } + else for( int i=0; true; i++ ) + { + // deprecated + RString sFrameKey = ssprintf( "Frame%04d", i ); + RString sDelayKey = ssprintf( "Delay%04d", i ); + State newState; + + int iFrameIndex; + if( !pNode->GetAttrValue(sFrameKey, iFrameIndex) ) + break; + if( iFrameIndex >= m_pTexture->GetNumFrames() ) + RageException::Throw( "%s: %s is %d, but the texture \"%s\" only has %d frames", + ActorUtil::GetWhere(pNode).c_str(), sFrameKey.c_str(), iFrameIndex, sPath.c_str(), m_pTexture->GetNumFrames() ); + + newState.rect = *m_pTexture->GetTextureCoordRect( iFrameIndex ); + + if( !pNode->GetAttrValue(sDelayKey, newState.fDelay) ) + break; + + aStates.push_back( newState ); + } + + if( !aStates.empty() ) + { + m_States = aStates; + Sprite::m_size.x = aStates[0].rect.GetWidth() / m_pTexture->GetSourceToTexCoordsRatioX(); + Sprite::m_size.y = aStates[0].rect.GetHeight() / m_pTexture->GetSourceToTexCoordsRatioY(); + } + } + + Actor::LoadFromNode( pNode ); +} + +void Sprite::UnloadTexture() +{ + if( m_pTexture != NULL ) // If there was a previous bitmap... + { + TEXTUREMAN->UnloadTexture( m_pTexture ); // Unload it. + m_pTexture = NULL; + + /* Make sure we're reset to frame 0, so if we're reused, we aren't left + * on a frame number that may be greater than the number of frames in + * the newly loaded image. */ + SetState( 0 ); + } +} + +void Sprite::EnableAnimation( bool bEnable ) +{ + bool bWasEnabled = m_bIsAnimating; + Actor::EnableAnimation( bEnable ); + + if( bEnable && !bWasEnabled ) + { + /* When we start animating a movie, we need to discard the first update; send + * 0 instead of the passed time. This is for two reasons: + * + * First, and most fundamentally, the time we'll receive on the next update + * represents time that passed *before* the movie was started. For example, + * 1: 20ms passes; 2: EnableAnimation(true); 3: Update(.020). We don't want + * to send that 20ms to the texture; on the first update, the movie's time + * should be 0. + * + * Second, we don't receive Update() calls if we're in a BGAnimation that + * doesn't have focus. It looks like 1: EnableAnimation(false); 2: 30 seconds + * pass; 3: EnableAnimation(true); 4: Update(30). We must be sure not to send + * that long 30-second update to the movie. + * + * (detail: the timestamps here are actually coming from GetEffectDeltaTime()) + */ + m_bSkipNextUpdate = true; + } +} + +void Sprite::SetTexture( RageTexture *pTexture ) +{ + ASSERT( pTexture != NULL ); + + if( m_pTexture != pTexture ) + { + UnloadTexture(); + m_pTexture = pTexture; + } + + ASSERT( m_pTexture->GetTextureWidth() >= 0 ); + ASSERT( m_pTexture->GetTextureHeight() >= 0 ); + + // the size of the sprite is the size of the image before it was scaled + Sprite::m_size.x = (float)m_pTexture->GetSourceFrameWidth(); + Sprite::m_size.y = (float)m_pTexture->GetSourceFrameHeight(); + + // apply clipping (if any) + if( m_fRememberedClipWidth != -1 && m_fRememberedClipHeight != -1 ) + ScaleToClipped( m_fRememberedClipWidth, m_fRememberedClipHeight ); + + // Load default states if we haven't before. + if( m_States.empty() ) + LoadStatesFromTexture(); +} + +void Sprite::LoadFromTexture( RageTextureID ID ) +{ + // LOG->Trace( "Sprite::LoadFromTexture( %s )", ID.filename.c_str() ); + + RageTexture *pTexture = NULL; + if( m_pTexture && m_pTexture->GetID() == ID ) + pTexture = m_pTexture; + else + pTexture = TEXTUREMAN->LoadTexture( ID ); + + SetTexture( pTexture ); +} + +void Sprite::LoadStatesFromTexture() +{ + // Assume the frames of this animation play in sequential order with 0.1 second delay. + m_States.clear(); + + if( m_pTexture == NULL ) + { + State newState; + newState.fDelay = 0.1f; + newState.rect = RectF( 0, 0, 1, 1 ); + m_States.push_back( newState ); + return; + } + + for( int i=0; iGetNumFrames(); ++i ) + { + State newState; + newState.fDelay = 0.1f; + newState.rect = *m_pTexture->GetTextureCoordRect( i ); + m_States.push_back( newState ); + } +} + +void Sprite::UpdateAnimationState() +{ + // Don't bother with state switching logic if there's only one state. + // We already know what's going to show. + if( m_States.size() > 1 ) + { + while( m_fSecsIntoState+0.0001f > 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 + m_iCurState = (m_iCurState+1) % m_States.size(); + } + } +} + +/* We treat frame animation and movie animation slightly differently. + * + * Sprite animation is always tied directly to the effect timer. If you pause + * animation, wait a while and restart it, sprite animations will snap back to + * the effect timer. This allows sprites to animate to the beat in BGAnimations, + * where they might be disabled for a while. + * + * Movies don't do this; if you pause a movie, wait a while and restart it, + * it'll pick up where it left off. We may have a lot of movies loaded, so + * it's too expensive to decode movies that aren't being displayed. Movies + * also don't loop when the effect timer loops. + * + * (I'd like to handle sprite and movie animation as consistently as possible; + * the above is just documentation of current practice.) -glenn */ +// todo: see if "current" practice is just that. -aj +void Sprite::Update( float fDelta ) +{ + Actor::Update( fDelta ); // do tweening + + const bool bSkipThisMovieUpdate = m_bSkipNextUpdate; + m_bSkipNextUpdate = false; + + if( !m_bIsAnimating ) + return; + + if( !m_pTexture ) // no texture, nothing to animate + return; + + float fTimePassed = GetEffectDeltaTime(); + m_fSecsIntoState += fTimePassed; + + if( m_fSecsIntoState < 0 ) + wrap( m_fSecsIntoState, GetAnimationLengthSeconds() ); + + UpdateAnimationState(); + + // If the texture is a movie, decode frames. + if( !bSkipThisMovieUpdate ) + m_pTexture->DecodeSeconds( max(0, fTimePassed) ); + + // update scrolling + if( m_fTexCoordVelocityX != 0 || m_fTexCoordVelocityY != 0 ) + { + float fTexCoords[8]; + Sprite::GetActiveTextureCoords( fTexCoords ); + + // top left, bottom left, bottom right, top right + fTexCoords[0] += fDelta*m_fTexCoordVelocityX; + fTexCoords[1] += fDelta*m_fTexCoordVelocityY; + fTexCoords[2] += fDelta*m_fTexCoordVelocityX; + fTexCoords[3] += fDelta*m_fTexCoordVelocityY; + fTexCoords[4] += fDelta*m_fTexCoordVelocityX; + fTexCoords[5] += fDelta*m_fTexCoordVelocityY; + fTexCoords[6] += fDelta*m_fTexCoordVelocityX; + fTexCoords[7] += fDelta*m_fTexCoordVelocityY; + + /* When wrapping, avoid gradual loss of precision and sending + * unreasonably large texture coordinates to the renderer by pushing + * texture coordinates back to 0. As long as we adjust all four + * coordinates by the same amount, this won't be visible. */ + if( m_bTextureWrapping ) + { + const float fXAdjust = floorf( fTexCoords[0] ); + const float fYAdjust = floorf( fTexCoords[1] ); + fTexCoords[0] -= fXAdjust; + fTexCoords[2] -= fXAdjust; + fTexCoords[4] -= fXAdjust; + fTexCoords[6] -= fXAdjust; + fTexCoords[1] -= fYAdjust; + fTexCoords[3] -= fYAdjust; + fTexCoords[5] -= fYAdjust; + fTexCoords[7] -= fYAdjust; + } + + Sprite::SetCustomTextureCoords( fTexCoords ); + } +} + +void TexCoordArrayFromRect( float fImageCoords[8], const RectF &rect ) +{ + fImageCoords[0] = rect.left; fImageCoords[1] = rect.top; // top left + fImageCoords[2] = rect.left; fImageCoords[3] = rect.bottom; // bottom left + fImageCoords[4] = rect.right; fImageCoords[5] = rect.bottom; // bottom right + fImageCoords[6] = rect.right; fImageCoords[7] = rect.top; // top right +} + +void Sprite::DrawTexture( const TweenState *state ) +{ + Actor::SetGlobalRenderStates(); // set Actor-specified render states + + RectF crop = state->crop; + // bail if cropped all the way + if( crop.left + crop.right >= 1 || + crop.top + crop.bottom >= 1 ) + return; + + // use m_temp_* variables to draw the object + RectF quadVerticies; + quadVerticies.left = -m_size.x/2.0f; + quadVerticies.right = +m_size.x/2.0f; + quadVerticies.top = -m_size.y/2.0f; + quadVerticies.bottom = +m_size.y/2.0f; + + /* Don't draw anything outside of the texture's image area. Texels outside + * of the image area aren't guaranteed to be initialized. */ + /* HACK: Clamp the crop values. It would be more accurate to clip the + * vertices so that the diffuse value is adjusted. */ + CLAMP( crop.left, 0, 1 ); + CLAMP( crop.right, 0, 1 ); + CLAMP( crop.top, 0, 1 ); + CLAMP( crop.bottom, 0, 1 ); + + RectF croppedQuadVerticies = quadVerticies; +#define IF_CROP_POS(side,opp_side) \ + if(state->crop.side!=0) \ + croppedQuadVerticies.side = \ + SCALE( crop.side, 0.f, 1.f, quadVerticies.side, quadVerticies.opp_side ) + IF_CROP_POS( left, right ); + IF_CROP_POS( top, bottom ); + IF_CROP_POS( right, left ); + IF_CROP_POS( bottom, top ); + + static RageSpriteVertex v[4]; + v[0].p = RageVector3( croppedQuadVerticies.left, croppedQuadVerticies.top, 0 ); // top left + v[1].p = RageVector3( croppedQuadVerticies.left, croppedQuadVerticies.bottom, 0 ); // bottom left + v[2].p = RageVector3( croppedQuadVerticies.right, croppedQuadVerticies.bottom, 0 ); // bottom right + v[3].p = RageVector3( croppedQuadVerticies.right, croppedQuadVerticies.top, 0 ); // top right + + DISPLAY->ClearAllTextures(); + DISPLAY->SetTexture( TextureUnit_1, m_pTexture? m_pTexture->GetTexHandle():0 ); + + // Must call this after setting the texture or else texture + // parameters have no effect. + Actor::SetTextureRenderStates(); // set Actor-specified render states + DISPLAY->SetEffectMode( m_EffectMode ); + + if( m_pTexture ) + { + float f[8]; + GetActiveTextureCoords( f ); + + if( state->crop.left || state->crop.right || state->crop.top || state->crop.bottom ) + { + RageVector2 texCoords[4] = { + RageVector2( f[0], f[1] ), // top left + RageVector2( f[2], f[3] ), // bottom left + RageVector2( f[4], f[5] ), // bottom right + RageVector2( f[6], f[7] ) // top right + }; + + for( int i = 0; i < 4; ++i ) + { + RageSpriteVertex *pVert = &v[i]; + + float fTopX = SCALE( pVert->p.x, quadVerticies.left, quadVerticies.right, texCoords[0].x, texCoords[3].x ); + float fBottomX = SCALE( pVert->p.x, quadVerticies.left, quadVerticies.right, texCoords[1].x, texCoords[2].x ); + pVert->t.x = SCALE( pVert->p.y, quadVerticies.top, quadVerticies.bottom, fTopX, fBottomX ); + + float fLeftY = SCALE( pVert->p.y, quadVerticies.top, quadVerticies.bottom, texCoords[0].y, texCoords[1].y ); + float fRightY = SCALE( pVert->p.y, quadVerticies.top, quadVerticies.bottom, texCoords[3].y, texCoords[2].y ); + pVert->t.y = SCALE( pVert->p.x, quadVerticies.left, quadVerticies.right, fLeftY, fRightY ); + } + } + else + { + v[0].t = RageVector2( f[0], f[1] ); // top left + v[1].t = RageVector2( f[2], f[3] ); // bottom left + v[2].t = RageVector2( f[4], f[5] ); // bottom right + v[3].t = RageVector2( f[6], f[7] ); // top right + } + } + else + { + // Just make sure we don't throw NaN/INF at the renderer: + for( unsigned i = 0; i < 4; ++i ) + v[i].t.x = v[i].t.y = 0; + } + + // Draw if we're not fully transparent + if( state->diffuse[0].a > 0 || + state->diffuse[1].a > 0 || + state->diffuse[2].a > 0 || + state->diffuse[3].a > 0 ) + { + DISPLAY->SetTextureMode( TextureUnit_1, TextureMode_Modulate ); + + // render the shadow + if( m_fShadowLengthX != 0 || m_fShadowLengthY != 0 ) + { + DISPLAY->PushMatrix(); + DISPLAY->TranslateWorld( m_fShadowLengthX, m_fShadowLengthY, 0 ); // shift by 5 units + RageColor c = m_ShadowColor; + c.a *= state->diffuse[0].a; + v[0].c = v[1].c = v[2].c = v[3].c = c; // semi-transparent black + DISPLAY->DrawQuad( v ); + DISPLAY->PopMatrix(); + } + + // render the diffuse pass + v[0].c = state->diffuse[0]; // top left + v[1].c = state->diffuse[2]; // bottom left + v[2].c = state->diffuse[3]; // bottom right + v[3].c = state->diffuse[1]; // top right + DISPLAY->DrawQuad( v ); + } + + // render the glow pass + if( state->glow.a > 0.0001f ) + { + DISPLAY->SetTextureMode( TextureUnit_1, TextureMode_Glow ); + v[0].c = v[1].c = v[2].c = v[3].c = state->glow; + DISPLAY->DrawQuad( v ); + } + DISPLAY->SetEffectMode( EffectMode_Normal ); +} + +bool Sprite::EarlyAbortDraw() const +{ + return m_pTexture == NULL; +} + +void Sprite::DrawPrimitives() +{ + if( m_pTempState->fade.top > 0 || + m_pTempState->fade.bottom > 0 || + m_pTempState->fade.left > 0 || + m_pTempState->fade.right > 0 ) + { + // We're fading the edges. + const RectF &FadeDist = m_pTempState->fade; + + // Actual size of the fade on each side: + RectF FadeSize = FadeDist; + + // If the cropped size is less than the fade distance in either dimension, clamp. + const float HorizRemaining = 1.0f - (m_pTempState->crop.left + m_pTempState->crop.right); + if( FadeDist.left+FadeDist.right > 0 && + HorizRemaining < FadeDist.left+FadeDist.right ) + { + const float LeftPercent = FadeDist.left/(FadeDist.left+FadeDist.right); + FadeSize.left = LeftPercent * HorizRemaining; + FadeSize.right = (1.0f-LeftPercent) * HorizRemaining; + } + + const float VertRemaining = 1.0f - (m_pTempState->crop.top + m_pTempState->crop.bottom); + if( FadeDist.top+FadeDist.bottom > 0 && + VertRemaining < FadeDist.top+FadeDist.bottom ) + { + const float TopPercent = FadeDist.top/(FadeDist.top+FadeDist.bottom); + FadeSize.top = TopPercent * VertRemaining; + FadeSize.bottom = (1.0f-TopPercent) * VertRemaining; + } + + // Alpha value of the un-faded side of each fade rect: + const float RightAlpha = SCALE( FadeSize.right, FadeDist.right, 0, 1, 0 ); + const float LeftAlpha = SCALE( FadeSize.left, FadeDist.left, 0, 1, 0 ); + const float TopAlpha = SCALE( FadeSize.top, FadeDist.top, 0, 1, 0 ); + const float BottomAlpha = SCALE( FadeSize.bottom, FadeDist.bottom, 0, 1, 0 ); + + // Draw the inside: + TweenState ts = *m_pTempState; + ts.crop.left += FadeDist.left; + ts.crop.right += FadeDist.right; + ts.crop.top += FadeDist.top; + ts.crop.bottom += FadeDist.bottom; + DrawTexture( &ts ); + + if( FadeSize.left > 0.001f ) + { + // Draw the left: + ts.crop = m_pTempState->crop; // restore + ts.crop.right = 1 - (ts.crop.left + FadeSize.left); + ts.crop.top += FadeDist.top; // lop off the corner if fading both x and y + ts.crop.bottom += FadeDist.bottom; + + ts.diffuse[0] = m_pTempState->diffuse[0]; // top left -> top left + ts.diffuse[2] = m_pTempState->diffuse[2]; // bottom left -> bottom left + ts.diffuse[3] = m_pTempState->diffuse[2]; // bottom left -> bottom right + ts.diffuse[1] = m_pTempState->diffuse[0]; // top left -> top right + ts.diffuse[0].a = 0; // top left + ts.diffuse[2].a = 0; // bottom left + ts.diffuse[3].a *= LeftAlpha; // bottom right + ts.diffuse[1].a *= LeftAlpha; // top right + DrawTexture( &ts ); + } + + if( FadeSize.right > 0.001f ) + { + // Draw the right: + ts.crop = m_pTempState->crop; // restore + ts.crop.left = 1 - (ts.crop.right + FadeSize.right); + ts.crop.top += FadeDist.top; + ts.crop.bottom += FadeDist.bottom; + + ts.diffuse[0] = m_pTempState->diffuse[1]; // top right -> top left + ts.diffuse[2] = m_pTempState->diffuse[3]; // bottom right -> bottom left + ts.diffuse[3] = m_pTempState->diffuse[3]; // bottom right -> bottom right + ts.diffuse[1] = m_pTempState->diffuse[1]; // top right -> top right + ts.diffuse[0].a *= RightAlpha; // top left + ts.diffuse[2].a *= RightAlpha; // bottom left + ts.diffuse[3].a = 0; // bottom right + ts.diffuse[1].a = 0; // top right + + DrawTexture( &ts ); + } + + if( FadeSize.top > 0.001f ) + { + // Draw the top: + ts.crop = m_pTempState->crop; // restore + ts.crop.bottom = 1 - (ts.crop.top + FadeSize.top); + ts.crop.left += FadeDist.left; + ts.crop.right += FadeDist.right; + + ts.diffuse[0] = m_pTempState->diffuse[0]; // top left -> top left + ts.diffuse[2] = m_pTempState->diffuse[0]; // top left -> bottom left + ts.diffuse[3] = m_pTempState->diffuse[1]; // top right -> bottom right + ts.diffuse[1] = m_pTempState->diffuse[1]; // top right -> top right + ts.diffuse[0].a = 0; // top left + ts.diffuse[2].a *= TopAlpha; // bottom left + ts.diffuse[3].a *= TopAlpha; // bottom right + ts.diffuse[1].a = 0; // top right + + DrawTexture( &ts ); + } + + if( FadeSize.bottom > 0.001f ) + { + // Draw the bottom: + ts.crop = m_pTempState->crop; // restore + ts.crop.top = 1 - (ts.crop.bottom + FadeSize.bottom); + ts.crop.left += FadeDist.left; + ts.crop.right += FadeDist.right; + + ts.diffuse[0] = m_pTempState->diffuse[2]; // bottom left -> top left + ts.diffuse[2] = m_pTempState->diffuse[2]; // bottom left -> bottom left + ts.diffuse[3] = m_pTempState->diffuse[3]; // bottom right -> bottom right + ts.diffuse[1] = m_pTempState->diffuse[3]; // bottom right -> top right + ts.diffuse[0].a *= BottomAlpha; // top left + ts.diffuse[2].a = 0; // bottom left + ts.diffuse[3].a = 0; // bottom right + ts.diffuse[1].a *= BottomAlpha; // top right + DrawTexture( &ts ); + } + } + else + { + DrawTexture( m_pTempState ); + } +} + + +int Sprite::GetNumStates() const +{ + return m_States.size(); +} + +void Sprite::SetState( int iNewState ) +{ + /* + * This assert will likely trigger if the "missing" theme element graphic + * is loaded in place of a multi-frame sprite. We want to know about these + * problems in debug builds, but they're not fatal. + */ + + // Never warn about setting state 0. + if( iNewState != 0 && (iNewState < 0 || iNewState >= (int)m_States.size()) ) + { + // Don't warn about number of states in "_blank" or "_missing". + if( !m_pTexture || (m_pTexture->GetID().filename.find("_blank") == string::npos && + m_pTexture->GetID().filename.find("_missing") == string::npos) ) + { + RString sError; + if( m_pTexture ) + sError = ssprintf("A Sprite '%s' (\"%s\") tried to set state to frame %d, but it has only %u frames.", + /* + * Using the state directly tends to give you an error message like "tried to set frame 6 of 6" + * which is very confusing if you don't know that one is 0-indexed and the other is 1-indexed. + * - Colby + */ + m_pTexture->GetID().filename.c_str(), this->m_sName.c_str(), iNewState+1, unsigned(m_States.size())); + else + sError = ssprintf("A Sprite (\"%s\") tried to set state index %d, but no texture is loaded.", + this->m_sName.c_str(), iNewState ); + Dialog::OK( sError, "SPRITE_INVALID_FRAME" ); + } + } + + CLAMP(iNewState, 0, (int)m_States.size()-1); + m_iCurState = iNewState; + m_fSecsIntoState = 0.0f; +} + +float Sprite::GetAnimationLengthSeconds() const +{ + return std::accumulate(m_States.begin(), m_States.end(), 0.f, [](float total, State const &s) { return total += s.fDelay; }); +} + +void Sprite::SetSecondsIntoAnimation( float fSeconds ) +{ + SetState( 0 ); // rewind to the first state + if( m_pTexture ) + m_pTexture->SetPosition( fSeconds ); + m_fSecsIntoState = fSeconds; + UpdateAnimationState(); +} + +RString Sprite::GetTexturePath() const +{ + if( m_pTexture==NULL ) + return RString(); + + return m_pTexture->GetID().filename; +} + +void Sprite::SetCustomTextureRect( const RectF &new_texcoord_frect ) +{ + m_bUsingCustomTexCoords = true; + m_bTextureWrapping = true; + TexCoordArrayFromRect( m_CustomTexCoords, new_texcoord_frect ); +} + +void Sprite::SetCustomTextureCoords( float fTexCoords[8] ) // order: top left, bottom left, bottom right, top right +{ + m_bUsingCustomTexCoords = true; + m_bTextureWrapping = true; + for( int i=0; i<8; i++ ) + m_CustomTexCoords[i] = fTexCoords[i]; +} + +void Sprite::SetCustomImageRect( RectF rectImageCoords ) +{ + // Convert to a rectangle in texture coordinate space. + rectImageCoords.left *= m_pTexture->GetImageWidth() / (float)m_pTexture->GetTextureWidth(); + rectImageCoords.right *= m_pTexture->GetImageWidth() / (float)m_pTexture->GetTextureWidth(); + rectImageCoords.top *= m_pTexture->GetImageHeight() / (float)m_pTexture->GetTextureHeight(); + rectImageCoords.bottom *= m_pTexture->GetImageHeight() / (float)m_pTexture->GetTextureHeight(); + + SetCustomTextureRect( rectImageCoords ); +} + +void Sprite::SetCustomImageCoords( float fImageCoords[8] ) // order: top left, bottom left, bottom right, top right +{ + // convert image coords to texture coords in place + for( int i=0; i<8; i+=2 ) + { + fImageCoords[i+0] *= m_pTexture->GetImageWidth() / (float)m_pTexture->GetTextureWidth(); + fImageCoords[i+1] *= m_pTexture->GetImageHeight() / (float)m_pTexture->GetTextureHeight(); + } + + SetCustomTextureCoords( fImageCoords ); +} + +const RectF *Sprite::GetCurrentTextureCoordRect() const +{ + return GetTextureCoordRectForState( m_iCurState ); +} + +const RectF *Sprite::GetTextureCoordRectForState( int iState ) const +{ + ASSERT_M( iState < (int) m_States.size(), ssprintf("%d, %d", int(iState), int(m_States.size())) ); + + return &m_States[iState].rect; +} + +/* If we're using custom coordinates, return them; otherwise return the + * coordinates for the current state. */ +void Sprite::GetActiveTextureCoords( float fTexCoordsOut[8] ) const +{ + if(m_bUsingCustomTexCoords) + { + // GetCustomTextureCoords + for( int i=0; i<8; i++ ) + fTexCoordsOut[i] = m_CustomTexCoords[i]; + } + else + { + // GetCurrentTextureCoords + const RectF *pTexCoordRect = GetCurrentTextureCoordRect(); + TexCoordArrayFromRect( fTexCoordsOut, *pTexCoordRect ); + } +} + + +void Sprite::StopUsingCustomCoords() +{ + m_bUsingCustomTexCoords = false; +} + +void Sprite::SetTexCoordVelocity(float fVelX, float fVelY) +{ + m_fTexCoordVelocityX = fVelX; + m_fTexCoordVelocityY = fVelY; +} + +void Sprite::ScaleToClipped( float fWidth, float fHeight ) +{ + m_fRememberedClipWidth = fWidth; + m_fRememberedClipHeight = fHeight; + + if( !m_pTexture ) + return; + + float fScaleFudgePercent = 0.15f; // scale up to this amount in one dimension to avoid clipping. + + // save the original X and Y. We're going to restore them later. + float fOriginalX = GetX(); + float fOriginalY = GetY(); + + if( fWidth != -1 && fHeight != -1 ) + { + // this is probably a background graphic or something not intended to be a CroppedSprite + Sprite::StopUsingCustomCoords(); + + // first find the correct zoom + Sprite::ScaleToCover( RectF(0, 0, fWidth, fHeight) ); + // find which dimension is larger + bool bXDimNeedsToBeCropped = GetZoomedWidth() > fWidth+0.01; + + if( bXDimNeedsToBeCropped ) // crop X + { + float fPercentageToCutOff = (this->GetZoomedWidth() - fWidth) / this->GetZoomedWidth(); + fPercentageToCutOff = max( fPercentageToCutOff-fScaleFudgePercent, 0 ); + float fPercentageToCutOffEachSide = fPercentageToCutOff / 2; + + // generate a rectangle with new texture coordinates + RectF fCustomImageRect( + fPercentageToCutOffEachSide, + 0, + 1 - fPercentageToCutOffEachSide, + 1 ); + SetCustomImageRect( fCustomImageRect ); + } + else // crop Y + { + float fPercentageToCutOff = (this->GetZoomedHeight() - fHeight) / this->GetZoomedHeight(); + fPercentageToCutOff = max( fPercentageToCutOff-fScaleFudgePercent, 0 ); + float fPercentageToCutOffEachSide = fPercentageToCutOff / 2; + + // generate a rectangle with new texture coordinates + RectF fCustomImageRect( + 0, + fPercentageToCutOffEachSide, + 1, + 1 - fPercentageToCutOffEachSide ); + SetCustomImageRect( fCustomImageRect ); + } + m_size = RageVector2( fWidth, fHeight ); + SetZoom( 1 ); + } + + // restore original XY + SetXY( fOriginalX, fOriginalY ); +} + + +// magic hurr +// This code should either be removed or refactored in the future -aj +void Sprite::CropTo( float fWidth, float fHeight ) +{ + m_fRememberedClipWidth = fWidth; + m_fRememberedClipHeight = fHeight; + + if( !m_pTexture ) + return; + + // save the original X&Y. We're going to restore them later. + float fOriginalX = GetX(); + float fOriginalY = GetY(); + + if( fWidth != -1 && fHeight != -1 ) + { + // this is probably a background graphic or something not intended to be a CroppedSprite + Sprite::StopUsingCustomCoords(); + + // first find the correct zoom + Sprite::ScaleToCover( RectF(0, 0, fWidth, fHeight) ); + // find which dimension is larger + bool bXDimNeedsToBeCropped = GetZoomedWidth() > fWidth+0.01; + + if( bXDimNeedsToBeCropped ) // crop X + { + float fPercentageToCutOff = (this->GetZoomedWidth() - fWidth) / this->GetZoomedWidth(); + float fPercentageToCutOffEachSide = fPercentageToCutOff / 2; + + // generate a rectangle with new texture coordinates + RectF fCustomImageRect( + fPercentageToCutOffEachSide, + 0, + 1 - fPercentageToCutOffEachSide, + 1 ); + SetCustomImageRect( fCustomImageRect ); + } + else // crop Y + { + float fPercentageToCutOff = (this->GetZoomedHeight() - fHeight) / this->GetZoomedHeight(); + float fPercentageToCutOffEachSide = fPercentageToCutOff / 2; + + // generate a rectangle with new texture coordinates + RectF fCustomImageRect( + 0, + fPercentageToCutOffEachSide, + 1, + 1 - fPercentageToCutOffEachSide ); + SetCustomImageRect( fCustomImageRect ); + } + m_size = RageVector2( fWidth, fHeight ); + SetZoom( 1 ); + } + + // restore original XY + SetXY( fOriginalX, fOriginalY ); +} +// end magic + +void Sprite::StretchTexCoords( float fX, float fY ) +{ + float fTexCoords[8]; + GetActiveTextureCoords( fTexCoords ); + + for( int j=0; j<8; j+=2 ) + { + fTexCoords[j ] += fX; + fTexCoords[j+1] += fY; + } + + SetCustomTextureCoords( fTexCoords ); +} + +void Sprite::AddImageCoords( float fX, float fY ) +{ + float fTexCoords[8]; + GetActiveTextureCoords( fTexCoords ); + + for( int j=0; j<8; j+=2 ) + { + fTexCoords[j ] += fX / (float)m_pTexture->GetTextureWidth(); + fTexCoords[j+1] += fY / (float)m_pTexture->GetTextureHeight(); + } + + SetCustomTextureCoords( fTexCoords ); +} + + +// lua start +#include "LuaBinding.h" + +/** @brief Allow Lua to have access to the Sprite. */ +class LunaSprite: public Luna +{ +public: + static int Load( T* p, lua_State *L ) + { + if( lua_isnil(L, 1) ) + { + p->UnloadTexture(); + } + else + { + RageTextureID ID( SArg(1) ); + p->Load( ID ); + } + return 0; + } + static int LoadBackground( T* p, lua_State *L ) + { + RageTextureID ID( SArg(1) ); + TEXTUREMAN->DisableOddDimensionWarning(); + p->Load( Sprite::SongBGTexture(ID) ); + TEXTUREMAN->EnableOddDimensionWarning(); + return 1; + } + static int LoadBanner( T* p, lua_State *L ) + { + RageTextureID ID( SArg(1) ); + TEXTUREMAN->DisableOddDimensionWarning(); + p->Load( Sprite::SongBannerTexture(ID) ); + TEXTUREMAN->EnableOddDimensionWarning(); + return 1; + } + + /* Commands that go in the tweening queue: + * Commands that take effect immediately (ignoring the tweening queue): */ + static int customtexturerect( T* p, lua_State *L ) { p->SetCustomTextureRect( RectF(FArg(1),FArg(2),FArg(3),FArg(4)) ); return 0; } + static int SetCustomImageRect( T* p, lua_State *L ) { p->SetCustomImageRect( RectF(FArg(1),FArg(2),FArg(3),FArg(4)) ); return 0; } + static int texcoordvelocity( T* p, lua_State *L ) { p->SetTexCoordVelocity( FArg(1),FArg(2) ); return 0; } + static int scaletoclipped( T* p, lua_State *L ) { p->ScaleToClipped( FArg(1),FArg(2) ); return 0; } + static int CropTo( T* p, lua_State *L ) { p->CropTo( FArg(1),FArg(2) ); return 0; } + static int stretchtexcoords( T* p, lua_State *L ) { p->StretchTexCoords( FArg(1),FArg(2) ); return 0; } + static int addimagecoords( T* p, lua_State *L ) { p->AddImageCoords( FArg(1),FArg(2) ); return 0; } + static int setstate( T* p, lua_State *L ) { p->SetState( IArg(1) ); return 0; } + static int GetState( T* p, lua_State *L ) { lua_pushnumber( L, p->GetState() ); return 1; } + static int GetAnimationLengthSeconds( T* p, lua_State *L ) { lua_pushnumber( L, p->GetAnimationLengthSeconds() ); return 1; } + static int SetSecondsIntoAnimation( T* p, lua_State *L ) { p->SetSecondsIntoAnimation(FArg(0)); return 0; } + static int SetTexture( T* p, lua_State *L ) + { + RageTexture *pTexture = Luna::check(L, 1); + pTexture = TEXTUREMAN->CopyTexture( pTexture ); + p->SetTexture( pTexture ); + return 0; + } + static int GetTexture( T* p, lua_State *L ) + { + RageTexture *pTexture = p->GetTexture(); + if( pTexture != NULL ) + pTexture->PushSelf(L); + else + lua_pushnil( L ); + return 1; + } + static int SetEffectMode( T* p, lua_State *L ) + { + EffectMode em = Enum::Check(L, 1); + p->SetEffectMode( em ); + return 0; + } + static int GetNumStates( T* p, lua_State *L ) { lua_pushnumber( L, p->GetNumStates() ); return 1; } + static int SetAllStateDelays( T* p, lua_State *L ) { p->SetAllStateDelays(FArg(1)); return 0; } + + LunaSprite() + { + ADD_METHOD( Load ); + ADD_METHOD( LoadBanner ); + ADD_METHOD( LoadBackground ); + ADD_METHOD( customtexturerect ); + ADD_METHOD( SetCustomImageRect ); + ADD_METHOD( texcoordvelocity ); + ADD_METHOD( scaletoclipped ); + ADD_METHOD( CropTo ); + ADD_METHOD( stretchtexcoords ); + ADD_METHOD( addimagecoords ); + ADD_METHOD( setstate ); + ADD_METHOD( GetState ); + ADD_METHOD( GetAnimationLengthSeconds ); + ADD_METHOD( SetSecondsIntoAnimation ); + ADD_METHOD( SetTexture ); + ADD_METHOD( GetTexture ); + ADD_METHOD( SetEffectMode ); + ADD_METHOD( GetNumStates ); + ADD_METHOD( SetAllStateDelays ); + } +}; + +LUA_REGISTER_DERIVED_CLASS( Sprite, Actor ) +// lua end + +/* + * (c) 2001-2004 Chris Danford + * 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. + */