diff --git a/src/ArrowEffects.cpp b/src/ArrowEffects.cpp index d87a944952..3c2bdd5f4c 100644 --- a/src/ArrowEffects.cpp +++ b/src/ArrowEffects.cpp @@ -213,6 +213,31 @@ void ArrowEffects::Update() } } +static float GetDisplayedBeat( const PlayerState* pPlayerState, float beat ) +{ + // do a binary search here + const vector &data = pPlayerState->m_CacheDisplayedBeat; + int max = data.size() - 1; + int l = 0, r = max; + while( l <= r ) + { + int m = ( l + r ) / 2; + if( ( m == 0 || data[m].beat <= beat ) && ( m == max || beat < data[m + 1].beat ) ) + { + return data[m].displayedBeat + data[m].velocity * (beat - data[m].beat); + } + else if( data[m].beat <= beat ) + { + l = m + 1; + } + else + { + r = m - 1; + } + } + return beat; +} + /* For visibility testing: if bAbsolute is false, random modifiers must return * the minimum possible scroll speed. */ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float fNoteBeat, float &fPeakYOffsetOut, bool &bIsPastPeakOut, bool bAbsolute ) @@ -235,7 +260,7 @@ float ArrowEffects::GetYOffset( const PlayerState* pPlayerState, int iCol, float float bShowEffects = !( GAMESTATE->m_bInStepEditor || !GAMESTATE->m_bIsUsingStepTiming ); float fBeatsUntilStep = fNoteBeat - fSongBeat; if( bShowEffects ) - fBeatsUntilStep = pCurSteps->m_Timing.GetDisplayedBeat(fNoteBeat) - pCurSteps->m_Timing.GetDisplayedBeat(fSongBeat); + fBeatsUntilStep = GetDisplayedBeat(pPlayerState, fNoteBeat) - GetDisplayedBeat(pPlayerState, fSongBeat); float fYOffsetBeatSpacing = fBeatsUntilStep; float fSpeedMultiplier = bShowEffects ? pCurSteps->m_Timing.GetDisplayedSpeedPercent( diff --git a/src/Player.cpp b/src/Player.cpp index 9ff5a7d923..ce7146f9df 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -751,6 +751,27 @@ void Player::SendComboMessages( int iOldCombo, int iOldMissCombo ) } } +static void GenerateCacheDataStructure(PlayerState *pPlayerState, NoteData ¬es) { + + pPlayerState->m_CacheDisplayedBeat.clear(); + + const vector *segs = pPlayerState->GetDisplayedTiming().m_avpTimingSegments; + + float displayedBeat = 0.0f; + float lastRealBeat = 0.0f; + float lastRatio = 1.0f; + for ( unsigned i = 0; i < segs[SEGMENT_SCROLL].size(); i++ ) + { + ScrollSegment *seg = static_cast(segs[SEGMENT_SCROLL][i]); + displayedBeat += ( seg->GetBeat() - lastRealBeat ) * lastRatio; + lastRealBeat = seg->GetBeat(); + lastRatio = seg->GetRatio(); + CacheDisplayedBeat c = { seg->GetBeat(), displayedBeat, seg->GetRatio() }; + pPlayerState->m_CacheDisplayedBeat.push_back( c ); + } + +} + void Player::Update( float fDeltaTime ) { const RageTimer now; @@ -880,6 +901,9 @@ void Player::Update( float fDeltaTime ) if( m_bPaused ) return; + // Generate some cache data structure. + GenerateCacheDataStructure(m_pPlayerState, m_NoteData); + // Check for a strum miss if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 && m_pPlayerState->m_fLastStrumMusicSeconds + g_fTimingWindowStrum < m_pPlayerState->m_Position.m_fMusicSeconds ) diff --git a/src/PlayerState.h b/src/PlayerState.h index 0ab7dfe1be..4a02e8c86c 100644 --- a/src/PlayerState.h +++ b/src/PlayerState.h @@ -12,6 +12,12 @@ #include "SampleHistory.h" struct lua_State; +struct CacheDisplayedBeat { + float beat; + float displayedBeat; + float velocity; +}; + /** @brief The player's indivdual state. */ class PlayerState { @@ -44,6 +50,16 @@ public: const SongPosition &GetDisplayedPosition() const; const TimingData &GetDisplayedTiming() const; + + /** + * @brief Holds a vector sorted by real beat, the beat that would be displayed + * in the NoteField (because they are affected by scroll segments), and + * also the velocity. + * This vector will be populated each frame by Player and will probably + * be used a lot in ArrowEffects. This is way better than iterating through + * all scroll segments per tap note per frame, which is very slow! + */ + vector m_CacheDisplayedBeat; /** * @brief Change the PlayerOptions to their default.