static void TexCoordsFromArray(RageSpriteVertex *v, const float *f) { 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 }
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 ); }
void BitmapText::BuildChars() { /* If we don't have a font yet, we'll do this when it loads. */ if( m_pFont == NULL ) return; /* calculate line lengths and widths */ m_size.x = 0; m_iLineWidths.clear(); for( unsigned l=0; l<m_wTextLines.size(); l++ ) // for each line { m_iLineWidths.push_back(m_pFont->GetLineWidthInSourcePixels( m_wTextLines[l] )); m_size.x = max( m_size.x, m_iLineWidths.back() ); } m_aVertices.clear(); m_pTextures.clear(); if( m_wTextLines.empty() ) return; m_size.y = float(m_pFont->GetHeight() * m_wTextLines.size()); int MinSpacing = 0; /* The height (from the origin to the baseline): */ int Padding = max(m_pFont->GetLineSpacing(), MinSpacing) - m_pFont->GetHeight(); /* There's padding between every line: */ m_size.y += Padding * (m_wTextLines.size()-1); int iY; // the top position of the first row of characters switch( m_VertAlign ) { case align_top: iY = 0; break; case align_middle: iY = -(int)roundf(m_size.y/2.0f); break; case align_bottom: iY = -(int)m_size.y; break; default: ASSERT( false ); return; } for( unsigned i=0; i<m_wTextLines.size(); i++ ) // foreach line { iY += m_pFont->GetHeight(); const wstring &szLine = m_wTextLines[i]; const int iLineWidth = m_iLineWidths[i]; int iX; switch( m_HorizAlign ) { case align_left: iX = 0; break; case align_center: iX = -(int)roundf(iLineWidth/2.0f); break; case align_right: iX = -iLineWidth; break; default: ASSERT( false ); return; } for( unsigned j=0; j<szLine.size(); j++ ) // for each character in the line { RageSpriteVertex v[4]; const glyph &g = m_pFont->GetGlyph(szLine[j]); /* set vertex positions */ v[0].p = RageVector3( iX+g.m_fHshift, iY+g.m_pPage->m_fVshift, 0 ); // top left v[1].p = RageVector3( iX+g.m_fHshift, iY+g.m_pPage->m_fVshift+g.m_fHeight, 0 ); // bottom left v[2].p = RageVector3( iX+g.m_fHshift+g.m_fWidth, iY+g.m_pPage->m_fVshift+g.m_fHeight, 0 ); // bottom right v[3].p = RageVector3( iX+g.m_fHshift+g.m_fWidth, iY+g.m_pPage->m_fVshift, 0 ); // top right /* Advance the cursor. */ iX += g.m_iHadvance; /* set texture coordinates */ v[0].t = RageVector2( g.m_TexRect.left, g.m_TexRect.top ); v[1].t = RageVector2( g.m_TexRect.left, g.m_TexRect.bottom ); v[2].t = RageVector2( g.m_TexRect.right, g.m_TexRect.bottom ); v[3].t = RageVector2( g.m_TexRect.right, g.m_TexRect.top ); m_aVertices.insert( m_aVertices.end(), &v[0], &v[4] ); m_pTextures.push_back( g.GetTexture() ); } /* The amount of padding a line needs: */ iY += Padding; } }
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 if( m_bUsingCustomPosCoords ) { for( int i=0; i < 4; ++i) { v[i].p.x+= m_CustomPosCoords[i*2]; v[i].p.y+= m_CustomPosCoords[(i*2)+1]; } } 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 ); }
void Sprite::ScaleToClipped( float fWidth, float fHeight ) { m_fRememberedClipWidth = fWidth; m_fRememberedClipHeight = fHeight; if( !m_pTexture ) return; int iSourceWidth = m_pTexture->GetSourceWidth(); int iSourceHeight = m_pTexture->GetSourceHeight(); // save the original X&Y. We're going to resore them later. float fOriginalX = GetX(); float fOriginalY = GetY(); if( IsDiagonalBanner(iSourceWidth, iSourceHeight) ) // this is a SSR/DWI CroppedSprite { float fCustomImageCoords[8] = { 0.02f, 0.78f, // top left 0.22f, 0.98f, // bottom left 0.98f, 0.22f, // bottom right 0.78f, 0.02f, // top right }; Sprite::SetCustomImageCoords( fCustomImageCoords ); if( fWidth != -1 && fHeight != -1) m_size = RageVector2( fWidth, fHeight ); else { /* If no crop size is set, then we're only being used to crop diagonal * banners so they look like regular ones. We don't actually care about * the size of the image, only that it has an aspect ratio of 4:1. */ m_size = RageVector2(256, 64); } SetZoom( 1 ); } else if( m_pTexture->GetID().filename.find( "(was rotated)" ) != m_pTexture->GetID().filename.npos && fWidth != -1 && fHeight != -1 ) { /* Dumb hack. Normally, we crop all sprites except for diagonal banners, * which are stretched. Low-res versions of banners need to do the same * thing as their full resolution counterpart, so the crossfade looks right. * However, low-res diagonal banners are un-rotated, to save space. BannerCache * drops the above text into the "filename" (which is otherwise unused for * these banners) to tell us this. */ Sprite::StopUsingCustomCoords(); m_size = RageVector2( fWidth, fHeight ); SetZoom( 1 ); } else 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 ); }
void Sprite::DrawTexture( const TweenState *state ) { Actor::SetGlobalRenderStates(); // set Actor-specified render states // bail if cropped all the way if( state->crop.left + state->crop.right >= 1 || state->crop.top + state->crop.bottom >= 1 ) return; // use m_temp_* variables to draw the object RectF quadVerticies; switch( m_HorizAlign ) { case align_left: quadVerticies.left = 0; quadVerticies.right = m_size.x; break; case align_center: quadVerticies.left = -m_size.x/2; quadVerticies.right = m_size.x/2; break; case align_right: quadVerticies.left = -m_size.x; quadVerticies.right = 0; break; default: ASSERT( false ); } switch( m_VertAlign ) { case align_top: quadVerticies.top = 0; quadVerticies.bottom = m_size.y; break; case align_middle: quadVerticies.top = -m_size.y/2; quadVerticies.bottom = m_size.y/2; break; case align_bottom: quadVerticies.top = -m_size.y; quadVerticies.bottom = 0; break; default: ASSERT(0); } /* 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 to that the diffuse value is adjusted. */ RectF crop = state->crop; 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( 0, m_pTexture ); // Must call this after setting the texture or else texture // parameters have no effect. Actor::SetTextureRenderStates(); // set Actor-specified render states if( m_pTexture ) { float f[8]; GetActiveTextureCoords(f); TexCoordsFromArray(v, f); 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 }; if( crop.left!=0 ) { v[0].t.x = SCALE( crop.left, 0.f, 1.f, texCoords[0].x, texCoords[3].x ); v[1].t.x = SCALE( crop.left, 0.f, 1.f, texCoords[1].x, texCoords[2].x ); } if( crop.right!=0 ) { v[2].t.x = SCALE( crop.right, 0.f, 1.f, texCoords[2].x, texCoords[1].x ); v[3].t.x = SCALE( crop.right, 0.f, 1.f, texCoords[3].x, texCoords[0].x ); } if( crop.top!=0 ) { v[0].t.y = SCALE( crop.top, 0.f, 1.f, texCoords[0].y, texCoords[1].y ); v[3].t.y = SCALE( crop.top, 0.f, 1.f, texCoords[3].y, texCoords[2].y ); } if( crop.bottom!=0 ) { v[1].t.y = SCALE( crop.bottom, 0.f, 1.f, texCoords[1].y, texCoords[0].y ); v[2].t.y = SCALE( crop.bottom, 0.f, 1.f, texCoords[2].y, texCoords[3].y ); } } 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->SetTextureModeModulate(); ////////////////////// // render the shadow ////////////////////// if( m_fShadowLength != 0 ) { DISPLAY->PushMatrix(); DISPLAY->TranslateWorld( m_fShadowLength, m_fShadowLength, 0 ); // shift by 5 units v[0].c = v[1].c = v[2].c = v[3].c = RageColor(0,0,0,0.5f*state->diffuse[0].a); // 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->SetTextureModeGlow(); v[0].c = v[1].c = v[2].c = v[3].c = state->glow; DISPLAY->DrawQuad( v ); } }
void BitmapText::BuildChars() { // If we don't have a font yet, we'll do this when it loads. if( m_pFont == NULL ) return; // calculate line lengths and widths m_size.x = 0; m_iLineWidths.clear(); for( unsigned l=0; l<m_wTextLines.size(); l++ ) // for each line { m_iLineWidths.push_back(m_pFont->GetLineWidthInSourcePixels( m_wTextLines[l] )); m_size.x = max( m_size.x, m_iLineWidths.back() ); } /* Ensure that the width is always even. This maintains pixel alignment; * fX below will always be an integer. */ m_size.x = QuantizeUp( m_size.x, 2.0f ); m_aVertices.clear(); m_vpFontPageTextures.clear(); if( m_wTextLines.empty() ) return; m_size.y = float(m_pFont->GetHeight() * m_wTextLines.size()); // The height (from the origin to the baseline): int iPadding = m_pFont->GetLineSpacing() - m_pFont->GetHeight(); iPadding += m_iVertSpacing; // There's padding between every line: m_size.y += iPadding * int(m_wTextLines.size()-1); // the top position of the first row of characters int iY = lrintf(-m_size.y/2.0f); for( unsigned i=0; i<m_wTextLines.size(); i++ ) // foreach line { iY += m_pFont->GetHeight(); wstring sLine = m_wTextLines[i]; if( m_pFont->IsRightToLeft() ) reverse( sLine.begin(), sLine.end() ); const int iLineWidth = m_iLineWidths[i]; float fX = SCALE( m_fHorizAlign, 0.0f, 1.0f, -m_size.x/2.0f, +m_size.x/2.0f - iLineWidth ); int iX = lrintf( fX ); for( unsigned j = 0; j < sLine.size(); ++j ) { RageSpriteVertex v[4]; const glyph &g = m_pFont->GetGlyph( sLine[j] ); if( m_pFont->IsRightToLeft() ) iX -= g.m_iHadvance; // set vertex positions v[0].p = RageVector3( iX+g.m_fHshift, iY+g.m_pPage->m_fVshift, 0 ); // top left v[1].p = RageVector3( iX+g.m_fHshift, iY+g.m_pPage->m_fVshift+g.m_fHeight, 0 ); // bottom left v[2].p = RageVector3( iX+g.m_fHshift+g.m_fWidth, iY+g.m_pPage->m_fVshift+g.m_fHeight, 0 ); // bottom right v[3].p = RageVector3( iX+g.m_fHshift+g.m_fWidth, iY+g.m_pPage->m_fVshift, 0 ); // top right // Advance the cursor. iX += g.m_iHadvance; // set texture coordinates v[0].t = RageVector2( g.m_TexRect.left, g.m_TexRect.top ); v[1].t = RageVector2( g.m_TexRect.left, g.m_TexRect.bottom ); v[2].t = RageVector2( g.m_TexRect.right, g.m_TexRect.bottom ); v[3].t = RageVector2( g.m_TexRect.right, g.m_TexRect.top ); m_aVertices.insert( m_aVertices.end(), &v[0], &v[4] ); m_vpFontPageTextures.push_back( g.GetFontPageTextures() ); } // The amount of padding a line needs: iY += iPadding; } if( m_bUsingDistortion ) { int iSeed = lrintf( RageTimer::GetTimeSinceStartFast()*500000.0f ); RandomGen rnd( iSeed ); for(unsigned int i= 0; i < m_aVertices.size(); i+=4) { float w= m_aVertices[i+2].p.x - m_aVertices[i].p.x; float h= m_aVertices[i+2].p.y - m_aVertices[i].p.y; for(unsigned int ioff= 0; ioff < 4; ++ioff) { m_aVertices[i+ioff].p.x += ((rnd()%9) / 8.0f - .5f) * m_fDistortion * w; m_aVertices[i+ioff].p.y += ((rnd()%9) / 8.0f - .5f) * m_fDistortion * h; } } } }
Actor::Actor() { m_size = RageVector2( 1, 1 ); Reset(); m_bFirstUpdate = true; }
void NoteDisplay::DrawHoldBottomCap( const TapNote& tn, int iCol, int iRow, bool bIsBeingHeld, float fYHead, float fYTail, int fYStep, float fPercentFadeToFail, float fColorScale, bool bGlow, float fYStartOffset, float fYEndOffset ) { // // Draw the bottom cap (always wavy) // StripBuffer queue; Sprite* pBottomCap = GetHoldBottomCapSprite( NoteRowToBeat(iRow), tn.subType == TapNote::hold_head_roll, bIsBeingHeld ); pBottomCap->SetZoom( ArrowEffects::GetZoom( m_pPlayerState ) ); // draw manually in small segments RageTexture* pTexture = pBottomCap->GetTexture(); const RectF *pRect = pBottomCap->GetCurrentTextureCoordRect(); DISPLAY->ClearAllTextures(); DISPLAY->SetTexture( 0, pTexture ); DISPLAY->SetBlendMode( BLEND_NORMAL ); DISPLAY->SetCullMode( CULL_NONE ); DISPLAY->SetTextureWrapping(false); const float fFrameWidth = pBottomCap->GetZoomedWidth(); const float fFrameHeight = pBottomCap->GetZoomedHeight(); const float fYCapTop = fYTail+cache->m_iStopDrawingHoldBodyOffsetFromTail; const float fYCapBottom = fYTail+cache->m_iStopDrawingHoldBodyOffsetFromTail+fFrameHeight; bool bReverse = m_pPlayerState->m_CurrentPlayerOptions.GetReversePercentForColumn(iCol) > 0.5f; if( bGlow ) fColorScale = 1; float fDrawYCapTop; float fDrawYCapBottom; { float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYStartOffset, m_fYReverseOffsetPixels ); float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYEndOffset, m_fYReverseOffsetPixels ); fDrawYCapTop = max( fYCapTop, bReverse ? fYEndPos : fYStartPos ); fDrawYCapBottom = min( fYCapBottom, bReverse ? fYStartPos : fYEndPos ); } bool bAllAreTransparent = true; bool bLast = false; // don't draw any part of the tail that is before the middle of the head float fY=max( fDrawYCapTop, fYHead ); for( ; !bLast; fY += fYStep ) { if( fY >= fDrawYCapBottom ) { fY = fDrawYCapBottom; bLast = true; } const float fYOffset = ArrowEffects::GetYOffsetFromYPos( m_pPlayerState, iCol, fY, m_fYReverseOffsetPixels ); const float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ); const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ); // XXX: Actor rotations use degrees, RageFastCos/Sin use radians. Convert here. const float fRotationY = ArrowEffects::GetRotationY( m_pPlayerState, fYOffset ) * PI/180; // if we're rotating, we need to modify the X and Z coords for the outer edges. const float fRotOffsetX = fFrameWidth/2 * RageFastCos(fRotationY); const float fRotOffsetZ = fFrameWidth/2 * RageFastSin(fRotationY); const float fXLeft = fX - fRotOffsetX; const float fXRight = fX + fRotOffsetX; const float fZLeft = fZ - fRotOffsetZ; const float fZRight = fZ + fRotOffsetZ; const float fTopDistFromTail = fY - fYCapTop; const float fTexCoordTop = SCALE( fTopDistFromTail, 0, fFrameHeight, pRect->top, pRect->bottom ); const float fTexCoordLeft = pRect->left; const float fTexCoordRight = pRect->right; const float fAlpha = ArrowGetAlphaOrGlow( bGlow, m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels ); const RageColor color = RageColor(fColorScale,fColorScale,fColorScale,fAlpha); if( fAlpha > 0 ) bAllAreTransparent = false; queue.v[0].p = RageVector3(fXLeft, fY, fZLeft); queue.v[0].c = color; queue.v[0].t = RageVector2(fTexCoordLeft, fTexCoordTop); queue.v[1].p = RageVector3(fXRight, fY, fZRight); queue.v[1].c = color; queue.v[1].t = RageVector2(fTexCoordRight, fTexCoordTop); queue.v+=2; if( queue.Free() < 2 ) { /* The queue is full. Render it, clear the buffer, and move back a step to * start off the quad strip again. */ if( !bAllAreTransparent ) queue.Draw(); queue.Init(); bAllAreTransparent = true; fY -= fYStep; } } if( !bAllAreTransparent ) queue.Draw(); }
void NoteDisplay::DrawHoldBody( const TapNote& tn, int iCol, int iRow, bool bIsBeingHeld, float fYHead, float fYTail, int fYStep, float fPercentFadeToFail, float fColorScale, bool bGlow, float fYStartOffset, float fYEndOffset ) { // // Draw the body (always wavy) // StripBuffer queue; Sprite* pSprBody = GetHoldBodySprite( NoteRowToBeat(iRow), tn.subType == TapNote::hold_head_roll, bIsBeingHeld ); pSprBody->SetZoom( ArrowEffects::GetZoom( m_pPlayerState ) ); // draw manually in small segments RageTexture* pTexture = pSprBody->GetTexture(); const RectF *pRect = pSprBody->GetCurrentTextureCoordRect(); DISPLAY->ClearAllTextures(); DISPLAY->SetTexture( 0, pTexture ); DISPLAY->SetBlendMode( BLEND_NORMAL ); DISPLAY->SetCullMode( CULL_NONE ); DISPLAY->SetTextureWrapping( true ); const float fFrameWidth = pSprBody->GetZoomedWidth(); const float fFrameHeight = pSprBody->GetZoomedHeight(); const float fYBodyTop = fYHead + cache->m_iStartDrawingHoldBodyOffsetFromHead; const float fYBodyBottom = fYTail + cache->m_iStopDrawingHoldBodyOffsetFromTail; const bool bReverse = m_pPlayerState->m_CurrentPlayerOptions.GetReversePercentForColumn(iCol) > 0.5f; bool bAnchorToBottom = bReverse && cache->m_bFlipHeadAndTailWhenReverse; if( bGlow ) fColorScale = 1; /* Only draw the section that's within the range specified. If a hold note is * very long, don't process or draw the part outside of the range. Don't change * fYBodyTop or fYBodyBottom; they need to be left alone to calculate texture * coordinates. */ float fDrawYBodyTop; float fDrawYBodyBottom; { float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYStartOffset, m_fYReverseOffsetPixels ); float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYEndOffset, m_fYReverseOffsetPixels ); fDrawYBodyTop = max( fYBodyTop, bReverse ? fYEndPos : fYStartPos ); fDrawYBodyBottom = min( fYBodyBottom, bReverse ? fYStartPos : fYEndPos ); } // top to bottom bool bAllAreTransparent = true; bool bLast = false; float fVertTexCoordOffset = 0; for( float fY = fDrawYBodyTop; !bLast; fY += fYStep ) { if( fY >= fDrawYBodyBottom ) { fY = fDrawYBodyBottom; bLast = true; } const float fYOffset = ArrowEffects::GetYOffsetFromYPos( m_pPlayerState, iCol, fY, m_fYReverseOffsetPixels ); const float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ); const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ); // XXX: Actor rotations use degrees, RageFastCos/Sin use radians. Convert here. const float fRotationY = ArrowEffects::GetRotationY( m_pPlayerState, fYOffset ) * PI/180; // if we're rotating, we need to modify the X and Z coords for the outer edges. const float fRotOffsetX = fFrameWidth/2 * RageFastCos(fRotationY); const float fRotOffsetZ = fFrameWidth/2 * RageFastSin(fRotationY); const float fXLeft = fX - fRotOffsetX; const float fXRight = fX + fRotOffsetX; const float fZLeft = fZ - fRotOffsetZ; const float fZRight = fZ + fRotOffsetZ; const float fDistFromBodyBottom = fYBodyBottom - fY; const float fDistFromBodyTop = fY - fYBodyTop; float fTexCoordTop = SCALE( bAnchorToBottom ? fDistFromBodyTop : fDistFromBodyBottom, 0, fFrameHeight, pRect->bottom, pRect->top ); /* For very large hold notes, shift the texture coordinates to be near 0, so we * don't send very large values to the renderer. */ if( fY == fDrawYBodyTop ) // first fVertTexCoordOffset = floorf( fTexCoordTop ); fTexCoordTop -= fVertTexCoordOffset; const float fTexCoordLeft = pRect->left; const float fTexCoordRight = pRect->right; const float fAlpha = ArrowGetAlphaOrGlow( bGlow, m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels ); const RageColor color = RageColor(fColorScale,fColorScale,fColorScale,fAlpha); if( fAlpha > 0 ) bAllAreTransparent = false; queue.v[0].p = RageVector3(fXLeft, fY, fZLeft); queue.v[0].c = color; queue.v[0].t = RageVector2(fTexCoordLeft, fTexCoordTop); queue.v[1].p = RageVector3(fXRight, fY, fZRight); queue.v[1].c = color; queue.v[1].t = RageVector2(fTexCoordRight, fTexCoordTop); queue.v+=2; if( queue.Free() < 2 ) { /* The queue is full. Render it, clear the buffer, and move back a step to * start off the quad strip again. */ if( !bAllAreTransparent ) queue.Draw(); queue.Init(); bAllAreTransparent = true; fY -= fYStep; } } if( !bAllAreTransparent ) queue.Draw(); }