From 07fb61a29191340aa70f7005f9645403786e9b7f Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Thu, 25 Dec 2014 05:02:28 -0700 Subject: [PATCH] Moved tap and hold rendering logic out of NoteField and into NoteDisplay. --- src/NoteDisplay.cpp | 185 ++++++++++++++++++++++++++++++++++++++ src/NoteDisplay.h | 32 +++++++ src/NoteField.cpp | 212 +++++++++++--------------------------------- 3 files changed, 271 insertions(+), 158 deletions(-) diff --git a/src/NoteDisplay.cpp b/src/NoteDisplay.cpp index d7606f5ed9..fca04b8b32 100644 --- a/src/NoteDisplay.cpp +++ b/src/NoteDisplay.cpp @@ -1,6 +1,8 @@ #include "global.h" #include "NoteDisplay.h" #include "GameState.h" +#include "GhostArrowRow.h" +#include "NoteData.h" #include "NoteSkinManager.h" #include "ArrowEffects.h" #include "RageLog.h" @@ -317,6 +319,189 @@ void NoteDisplay::Load( int iColNum, const PlayerState* pPlayerState, float fYRe } } +inline float NoteRowToVisibleBeat( const PlayerState *pPlayerState, int iRow ) +{ + return NoteRowToBeat(iRow); +} + +bool NoteDisplay::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const +{ + // IMPORTANT: Do not modify this function without also modifying the + // version that is in NoteField.cpp or coming up with a good way to + // merge them. -Kyz + // TRICKY: If boomerang is on, then ones in the range + // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible. + // Test to see if this beat is visible before drawing. + float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fBeat ); + if( fYOffset > iDrawDistanceBeforeTargetsPixels ) // off screen + return false; + if( fYOffset < iDrawDistanceAfterTargetsPixels ) // off screen + return false; + + return true; +} + +bool NoteDisplay::DrawHoldsInRange(NoteDisplayRenderArgs const& args, + vector const& tap_set) +{ + bool any_upcoming = false; + for(vector::const_iterator tapit= + tap_set.begin(); tapit != tap_set.end(); ++tapit) + { + TapNote const& tn= (*tapit)->second; + HoldNoteResult const& result= tn.HoldResult; + int start_row= (*tapit)->first; + int end_row = start_row + tn.iDuration; + + // TRICKY: If boomerang is on, then all notes in the range + // [first_row,last_row] aren't necessarily visible. + // Test every note to make sure it's on screen before drawing + float throw_away; + bool start_past_peak = false; + bool end_past_peak = false; + float start_y = ArrowEffects::GetYOffset(m_pPlayerState, args.column, + NoteRowToVisibleBeat(m_pPlayerState, start_row), throw_away, + start_past_peak); + float end_y = ArrowEffects::GetYOffset(m_pPlayerState, args.column, + NoteRowToVisibleBeat(m_pPlayerState, end_row), throw_away, + end_past_peak); + bool tail_visible = args.draw_pixels_after_targets <= end_y && + end_y <= args.draw_pixels_before_targets; + bool head_visible = args.draw_pixels_after_targets <= start_y && + start_y <= args.draw_pixels_before_targets; + bool straddling_visible = start_y <= args.draw_pixels_after_targets && + args.draw_pixels_before_targets <= end_y; + bool straddling_peak = start_past_peak && !end_past_peak; + if(!(tail_visible || head_visible || straddling_visible || straddling_peak)) + { + //LOG->Trace( "skip drawing this hold." ); + continue; // skip + } + + bool is_addition = (tn.source == TapNoteSource_Addition); + bool hopo_possible = (tn.bHopoPossible); + bool use_addition_coloring = is_addition || hopo_possible; + const bool hold_ghost_showing = tn.HoldResult.bActive && tn.HoldResult.fLife > 0; + const bool is_holding = tn.HoldResult.bHeld; + if(hold_ghost_showing) + { + args.ghost_row.SetHoldShowing(args.column, tn); + } + + ASSERT_M(NoteRowToBeat(start_row) > -2000, ssprintf("%i %i %i", start_row, end_row, args.column)); + + bool in_selection_range = false; + if(args.selection_begin_marker != -1 && args.selection_end_marker != -1) + { + in_selection_range = (args.selection_begin_marker <= start_row && + end_row < args.selection_end_marker); + } + + DrawHold(tn, args.column, start_row, is_holding, result, + use_addition_coloring, + in_selection_range ? args.selection_glow : args.fail_fade, + m_fYReverseOffsetPixels, (float)args.draw_pixels_after_targets, + (float)args.draw_pixels_before_targets, + args.draw_pixels_before_targets, args.fade_before_targets); + + bool note_upcoming = NoteRowToBeat(start_row) > + m_pPlayerState->GetDisplayedPosition().m_fSongBeat; + any_upcoming |= note_upcoming; + } + return any_upcoming; +} + +bool NoteDisplay::DrawTapsInRange(NoteDisplayRenderArgs const& args, + vector const& tap_set) +{ + bool any_upcoming= false; + // draw notes from furthest to closest + for(vector::const_iterator tapit= + tap_set.begin(); tapit != tap_set.end(); ++tapit) + { + int tap_row= (*tapit)->first; + TapNote const& tn= (*tapit)->second; + + // TRICKY: If boomerang is on, then all notes in the range + // [first_row,last_row] aren't necessarily visible. + // Test every note to make sure it's on screen before drawing. + if(!IsOnScreen(NoteRowToBeat(tap_row), args.column, + args.draw_pixels_after_targets, args.draw_pixels_before_targets)) + { + continue; // skip + } + + // Hm, this assert used to pass the first and last rows to draw, when it + // was in NoteField, but those aren't available here. + // Well, anyone who has to investigate hitting it can use a debugger to + // discover the values, hopefully. -Kyz + ASSERT_M(NoteRowToBeat(tap_row) > -2000, + ssprintf("Invalid tap_row: %i, %f %f", + tap_row, + m_pPlayerState->GetDisplayedPosition().m_fSongBeat, + m_pPlayerState->GetDisplayedPosition().m_fMusicSeconds)); + + // See if there is a hold step that begins on this index. + // Only do this if the noteskin cares. + bool hold_begins_on_this_beat = false; + if(DrawHoldHeadForTapsOnSameRow()) + { + for(int c2= 0; c2 < args.note_data.GetNumTracks(); ++c2) + { + const TapNote &tmp = args.note_data.GetTapNote(c2, tap_row); + if(tmp.type == TapNoteType_HoldHead && + tmp.subType == TapNoteSubType_Hold) + { + hold_begins_on_this_beat = true; + break; + } + } + } + + // do the same for a roll. + bool roll_begins_on_this_beat = false; + if(DrawRollHeadForTapsOnSameRow()) + { + for(int c2= 0; c2 < args.note_data.GetNumTracks(); ++c2) + { + const TapNote &tmp = args.note_data.GetTapNote(c2, tap_row); + if(tmp.type == TapNoteType_HoldHead && + tmp.subType == TapNoteSubType_Roll) + { + roll_begins_on_this_beat = true; + break; + } + } + } + + bool in_selection_range = false; + if(args.selection_begin_marker != -1 && args.selection_end_marker != -1) + { + in_selection_range = args.selection_begin_marker <= tap_row && + tap_row < args.selection_end_marker; + } + + bool is_addition = (tn.source == TapNoteSource_Addition); + bool hopo_possible = (tn.bHopoPossible); + bool use_addition_coloring = is_addition || hopo_possible; + DrawTap(tn, args.column, NoteRowToVisibleBeat(m_pPlayerState, tap_row), + hold_begins_on_this_beat, roll_begins_on_this_beat, + use_addition_coloring, + in_selection_range ? args.selection_glow : args.fail_fade, + m_fYReverseOffsetPixels, args.draw_pixels_after_targets, + args.draw_pixels_before_targets, args.fade_before_targets); + + any_upcoming |= NoteRowToBeat(tap_row) > + m_pPlayerState->GetDisplayedPosition().m_fSongBeat; + + if(!PREFSMAN->m_FastNoteRendering) + { + DISPLAY->ClearZBuffer(); + } + } + return any_upcoming; +} + bool NoteDisplay::DrawHoldHeadForTapsOnSameRow() const { return cache->m_bDrawHoldHeadForTapsOnSameRow; diff --git a/src/NoteDisplay.h b/src/NoteDisplay.h index c0c88d8733..2bc3d0161b 100644 --- a/src/NoteDisplay.h +++ b/src/NoteDisplay.h @@ -1,6 +1,7 @@ #ifndef NOTE_DISPLAY_H #define NOTE_DISPLAY_H +#include "NoteData.h" #include "PlayerNumber.h" #include "GameInput.h" @@ -8,6 +9,7 @@ class Actor; class Sprite; class Model; class PlayerState; +class GhostArrowRow; struct TapNote; struct HoldNoteResult; struct NoteMetricCache_t; @@ -83,6 +85,30 @@ enum ActiveType #define FOREACH_ActiveType( i ) FOREACH_ENUM( ActiveType, i ) const RString &ActiveTypeToString( ActiveType at ); +// A little pod struct to carry the data the NoteField needs to pass to the +// NoteDisplay during rendering. +struct NoteDisplayRenderArgs +{ + NoteData const& note_data; + int column; + int draw_pixels_before_targets; + int draw_pixels_after_targets; + int selection_begin_marker; + int selection_end_marker; + float selection_glow; + float fail_fade; + float fade_before_targets; + GhostArrowRow& ghost_row; +NoteDisplayRenderArgs(NoteData const& nd, int dpbt, int dpat, int sbm, + int sem, float sg, float ff, float fbt, GhostArrowRow& gr) + :note_data(nd), column(0), + draw_pixels_before_targets(dpbt), draw_pixels_after_targets(dpat), + selection_begin_marker(sbm), selection_end_marker(sem), + selection_glow(sg), fail_fade(ff), fade_before_targets(fbt), ghost_row(gr) + {} +}; + + /** @brief Draws TapNotes and HoldNotes. */ class NoteDisplay { @@ -94,6 +120,12 @@ public: static void Update( float fDeltaTime ); + bool IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const; + + bool DrawHoldsInRange(NoteDisplayRenderArgs const& args, + vector const& tap_set); + bool DrawTapsInRange(NoteDisplayRenderArgs const& args, + vector const& tap_set); /** * @brief Draw the TapNote onto the NoteField. * @param tn the TapNote in question. diff --git a/src/NoteField.cpp b/src/NoteField.cpp index e3ed26873c..094dabfe4d 100644 --- a/src/NoteField.cpp +++ b/src/NoteField.cpp @@ -36,11 +36,8 @@ static ThemeMetric FADE_FAIL_TIME( "NoteField", "FadeFailTime" ); static RString RoutineNoteSkinName( size_t i ) { return ssprintf("RoutineNoteSkinP%i",int(i+1)); } static ThemeMetric1D ROUTINE_NOTESKIN( "NoteField", RoutineNoteSkinName, NUM_PLAYERS ); -static bool FAST_NOTE_RENDERING_PREF_CACHED= false; - NoteField::NoteField() { - FAST_NOTE_RENDERING_PREF_CACHED= PREFSMAN->m_FastNoteRendering; m_pNoteData = NULL; m_pCurDisplay = NULL; @@ -801,13 +798,11 @@ float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceB return fLastBeatToDraw; } -inline float NoteRowToVisibleBeat( const PlayerState *pPlayerState, int iRow ) -{ - return NoteRowToBeat(iRow); -} - bool NoteField::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const { + // IMPORTANT: Do not modify this function without also modifying the + // version that is in NoteDisplay.cpp or coming up with a good way to + // merge them. -Kyz // TRICKY: If boomerang is on, then ones in the range // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible. // Test to see if this beat is visible before drawing. @@ -1208,168 +1203,69 @@ void NoteField::DrawPrimitives() ssprintf("NumTracks %d != ColsPerPlayer %d",m_pNoteData->GetNumTracks(), GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer)); + NoteDisplayRenderArgs display_args(*m_pNoteData, + iDrawDistanceBeforeTargetsPixels, + iDrawDistanceAfterTargetsPixels, m_iBeginMarker, m_iEndMarker, + fSelectedRangeGlow, m_fPercentFadeToFail, FADE_BEFORE_TARGETS_PERCENT, + m_pCurDisplay->m_GhostArrowRow); for( int j=0; jGetNumTracks(); j++ ) // for each arrow column { const int c = pStyle->m_iColumnDrawOrder[j]; - - bool bAnyUpcomingInThisCol = false; - - // Draw all HoldNotes in this column (so that they appear under the tap notes) - { - NoteData::TrackMap::const_iterator begin, end; - m_pNoteData->GetTapNoteRangeInclusive( c, iFirstRowToDraw, iLastRowToDraw+1, begin, end ); - - for( ; begin != end; ++begin ) - { - const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, j); - if( tn.type != TapNoteType_HoldHead ) - continue; // skip - - const HoldNoteResult &Result = tn.HoldResult; - if( Result.hns == HNS_Held ) // if this HoldNote was completed - continue; // don't draw anything - - int iStartRow = begin->first; - int iEndRow = iStartRow + tn.iDuration; - - // TRICKY: If boomerang is on, then all notes in the range - // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible. - // Test every note to make sure it's on screen before drawing - float fThrowAway; - bool bStartIsPastPeak = false; - bool bEndIsPastPeak = false; - float fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iStartRow), fThrowAway, bStartIsPastPeak ); - float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iEndRow), fThrowAway, bEndIsPastPeak ); - - bool bTailIsOnVisible = iDrawDistanceAfterTargetsPixels <= fEndYOffset && fEndYOffset <= iDrawDistanceBeforeTargetsPixels; - bool bHeadIsVisible = iDrawDistanceAfterTargetsPixels <= fStartYOffset && fStartYOffset <= iDrawDistanceBeforeTargetsPixels; - bool bStraddlingVisible = fStartYOffset <= iDrawDistanceAfterTargetsPixels && iDrawDistanceBeforeTargetsPixels <= fEndYOffset; - bool bStaddlingPeak = bStartIsPastPeak && !bEndIsPastPeak; - if( !(bTailIsOnVisible || bHeadIsVisible || bStraddlingVisible || bStaddlingPeak) ) - { - //LOG->Trace( "skip drawing this hold." ); - continue; // skip - } - - bool bIsAddition = (tn.source == TapNoteSource_Addition); - bool bIsHopoPossible = (tn.bHopoPossible); - bool bUseAdditionColoring = bIsAddition || bIsHopoPossible; - const bool bHoldGhostShowing = tn.HoldResult.bActive && tn.HoldResult.fLife > 0; - const bool bIsHoldingNote = tn.HoldResult.bHeld; - if( bHoldGhostShowing ) - m_pCurDisplay->m_GhostArrowRow.SetHoldShowing( c, tn ); - - ASSERT_M( NoteRowToBeat(iStartRow) > -2000, ssprintf("%i %i %i", iStartRow, iEndRow, c) ); - - bool bIsInSelectionRange = false; - if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 ) - bIsInSelectionRange = (m_iBeginMarker <= iStartRow && iEndRow < m_iEndMarker); - - NoteDisplayCols *displayCols = tn.pn == PLAYER_INVALID ? m_pCurDisplay : m_pDisplays[tn.pn]; - displayCols->display[c].DrawHold( tn, c, iStartRow, bIsHoldingNote, Result, bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, - m_fYReverseOffsetPixels, (float) iDrawDistanceAfterTargetsPixels, (float) iDrawDistanceBeforeTargetsPixels, iDrawDistanceBeforeTargetsPixels, FADE_BEFORE_TARGETS_PERCENT ); - - bool bNoteIsUpcoming = NoteRowToBeat(iStartRow) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat; - bAnyUpcomingInThisCol |= bNoteIsUpcoming; - } - } - - // Draw all TapNotes in this column - - // draw notes from furthest to closest + display_args.column= c; + bool any_upcoming= false; + // Build lists of holds and taps for each player number, then pass those + // lists to the displays to draw. + // The vector in the NUM_PlayerNumber slot should stay empty, not worth + // optimizing it out. -Kyz + vector > holds(PLAYER_INVALID+1); + vector > taps(PLAYER_INVALID+1); NoteData::TrackMap::const_iterator begin, end; - m_pNoteData->GetTapNoteRange( c, iFirstRowToDraw, iLastRowToDraw+1, begin, end ); - for( ; begin != end; ++begin ) + m_pNoteData->GetTapNoteRangeInclusive(c, iFirstRowToDraw, + iLastRowToDraw+1, begin, end); + for(; begin != end; ++begin) { - int q = begin->first; - const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, q); - - // Switch modified by Wolfman2000, tested by Saturn2888 - // Fixes hold head overlapping issue, but not the rolls. - switch( tn.type ) + TapNote const& tn= begin->second; + switch(tn.type) { - case TapNoteType_Empty: // no note here - { + case TapNoteType_Empty: continue; - } + case TapNoteType_Tap: + case TapNoteType_HoldTail: + case TapNoteType_Mine: + case TapNoteType_Lift: + case TapNoteType_Attack: + case TapNoteType_AutoKeysound: + case TapNoteType_Fake: + if(!tn.result.bHidden) + { + taps[tn.pn].push_back(begin); + } + break; case TapNoteType_HoldHead: - { - //if (tn.subType == TapNoteSubType_Roll) - continue; // skip - } - default: break; - } - - // Don't draw hidden (fully judged) steps. - if( tn.result.bHidden ) - continue; - - // TRICKY: If boomerang is on, then all notes in the range - // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible. - // Test every note to make sure it's on screen before drawing. - if( !IsOnScreen( NoteRowToBeat(q), c, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) ) - continue; // skip - - ASSERT_M( NoteRowToBeat(q) > -2000, ssprintf("%i %i %i, %f %f", q, iLastRowToDraw, - iFirstRowToDraw, m_pPlayerState->GetDisplayedPosition().m_fSongBeat, m_pPlayerState->GetDisplayedPosition().m_fMusicSeconds) ); - - // See if there is a hold step that begins on this index. - // Only do this if the noteskin cares. - bool bHoldNoteBeginsOnThisBeat = false; - if( m_pCurDisplay->display[c].DrawHoldHeadForTapsOnSameRow() ) - { - for( int c2=0; c2GetNumTracks(); c2++ ) - { - const TapNote &tmp = m_pNoteData->GetTapNote(c2, q); - if(tmp.type == TapNoteType_HoldHead && - tmp.subType == TapNoteSubType_Hold) + if(tn.HoldResult.hns != HNS_Held) { - bHoldNoteBeginsOnThisBeat = true; - break; + holds[tn.pn].push_back(begin); } - } - } - - // do the same for a roll. - bool bRollNoteBeginsOnThisBeat = false; - if (m_pCurDisplay->display[c].DrawRollHeadForTapsOnSameRow() ) - { - for( int c2=0; c2GetNumTracks(); c2++ ) - { - const TapNote &tmp = m_pNoteData->GetTapNote(c2, q); - if(tmp.type == TapNoteType_HoldHead && - tmp.subType == TapNoteSubType_Roll) - { - bRollNoteBeginsOnThisBeat = true; - break; - } - } - } - - bool bIsInSelectionRange = false; - if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 ) - bIsInSelectionRange = m_iBeginMarker<=q && qdisplay[c].DrawTap(tn, c, NoteRowToVisibleBeat(m_pPlayerState, q), - bHoldNoteBeginsOnThisBeat, bRollNoteBeginsOnThisBeat, - bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, - m_fYReverseOffsetPixels, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels, - FADE_BEFORE_TARGETS_PERCENT ); - - bool bNoteIsUpcoming = NoteRowToBeat(q) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat; - bAnyUpcomingInThisCol |= bNoteIsUpcoming; - - if(!FAST_NOTE_RENDERING_PREF_CACHED) - { - DISPLAY->ClearZBuffer(); + break; } } - - cur->m_ReceptorArrowRow.SetNoteUpcoming( c, bAnyUpcomingInThisCol ); +#define DTS_INNER(pn, tap_set, draw_func, disp) \ + if(!tap_set[pn].empty()) \ + { \ + any_upcoming|= disp->display[c].draw_func(display_args, tap_set[pn]); \ + } +#define DRAW_TAP_SET(tap_set, draw_func) \ + FOREACH_PlayerNumber(pn) \ + { \ + DTS_INNER(pn, tap_set, draw_func, m_pDisplays[pn]); \ + } + DRAW_TAP_SET(holds, DrawHoldsInRange); + DTS_INNER(PLAYER_INVALID, holds, DrawHoldsInRange, m_pCurDisplay); + DRAW_TAP_SET(taps, DrawTapsInRange); + DTS_INNER(PLAYER_INVALID, taps, DrawTapsInRange, m_pCurDisplay); +#undef DTS_INNER +#undef DRAW_TAP_SET + cur->m_ReceptorArrowRow.SetNoteUpcoming(c, any_upcoming); } cur->m_GhostArrowRow.Draw();