#include "global.h" /* ----------------------------------------------------------------------------- Class: NoteField Desc: See header. Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. Chris Danford ----------------------------------------------------------------------------- */ #include "NoteField.h" #include "RageUtil.h" #include "GameConstantsAndTypes.h" #include "PrefsManager.h" #include "ArrowEffects.h" #include "PrefsManager.h" #include "GameManager.h" #include "GameState.h" #include "RageException.h" #include "RageTimer.h" #include "RageLog.h" #include #include "ThemeManager.h" #include "RageDisplay.h" const float HOLD_NOTE_BITS_PER_BEAT = 6; const float HOLD_NOTE_BITS_PER_ROW = HOLD_NOTE_BITS_PER_BEAT / ROWS_PER_BEAT; const float ROWS_BETWEEN_HOLD_BITS = 1 / HOLD_NOTE_BITS_PER_ROW; NoteField::NoteField() { m_textMeasureNumber.LoadFromFont( THEME->GetPathTo("Fonts","Common normal") ); m_textMeasureNumber.SetZoom( 1.0f ); m_rectMarkerBar.EnableShadow( false ); m_rectMarkerBar.SetEffectDiffuseShift( 2, RageColor(1,1,1,0.5f), RageColor(0.5f,0.5f,0.5f,0.5f) ); m_sprBars.Load( THEME->GetPathTo("Graphics","NoteField bars") ); m_sprBars.StopAnimating(); m_fBeginMarker = m_fEndMarker = -1; m_fPercentFadeToFail = -1; } void NoteField::Load( NoteData* pNoteData, PlayerNumber pn, int iFirstPixelToDraw, int iLastPixelToDraw ) { m_PlayerNumber = pn; m_iFirstPixelToDraw = iFirstPixelToDraw; m_iLastPixelToDraw = iLastPixelToDraw; m_fPercentFadeToFail = -1; NoteDataWithScoring::Init(); m_bIsHoldingHoldNote.clear(); m_bIsHoldingHoldNote.insert(m_bIsHoldingHoldNote.end(), pNoteData->GetNumTapNotes(), false); this->CopyAll( pNoteData ); // init note displays for( int c=0; cGetCurrentStyleDef()->m_iColsPerPlayer ); } void NoteField::Update( float fDeltaTime ) { m_rectMarkerBar.Update( fDeltaTime ); if( m_fPercentFadeToFail >= 0 ) m_fPercentFadeToFail = min( m_fPercentFadeToFail + fDeltaTime/1.5f, 1 ); // take 1.5 seconds to totally fade } float NoteField::GetWidth() { const StyleDef* pStyleDef = GAMESTATE->GetCurrentStyleDef(); float fMinX, fMaxX; pStyleDef->GetMinAndMaxColX( m_PlayerNumber, fMinX, fMaxX ); return fMaxX - fMinX + ARROW_SIZE; } void NoteField::DrawBeatBar( const float fBeat ) { bool bIsMeasure = fmodf( fBeat, (float)BEATS_PER_MEASURE ) == 0; int iMeasureIndex = (int)fBeat / BEATS_PER_MEASURE; int iMeasureNoDisplay = iMeasureIndex+1; NoteType nt = BeatToNoteType( fBeat ); const float fYOffset = ArrowGetYOffset( m_PlayerNumber, fBeat ); const float fYPos = ArrowGetYPos( m_PlayerNumber, fYOffset ); float fAlpha; int iState; if( bIsMeasure ) { fAlpha = 1; iState = 0; } else { float fScrollSpeed = GAMESTATE->m_CurrentPlayerOptions[m_PlayerNumber].m_fScrollSpeed; switch( nt ) { default: ASSERT(0); case NOTE_TYPE_4TH: fAlpha = 1; iState = 1; break; case NOTE_TYPE_8TH: fAlpha = SCALE(fScrollSpeed,1.f,2.f,0.f,1.f); iState = 2; break; case NOTE_TYPE_16TH:fAlpha = SCALE(fScrollSpeed,2.f,4.f,0.f,1.f); iState = 3; break; } CLAMP( fAlpha, 0, 1 ); } float fWidth = GetWidth(); float fFrameWidth = m_sprBars.GetUnzoomedWidth(); m_sprBars.SetX( 0 ); m_sprBars.SetY( fYPos ); m_sprBars.SetDiffuse( RageColor(1,1,1,fAlpha) ); m_sprBars.SetState( iState ); m_sprBars.SetCustomTextureRect( RectF(0,SCALE(iState,0.f,4.f,0.f,1.f), fWidth/fFrameWidth, SCALE(iState+1,0.f,4.f,0.f,1.f)) ); m_sprBars.SetZoomX( fWidth/m_sprBars.GetUnzoomedWidth() ); m_sprBars.Draw(); if( bIsMeasure ) { m_textMeasureNumber.SetDiffuse( RageColor(1,1,1,1) ); m_textMeasureNumber.SetGlow( RageColor(1,1,1,0) ); m_textMeasureNumber.SetHorizAlign( Actor::align_right ); m_textMeasureNumber.SetText( ssprintf("%d", iMeasureNoDisplay) ); m_textMeasureNumber.SetXY( -fWidth/2, fYPos ); m_textMeasureNumber.Draw(); } } void NoteField::DrawMarkerBar( const float fBeat ) { const float fYOffset = ArrowGetYOffset( m_PlayerNumber, fBeat ); const float fYPos = ArrowGetYPos( m_PlayerNumber, fYOffset ); m_rectMarkerBar.StretchTo( RectF(-GetWidth()/2, fYPos-ARROW_SIZE/2, GetWidth()/2, fYPos+ARROW_SIZE/2) ); m_rectMarkerBar.Draw(); } void NoteField::DrawAreaHighlight( const float fStartBeat, const float fEndBeat ) { float fYStartOffset = ArrowGetYOffset( m_PlayerNumber, fStartBeat ); float fYStartPos = ArrowGetYPos( m_PlayerNumber, fYStartOffset ); float fYEndOffset = ArrowGetYOffset( m_PlayerNumber, fEndBeat ); float fYEndPos = ArrowGetYPos( m_PlayerNumber, fYEndOffset ); // Something in OpenGL crashes if this is values are too large. Strange. -Chris fYStartPos = max( fYStartPos, -1000 ); fYEndPos = min( fYEndPos, +5000 ); m_rectAreaHighlight.StretchTo( RectF(-GetWidth()/2, fYStartPos-ARROW_SIZE/2, GetWidth()/2, fYEndPos+ARROW_SIZE/2) ); m_rectAreaHighlight.SetDiffuse( RageColor(1,0,0,0.3f) ); m_rectAreaHighlight.Draw(); } void NoteField::DrawBPMText( const float fBeat, const float fBPM ) { const float fYOffset = ArrowGetYOffset( m_PlayerNumber, fBeat ); const float fYPos = ArrowGetYPos( m_PlayerNumber, fYOffset ); m_textMeasureNumber.SetHorizAlign( Actor::align_right ); m_textMeasureNumber.SetDiffuse( RageColor(1,0,0,1) ); m_textMeasureNumber.SetGlow( RageColor(1,1,1,cosf(RageTimer::GetTimeSinceStart()*2)/2+0.5f) ); m_textMeasureNumber.SetText( ssprintf("%.2f", fBPM) ); m_textMeasureNumber.SetXY( -GetWidth()/2.f - 60, fYPos ); m_textMeasureNumber.Draw(); } void NoteField::DrawFreezeText( const float fBeat, const float fSecs ) { const float fYOffset = ArrowGetYOffset( m_PlayerNumber, fBeat ); const float fYPos = ArrowGetYPos( m_PlayerNumber, fYOffset ); m_textMeasureNumber.SetHorizAlign( Actor::align_right ); m_textMeasureNumber.SetDiffuse( RageColor(0.8f,0.8f,0,1) ); m_textMeasureNumber.SetGlow( RageColor(1,1,1,cosf(RageTimer::GetTimeSinceStart()*2)/2+0.5f) ); m_textMeasureNumber.SetText( ssprintf("%.2f", fSecs) ); m_textMeasureNumber.SetXY( -GetWidth()/2.f - 10, fYPos ); m_textMeasureNumber.Draw(); } void NoteField::DrawBGChangeText( const float fBeat, const CString sNewBGName ) { const float fYOffset = ArrowGetYOffset( m_PlayerNumber, fBeat ); const float fYPos = ArrowGetYPos( m_PlayerNumber, fYOffset ); m_textMeasureNumber.SetHorizAlign( Actor::align_left ); m_textMeasureNumber.SetDiffuse( RageColor(0,1,0,1) ); m_textMeasureNumber.SetGlow( RageColor(1,1,1,cosf(RageTimer::GetTimeSinceStart()*2)/2+0.5f) ); m_textMeasureNumber.SetText( sNewBGName ); m_textMeasureNumber.SetXY( +GetWidth()/2.f, fYPos ); m_textMeasureNumber.Draw(); } void NoteField::DrawPrimitives() { //LOG->Trace( "NoteField::DrawPrimitives()" ); const float fSongBeat = GAMESTATE->m_fSongBeat; // CPU OPTIMIZATION OPPORTUNITY: // change this probing to binary search // probe for first note on the screen float fFirstBeatToDraw = fSongBeat-4; // Adjust to balance of performance and showing enough notes. while( fFirstBeatToDrawfSongBeat ) { float fYOffset = ArrowGetYOffset(m_PlayerNumber, fLastBeatToDraw); float fYPosWOReverse = ArrowGetYPosWithoutReverse(m_PlayerNumber, fYOffset ); if( fYPosWOReverse > m_iLastPixelToDraw ) // off screen fLastBeatToDraw -= 0.1f; // move toward fSongBeat else // on screen break; // stop probing } fLastBeatToDraw += 0.1f; // fast forward since we intentionally overshot const int iFirstIndexToDraw = BeatToNoteRow(fFirstBeatToDraw); const int iLastIndexToDraw = BeatToNoteRow(fLastBeatToDraw); // LOG->Trace( "start = %f.1, end = %f.1", fFirstBeatToDraw-fSongBeat, fLastBeatToDraw-fSongBeat ); // LOG->Trace( "Drawing elements %d through %d", iFirstIndexToDraw, iLastIndexToDraw ); if( GAMESTATE->m_bEditing ) { ASSERT(GAMESTATE->m_pCurSong); unsigned i; // // Draw beat bars // { float fStartDrawingMeasureBars = max( 0, froundf(fFirstBeatToDraw-0.25f,0.25f) ); for( float f=fStartDrawingMeasureBars; f &aBPMSegments = GAMESTATE->m_pCurSong->m_BPMSegments; for( i=0; i= fFirstBeatToDraw && aBPMSegments[i].m_fStartBeat <= fLastBeatToDraw) DrawBPMText( aBPMSegments[i].m_fStartBeat, aBPMSegments[i].m_fBPM ); } // // Freeze text // vector &aStopSegments = GAMESTATE->m_pCurSong->m_StopSegments; for( i=0; i= fFirstBeatToDraw && aStopSegments[i].m_fStartBeat <= fLastBeatToDraw) DrawFreezeText( aStopSegments[i].m_fStartBeat, aStopSegments[i].m_fStopSeconds ); } // // BGChange text // vector &aBackgroundChanges = GAMESTATE->m_pCurSong->m_BackgroundChanges; for( i=0; i= fFirstBeatToDraw && aBackgroundChanges[i].m_fStartBeat <= fLastBeatToDraw) DrawBGChangeText( aBackgroundChanges[i].m_fStartBeat, aBackgroundChanges[i].m_sBGName ); } // // Draw marker bars // if( m_fBeginMarker != -1 && m_fEndMarker != -1 ) DrawAreaHighlight( m_fBeginMarker, m_fEndMarker ); else if( m_fBeginMarker != -1 ) DrawMarkerBar( m_fBeginMarker ); else if( m_fEndMarker != -1 ) DrawMarkerBar( m_fEndMarker ); } // // Optimization is very important here because there are so many arrows to draw. // Draw the arrows in order of column. This minimize texture switches and let us // draw in big batches. // float fSelectedRangeGlow = SCALE( cosf(RageTimer::GetTimeSinceStart()*2), -1, 1, 0.1f, 0.3f ); for( int c=0; c fLastBeatToDraw ) ) { continue; // skip } bool bIsInSelectionRange = false; if( m_fBeginMarker!=-1 && m_fEndMarker!=-1 ) bIsInSelectionRange = m_fBeginMarker<=hn.fStartBeat && hn.fStartBeat<=m_fEndMarker && m_fBeginMarker<=hn.fEndBeat && hn.fEndBeat<=m_fEndMarker; m_NoteDisplay[c].DrawHold( hn, bIsHoldingNote, fLife, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail ); } /////////////////////////////////// // Draw all TapNotes in this column /////////////////////////////////// for( i=iFirstIndexToDraw; i<=iLastIndexToDraw; i++ ) // for each row { if( GetTapNote(c, i) == TAP_EMPTY ) // no note here continue; // skip if( GetTapNote(c, i) == TAP_HOLD_HEAD ) // this is a HoldNote begin marker. Grade it, but don't draw continue; // skip // See if there is a hold step that begins on this index. bool bHoldNoteBeginsOnThisBeat = false; for( int c2=0; c2