From 07fb61a29191340aa70f7005f9645403786e9b7f Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Thu, 25 Dec 2014 05:02:28 -0700 Subject: [PATCH 01/14] 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(); From 64d384410c06c2b3f6289acbf2279774d29611df Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Fri, 26 Dec 2014 01:54:34 -0700 Subject: [PATCH 02/14] Created NoteColumnRenderer to handle column rendering. Added m_FakeParent to Actor to solve part of the problem. --- src/Actor.cpp | 38 +++++++- src/Actor.h | 9 ++ src/GhostArrowRow.cpp | 9 ++ src/GhostArrowRow.h | 2 + src/NoteDisplay.cpp | 101 ++++++++++++++++----- src/NoteDisplay.h | 48 ++++++---- src/NoteField.cpp | 191 ++++++++++++++++++--------------------- src/NoteField.h | 10 +- src/ReceptorArrowRow.cpp | 9 ++ src/ReceptorArrowRow.h | 2 + 10 files changed, 275 insertions(+), 144 deletions(-) diff --git a/src/Actor.cpp b/src/Actor.cpp index 402899c411..318e06e8ab 100644 --- a/src/Actor.cpp +++ b/src/Actor.cpp @@ -164,6 +164,7 @@ Actor::Actor() m_size = RageVector2( 1, 1 ); InitState(); m_pParent = NULL; + m_FakeParent= NULL; m_bFirstUpdate = true; } @@ -183,6 +184,7 @@ Actor::Actor( const Actor &cpy ): #define CPY(x) x = cpy.x CPY( m_sName ); CPY( m_pParent ); + CPY( m_FakeParent ); CPY( m_pLuaInstance ); CPY( m_baseRotation ); @@ -279,24 +281,56 @@ void Actor::LoadFromNode( const XNode* pNode ) PlayCommandNoRecurse( Message("Init") ); } +bool Actor::PartiallyOpaque() +{ + return m_pTempState->diffuse[0].a > 0 || m_pTempState->diffuse[1].a > 0 || + m_pTempState->diffuse[2].a > 0 || m_pTempState->diffuse[3].a > 0 || + m_pTempState->glow.a > 0; +} + void Actor::Draw() { if( !m_bVisible || m_fHibernateSecondsLeft > 0 || this->EarlyAbortDraw() ) + { return; // early abort + } + bool fake_parent_partially_opaque= true; + if(m_FakeParent) + { + if(!m_FakeParent->m_bVisible || m_FakeParent->m_fHibernateSecondsLeft > 0 + || m_FakeParent->EarlyAbortDraw()) + { + return; + } + m_FakeParent->PreDraw(); + fake_parent_partially_opaque= m_FakeParent->PartiallyOpaque(); + } this->PreDraw(); ASSERT( m_pTempState != NULL ); - if( m_pTempState->diffuse[0].a > 0 || m_pTempState->diffuse[1].a > 0 || m_pTempState->diffuse[2].a > 0 || m_pTempState->diffuse[3].a > 0 || m_pTempState->glow.a > 0 ) // This Actor is not fully transparent - { + if(PartiallyOpaque() && fake_parent_partially_opaque) + { + if(m_FakeParent) + { + m_FakeParent->BeginDraw(); + } // call the most-derived versions this->BeginDraw(); this->DrawPrimitives(); // call the most-derived version of DrawPrimitives(); this->EndDraw(); + if(m_FakeParent) + { + m_FakeParent->EndDraw(); + } } this->PostDraw(); + if(m_FakeParent) + { + m_FakeParent->PostDraw(); + } m_pTempState = NULL; } diff --git a/src/Actor.h b/src/Actor.h index 2be5f711bd..781533987d 100644 --- a/src/Actor.h +++ b/src/Actor.h @@ -232,6 +232,8 @@ public: float aux; }; + // PartiallyOpaque broken out of Draw for reuse and clarity. + bool PartiallyOpaque(); /** * @brief Calls multiple functions for drawing the Actors. * @@ -305,6 +307,9 @@ public: * @return the Actor's lineage. */ RString GetLineage() const; + void SetFakeParent(Actor* mailman) { m_FakeParent= mailman; } + Actor* GetFakeParent() { return m_FakeParent; } + /** * @brief Retrieve the Actor's x position. * @return the Actor's x position. */ @@ -607,6 +612,10 @@ protected: RString m_sName; /** @brief the current parent of this Actor if it exists. */ Actor *m_pParent; + // m_FakeParent exists to provide a way to render the actor inside another's + // state without making that actor the parent. It's like having multiple + // parents. -Kyz + Actor* m_FakeParent; /** @brief Some general information about the Tween. */ struct TweenInfo diff --git a/src/GhostArrowRow.cpp b/src/GhostArrowRow.cpp index fef07474e7..2afe66e1e0 100644 --- a/src/GhostArrowRow.cpp +++ b/src/GhostArrowRow.cpp @@ -35,6 +35,15 @@ void GhostArrowRow::Load( const PlayerState* pPlayerState, float fYReverseOffset } } +void GhostArrowRow::SetColumnRenderers(vector& renderers) +{ + ASSERT_M(renderers.size() == m_Ghost.size(), "Notefield has different number of columns than ghost row."); + for(size_t c= 0; c < m_Ghost.size(); ++c) + { + m_Ghost[c]->SetFakeParent(&(renderers[c])); + } +} + GhostArrowRow::~GhostArrowRow() { for( unsigned i = 0; i < m_Ghost.size(); ++i ) diff --git a/src/GhostArrowRow.h b/src/GhostArrowRow.h index b1647d3aa9..c091b04a81 100644 --- a/src/GhostArrowRow.h +++ b/src/GhostArrowRow.h @@ -4,6 +4,7 @@ #include "ActorFrame.h" #include "GameConstantsAndTypes.h" #include "NoteTypes.h" +#include "NoteDisplay.h" class PlayerState; /** @brief Row of GhostArrow Actors. */ @@ -15,6 +16,7 @@ public: virtual void DrawPrimitives(); void Load( const PlayerState* pPlayerState, float fYReverseOffset ); + void SetColumnRenderers(vector& renderers); void DidTapNote( int iCol, TapNoteScore tns, bool bBright ); void DidHoldNote( int iCol, HoldNoteScore hns, bool bBright ); diff --git a/src/NoteDisplay.cpp b/src/NoteDisplay.cpp index fca04b8b32..a9fe88813c 100644 --- a/src/NoteDisplay.cpp +++ b/src/NoteDisplay.cpp @@ -7,6 +7,7 @@ #include "ArrowEffects.h" #include "RageLog.h" #include "RageDisplay.h" +#include "ReceptorArrowRow.h" #include "ActorUtil.h" #include "Style.h" #include "PlayerState.h" @@ -341,8 +342,8 @@ bool NoteDisplay::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTarge return true; } -bool NoteDisplay::DrawHoldsInRange(NoteDisplayRenderArgs const& args, - vector const& tap_set) +bool NoteDisplay::DrawHoldsInRange(CommonColumnRenderArgs const& args, + int column, vector const& tap_set) { bool any_upcoming = false; for(vector::const_iterator tapit= @@ -359,10 +360,10 @@ bool NoteDisplay::DrawHoldsInRange(NoteDisplayRenderArgs const& args, float throw_away; bool start_past_peak = false; bool end_past_peak = false; - float start_y = ArrowEffects::GetYOffset(m_pPlayerState, args.column, + float start_y = ArrowEffects::GetYOffset(m_pPlayerState, column, NoteRowToVisibleBeat(m_pPlayerState, start_row), throw_away, start_past_peak); - float end_y = ArrowEffects::GetYOffset(m_pPlayerState, args.column, + float end_y = ArrowEffects::GetYOffset(m_pPlayerState, column, NoteRowToVisibleBeat(m_pPlayerState, end_row), throw_away, end_past_peak); bool tail_visible = args.draw_pixels_after_targets <= end_y && @@ -385,19 +386,19 @@ bool NoteDisplay::DrawHoldsInRange(NoteDisplayRenderArgs const& args, const bool is_holding = tn.HoldResult.bHeld; if(hold_ghost_showing) { - args.ghost_row.SetHoldShowing(args.column, tn); + args.ghost_row->SetHoldShowing(column, tn); } - ASSERT_M(NoteRowToBeat(start_row) > -2000, ssprintf("%i %i %i", start_row, end_row, args.column)); + ASSERT_M(NoteRowToBeat(start_row) > -2000, ssprintf("%i %i %i", start_row, end_row, column)); bool in_selection_range = false; - if(args.selection_begin_marker != -1 && args.selection_end_marker != -1) + 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); + in_selection_range = (*args.selection_begin_marker <= start_row && + end_row < *args.selection_end_marker); } - DrawHold(tn, args.column, start_row, is_holding, result, + DrawHold(tn, 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, @@ -411,8 +412,8 @@ bool NoteDisplay::DrawHoldsInRange(NoteDisplayRenderArgs const& args, return any_upcoming; } -bool NoteDisplay::DrawTapsInRange(NoteDisplayRenderArgs const& args, - vector const& tap_set) +bool NoteDisplay::DrawTapsInRange(CommonColumnRenderArgs const& args, + int column, vector const& tap_set) { bool any_upcoming= false; // draw notes from furthest to closest @@ -425,7 +426,7 @@ bool NoteDisplay::DrawTapsInRange(NoteDisplayRenderArgs const& args, // 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, + if(!IsOnScreen(NoteRowToBeat(tap_row), column, args.draw_pixels_after_targets, args.draw_pixels_before_targets)) { continue; // skip @@ -446,9 +447,9 @@ bool NoteDisplay::DrawTapsInRange(NoteDisplayRenderArgs const& args, bool hold_begins_on_this_beat = false; if(DrawHoldHeadForTapsOnSameRow()) { - for(int c2= 0; c2 < args.note_data.GetNumTracks(); ++c2) + for(int c2= 0; c2 < args.note_data->GetNumTracks(); ++c2) { - const TapNote &tmp = args.note_data.GetTapNote(c2, tap_row); + const TapNote &tmp = args.note_data->GetTapNote(c2, tap_row); if(tmp.type == TapNoteType_HoldHead && tmp.subType == TapNoteSubType_Hold) { @@ -462,9 +463,9 @@ bool NoteDisplay::DrawTapsInRange(NoteDisplayRenderArgs const& args, bool roll_begins_on_this_beat = false; if(DrawRollHeadForTapsOnSameRow()) { - for(int c2= 0; c2 < args.note_data.GetNumTracks(); ++c2) + for(int c2= 0; c2 < args.note_data->GetNumTracks(); ++c2) { - const TapNote &tmp = args.note_data.GetTapNote(c2, tap_row); + const TapNote &tmp = args.note_data->GetTapNote(c2, tap_row); if(tmp.type == TapNoteType_HoldHead && tmp.subType == TapNoteSubType_Roll) { @@ -475,16 +476,16 @@ bool NoteDisplay::DrawTapsInRange(NoteDisplayRenderArgs const& args, } bool in_selection_range = false; - if(args.selection_begin_marker != -1 && args.selection_end_marker != -1) + 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; + 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), + DrawTap(tn, 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, @@ -1064,6 +1065,64 @@ void NoteDisplay::DrawTap(const TapNote& tn, int iCol, float fBeat, pActor->PlayCommand( "UnsetAttack" ); } +void NoteColumnRenderer::DrawPrimitives() +{ + 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_render_args->note_data->GetTapNoteRangeInclusive(m_column, + m_render_args->first_row, m_render_args->last_row+1, begin, end); + for(; begin != end; ++begin) + { + TapNote const& tn= begin->second; + switch(tn.type) + { + 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.HoldResult.hns != HNS_Held) + { + holds[tn.pn].push_back(begin); + } + break; + } + } +#define DTS_INNER(pn, tap_set, draw_func, disp) \ + if(!tap_set[pn].empty()) \ + { \ + any_upcoming|= disp->draw_func(*m_render_args, m_column, tap_set[pn]); \ + } +#define DRAW_TAP_SET(tap_set, draw_func) \ + FOREACH_PlayerNumber(pn) \ + { \ + DTS_INNER(pn, tap_set, draw_func, m_displays[pn]); \ + } + DRAW_TAP_SET(holds, DrawHoldsInRange); + DTS_INNER(PLAYER_INVALID, holds, DrawHoldsInRange, m_displays[PLAYER_INVALID]); + DRAW_TAP_SET(taps, DrawTapsInRange); + DTS_INNER(PLAYER_INVALID, taps, DrawTapsInRange, m_displays[PLAYER_INVALID]); +#undef DTS_INNER +#undef DRAW_TAP_SET + m_render_args->receptor_row->SetNoteUpcoming(m_column, any_upcoming); +} + /* * (c) 2001-2006 Brian Bugh, Ben Nordstrom, Chris Danford, Steve Checkoway * All rights reserved. diff --git a/src/NoteDisplay.h b/src/NoteDisplay.h index 2bc3d0161b..3487be8d6c 100644 --- a/src/NoteDisplay.h +++ b/src/NoteDisplay.h @@ -1,15 +1,16 @@ #ifndef NOTE_DISPLAY_H #define NOTE_DISPLAY_H +#include "ActorFrame.h" #include "NoteData.h" #include "PlayerNumber.h" #include "GameInput.h" -class Actor; class Sprite; class Model; class PlayerState; class GhostArrowRow; +class ReceptorArrowRow; struct TapNote; struct HoldNoteResult; struct NoteMetricCache_t; @@ -87,28 +88,22 @@ 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 +struct CommonColumnRenderArgs { - NoteData const& note_data; - int column; + ReceptorArrowRow* receptor_row; + GhostArrowRow* ghost_row; + NoteData const* note_data; + int first_row; + int last_row; int draw_pixels_before_targets; int draw_pixels_after_targets; - int selection_begin_marker; - int selection_end_marker; + 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 { @@ -122,9 +117,9 @@ public: bool IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const; - bool DrawHoldsInRange(NoteDisplayRenderArgs const& args, + bool DrawHoldsInRange(CommonColumnRenderArgs const& args, int column, vector const& tap_set); - bool DrawTapsInRange(NoteDisplayRenderArgs const& args, + bool DrawTapsInRange(CommonColumnRenderArgs const& args, int column, vector const& tap_set); /** * @brief Draw the TapNote onto the NoteField. @@ -184,6 +179,25 @@ private: float m_fYReverseOffsetPixels; }; +// So, this is a bit screwy, and it's all because routine forces rendering +// notes from different noteskins in the same column. +// NoteColumnRenderer exists to hold all the data needed for rendering a +// column and apply any transforms from that column's actor to the +// NoteDisplays that render the notes. +// NoteColumnRenderer is also used as a fake parent for the receptor and ghost +// actors so they can move with the rest of the column. I didn't use +// ActorProxy because the receptor/ghost actors need to pull in the parent +// state of their rows and the parent state of the column. -Kyz +class NoteColumnRenderer : public ActorFrame +{ + public: + NoteDisplay* m_displays[PLAYER_INVALID+1]; + CommonColumnRenderArgs* m_render_args; + int m_column; + + virtual void DrawPrimitives(); +}; + #endif /** diff --git a/src/NoteField.cpp b/src/NoteField.cpp index 094dabfe4d..1ff8fc1cf7 100644 --- a/src/NoteField.cpp +++ b/src/NoteField.cpp @@ -61,9 +61,13 @@ NoteField::NoteField() m_sprBeatBars.Load( THEME->GetPathG("NoteField","bars") ); m_sprBeatBars.StopAnimating(); + // I decided to do it this way because I don't want to dig through + // ScreenEdit to change all the places it touches the markers. -Kyz + m_ColumnRenderArgs.selection_begin_marker= &m_iBeginMarker; + m_ColumnRenderArgs.selection_end_marker= &m_iEndMarker; m_iBeginMarker = m_iEndMarker = -1; - m_fPercentFadeToFail = -1; + m_ColumnRenderArgs.fail_fade = -1; m_StepCallback.SetFromNil(); m_SetPressedCallback.SetFromNil(); @@ -166,6 +170,8 @@ void NoteField::CacheAllUsedNoteSkins() ASSERT_M( it != m_NoteDisplays.end(), sNoteSkinLower ); m_pDisplays[pn] = it->second; } + + InitColumnRenderers(); } void NoteField::Init( const PlayerState* pPlayerState, float fYReverseOffsetPixels ) @@ -186,7 +192,7 @@ void NoteField::Load( m_iDrawDistanceBeforeTargetsPixels = iDrawDistanceBeforeTargetsPixels; ASSERT( m_iDrawDistanceBeforeTargetsPixels >= m_iDrawDistanceAfterTargetsPixels ); - m_fPercentFadeToFail = -1; + m_ColumnRenderArgs.fail_fade = -1; //int i1 = m_pNoteData->GetNumTracks(); //int i2 = GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; @@ -237,6 +243,27 @@ void NoteField::Load( ASSERT_M( it != m_NoteDisplays.end(), sNoteSkinLower ); m_pDisplays[pn] = it->second; } + InitColumnRenderers(); +} + +void NoteField::InitColumnRenderers() +{ + m_ColumnRenderArgs.receptor_row= &(m_pCurDisplay->m_ReceptorArrowRow); + m_ColumnRenderArgs.ghost_row= &(m_pCurDisplay->m_GhostArrowRow); + m_ColumnRenderArgs.note_data= m_pNoteData; + m_ColumnRenderers.resize(GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer); + for(size_t ncr= 0; ncr < m_ColumnRenderers.size(); ++ncr) + { + FOREACH_EnabledPlayer(pn) + { + m_ColumnRenderers[ncr].m_displays[pn]= &(m_pDisplays[pn]->display[ncr]); + } + m_ColumnRenderers[ncr].m_displays[PLAYER_INVALID]= &(m_pCurDisplay->display[ncr]); + m_ColumnRenderers[ncr].m_column= ncr; + m_ColumnRenderers[ncr].m_render_args= &m_ColumnRenderArgs; + } + m_pCurDisplay->m_ReceptorArrowRow.SetColumnRenderers(m_ColumnRenderers); + m_pCurDisplay->m_GhostArrowRow.SetColumnRenderers(m_ColumnRenderers); } void NoteField::Update( float fDeltaTime ) @@ -248,6 +275,11 @@ void NoteField::Update( float fDeltaTime ) ActorFrame::Update( fDeltaTime ); + for(size_t c= 0; c < m_ColumnRenderers.size(); ++c) + { + m_ColumnRenderers[c].Update(fDeltaTime); + } + // update m_fBoardOffsetPixels, m_fCurrentBeatLastUpdate, m_fYPosCurrentBeatLastUpdate const float fCurrentBeat = m_pPlayerState->GetDisplayedPosition().m_fSongBeat; bool bTweeningOn = m_sprBoard->GetCurrentDiffuseAlpha() >= 0.98 && m_sprBoard->GetCurrentDiffuseAlpha() < 1.00; // HACK @@ -273,11 +305,11 @@ void NoteField::Update( float fDeltaTime ) cur->m_ReceptorArrowRow.Update( fDeltaTime ); cur->m_GhostArrowRow.Update( fDeltaTime ); - if( m_fPercentFadeToFail >= 0 ) - m_fPercentFadeToFail = min( m_fPercentFadeToFail + fDeltaTime/FADE_FAIL_TIME, 1 ); + if( m_ColumnRenderArgs.fail_fade >= 0 ) + m_ColumnRenderArgs.fail_fade = min( m_ColumnRenderArgs.fail_fade + fDeltaTime/FADE_FAIL_TIME, 1 ); // Update fade to failed - m_pCurDisplay->m_ReceptorArrowRow.SetFadeToFailPercent( m_fPercentFadeToFail ); + m_pCurDisplay->m_ReceptorArrowRow.SetFadeToFailPercent( m_ColumnRenderArgs.fail_fade ); NoteDisplay::Update( fDeltaTime ); /* Update all NoteDisplays. Hack: We need to call this once per frame, not @@ -829,41 +861,41 @@ void NoteField::DrawPrimitives() const PlayerOptions ¤t_po = m_pPlayerState->m_PlayerOptions.GetCurrent(); // Adjust draw range depending on some effects - int iDrawDistanceAfterTargetsPixels = m_iDrawDistanceAfterTargetsPixels; + m_ColumnRenderArgs.draw_pixels_after_targets= m_iDrawDistanceAfterTargetsPixels; // HACK: If boomerang and centered are on, then we want to draw much // earlier so that the notes don't pop on screen. float fCenteredTimesBoomerang = current_po.m_fScrolls[PlayerOptions::SCROLL_CENTERED] * current_po.m_fAccels[PlayerOptions::ACCEL_BOOMERANG]; - iDrawDistanceAfterTargetsPixels += int(SCALE( fCenteredTimesBoomerang, 0.f, 1.f, 0.f, -SCREEN_HEIGHT/2 )); - int iDrawDistanceBeforeTargetsPixels = m_iDrawDistanceBeforeTargetsPixels; + m_ColumnRenderArgs.draw_pixels_after_targets += int(SCALE( fCenteredTimesBoomerang, 0.f, 1.f, 0.f, -SCREEN_HEIGHT/2 )); + m_ColumnRenderArgs.draw_pixels_before_targets = m_iDrawDistanceBeforeTargetsPixels; float fDrawScale = 1; fDrawScale *= 1 + 0.5f * fabsf( current_po.m_fPerspectiveTilt ); fDrawScale *= 1 + fabsf( current_po.m_fEffects[PlayerOptions::EFFECT_MINI] ); - iDrawDistanceAfterTargetsPixels = (int)(iDrawDistanceAfterTargetsPixels * fDrawScale); - iDrawDistanceBeforeTargetsPixels = (int)(iDrawDistanceBeforeTargetsPixels * fDrawScale); + m_ColumnRenderArgs.draw_pixels_after_targets = (int)(m_ColumnRenderArgs.draw_pixels_after_targets * fDrawScale); + m_ColumnRenderArgs.draw_pixels_before_targets = (int)(m_ColumnRenderArgs.draw_pixels_before_targets * fDrawScale); // Probe for first and last notes on the screen - float fFirstBeatToDraw = FindFirstDisplayedBeat( m_pPlayerState, iDrawDistanceAfterTargetsPixels ); - float fLastBeatToDraw = FindLastDisplayedBeat( m_pPlayerState, iDrawDistanceBeforeTargetsPixels ); + float fFirstBeatToDraw = FindFirstDisplayedBeat( m_pPlayerState, m_ColumnRenderArgs.draw_pixels_after_targets ); + float fLastBeatToDraw = FindLastDisplayedBeat( m_pPlayerState, m_ColumnRenderArgs.draw_pixels_before_targets ); m_pPlayerState->m_fLastDrawnBeat = fLastBeatToDraw; - const int iFirstRowToDraw = BeatToNoteRow(fFirstBeatToDraw); - const int iLastRowToDraw = BeatToNoteRow(fLastBeatToDraw); + m_ColumnRenderArgs.first_row = BeatToNoteRow(fFirstBeatToDraw); + m_ColumnRenderArgs.last_row = BeatToNoteRow(fLastBeatToDraw); //LOG->Trace( "start = %f.1, end = %f.1", fFirstBeatToDraw-fSongBeat, fLastBeatToDraw-fSongBeat ); - //LOG->Trace( "Drawing elements %d through %d", iFirstRowToDraw, iLastRowToDraw ); + //LOG->Trace( "Drawing elements %d through %d", m_ColumnRenderArgs.first_row, m_ColumnRenderArgs.last_row ); -#define IS_ON_SCREEN( fBeat ) ( fFirstBeatToDraw <= (fBeat) && (fBeat) <= fLastBeatToDraw && IsOnScreen( fBeat, 0, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) ) +#define IS_ON_SCREEN( fBeat ) ( fFirstBeatToDraw <= (fBeat) && (fBeat) <= fLastBeatToDraw && IsOnScreen( fBeat, 0, m_ColumnRenderArgs.draw_pixels_after_targets, m_ColumnRenderArgs.draw_pixels_before_targets ) ) // Draw board if( SHOW_BOARD ) { - DrawBoard( iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ); + DrawBoard( m_ColumnRenderArgs.draw_pixels_after_targets, m_ColumnRenderArgs.draw_pixels_before_targets ); } // Draw Receptors @@ -886,7 +918,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < tSigs.size(); i++) { const TimeSignatureSegment *ts = ToTimeSignature(tSigs[i]); - int iSegmentEndRow = (i + 1 == tSigs.size()) ? iLastRowToDraw : tSigs[i+1]->GetRow(); + int iSegmentEndRow = (i + 1 == tSigs.size()) ? m_ColumnRenderArgs.last_row : tSigs[i+1]->GetRow(); // beat bars every 16th note int iDrawBeatBarsEveryRows = BeatToNoteRow( ((float)ts->GetDen()) / 4 ) / 4; @@ -929,7 +961,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_SCROLL]->size(); i++) { ScrollSegment *seg = ToScroll( segs[SEGMENT_SCROLL]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -941,7 +973,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_BPM]->size(); i++) { const BPMSegment *seg = ToBPM( segs[SEGMENT_BPM]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -953,7 +985,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_STOP]->size(); i++) { const StopSegment *seg = ToStop( segs[SEGMENT_STOP]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -965,7 +997,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_DELAY]->size(); i++) { const DelaySegment *seg = ToDelay( segs[SEGMENT_DELAY]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -977,7 +1009,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_WARP]->size(); i++) { const WarpSegment *seg = ToWarp( segs[SEGMENT_WARP]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -989,7 +1021,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_TIME_SIG]->size(); i++) { const TimeSignatureSegment *seg = ToTimeSignature( segs[SEGMENT_TIME_SIG]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1001,7 +1033,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_TICKCOUNT]->size(); i++) { const TickcountSegment *seg = ToTickcount( segs[SEGMENT_TICKCOUNT]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1013,7 +1045,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_COMBO]->size(); i++) { const ComboSegment *seg = ToCombo( segs[SEGMENT_COMBO]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1025,7 +1057,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_LABEL]->size(); i++) { const LabelSegment *seg = ToLabel( segs[SEGMENT_LABEL]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1037,7 +1069,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_SPEED]->size(); i++) { const SpeedSegment *seg = ToSpeed( segs[SEGMENT_SPEED]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1050,7 +1082,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_FAKE]->size(); i++) { const FakeSegment *seg = ToFake( segs[SEGMENT_FAKE]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1070,8 +1102,8 @@ void NoteField::DrawPrimitives() float fSecond = a->fStartSecond; float fBeat = timing.GetBeatFromElapsedTime( fSecond ); - if( BeatToNoteRow(fBeat) >= iFirstRowToDraw && - BeatToNoteRow(fBeat) <= iLastRowToDraw) + if( BeatToNoteRow(fBeat) >= m_ColumnRenderArgs.first_row && + BeatToNoteRow(fBeat) <= m_ColumnRenderArgs.last_row) { if( IS_ON_SCREEN(fBeat) ) DrawAttackText( fBeat, *a ); @@ -1089,8 +1121,8 @@ void NoteField::DrawPrimitives() FOREACH_CONST(Attack, attacks, a) { float fBeat = timing.GetBeatFromElapsedTime(a->fStartSecond); - if (BeatToNoteRow(fBeat) >= iFirstRowToDraw && - BeatToNoteRow(fBeat) <= iLastRowToDraw && + if (BeatToNoteRow(fBeat) >= m_ColumnRenderArgs.first_row && + BeatToNoteRow(fBeat) <= m_ColumnRenderArgs.last_row && IS_ON_SCREEN(fBeat)) { this->DrawAttackText(fBeat, *a); @@ -1174,20 +1206,20 @@ void NoteField::DrawPrimitives() { int iBegin = m_iBeginMarker; int iEnd = m_iEndMarker; - CLAMP( iBegin, iFirstRowToDraw, iLastRowToDraw ); - CLAMP( iEnd, iFirstRowToDraw, iLastRowToDraw ); + CLAMP( iBegin, m_ColumnRenderArgs.first_row, m_ColumnRenderArgs.last_row ); + CLAMP( iEnd, m_ColumnRenderArgs.first_row, m_ColumnRenderArgs.last_row ); DrawAreaHighlight( iBegin, iEnd ); } else if( m_iBeginMarker != -1 ) { - if( m_iBeginMarker >= iFirstRowToDraw && - m_iBeginMarker <= iLastRowToDraw ) + if( m_iBeginMarker >= m_ColumnRenderArgs.first_row && + m_iBeginMarker <= m_ColumnRenderArgs.last_row ) DrawMarkerBar( m_iBeginMarker ); } else if( m_iEndMarker != -1 ) { - if( m_iEndMarker >= iFirstRowToDraw && - m_iEndMarker <= iLastRowToDraw ) + if( m_iEndMarker >= m_ColumnRenderArgs.first_row && + m_iEndMarker <= m_ColumnRenderArgs.last_row ) DrawMarkerBar( m_iEndMarker ); } } @@ -1196,76 +1228,19 @@ void NoteField::DrawPrimitives() // Draw the arrows in order of column. This minimizes texture switches and // lets us draw in big batches. - float fSelectedRangeGlow = SCALE( RageFastCos(RageTimer::GetTimeSinceStartFast()*2), -1, 1, 0.1f, 0.3f ); - const Style* pStyle = GAMESTATE->GetCurrentStyle(); ASSERT_M(m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer, 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); + m_ColumnRenderArgs.selection_glow= SCALE( + RageFastCos(RageTimer::GetTimeSinceStartFast()*2), -1, 1, 0.1f, 0.3f); + m_ColumnRenderArgs.fade_before_targets= FADE_BEFORE_TARGETS_PERCENT; + for( int j=0; jGetNumTracks(); j++ ) // for each arrow column { const int c = pStyle->m_iColumnDrawOrder[j]; - 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->GetTapNoteRangeInclusive(c, iFirstRowToDraw, - iLastRowToDraw+1, begin, end); - for(; begin != end; ++begin) - { - TapNote const& tn= begin->second; - switch(tn.type) - { - 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.HoldResult.hns != HNS_Held) - { - holds[tn.pn].push_back(begin); - } - break; - } - } -#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); + m_ColumnRenderers[c].Draw(); } cur->m_GhostArrowRow.Draw(); @@ -1273,7 +1248,7 @@ void NoteField::DrawPrimitives() void NoteField::FadeToFail() { - m_fPercentFadeToFail = max( 0.0f, m_fPercentFadeToFail ); // this will slowly increase every Update() + m_ColumnRenderArgs.fail_fade = max( 0.0f, m_ColumnRenderArgs.fail_fade ); // this will slowly increase every Update() // don't fade all over again if this is called twice } @@ -1471,6 +1446,17 @@ public: return 0; } + static int GetColumnActors(T* p, lua_State* L) + { + lua_createtable(L, p->m_ColumnRenderers.size(), 0); + for(size_t i= 0; i < p->m_ColumnRenderers.size(); ++i) + { + p->m_ColumnRenderers[i].PushSelf(L); + lua_rawseti(L, -2, i+1); + } + return 1; + } + LunaNoteField() { ADD_METHOD(SetStepCallback); @@ -1481,6 +1467,7 @@ public: ADD_METHOD(SetPressed); ADD_METHOD(DidTapNote); ADD_METHOD(DidHoldNote); + ADD_METHOD(GetColumnActors); } }; diff --git a/src/NoteField.h b/src/NoteField.h index cfdf24be04..c10dcbd435 100644 --- a/src/NoteField.h +++ b/src/NoteField.h @@ -29,6 +29,8 @@ public: int iDrawDistanceBeforeTargetsPixels ); virtual void Unload(); + void InitColumnRenderers(); + virtual void HandleMessage( const Message &msg ); // This is done automatically by Init(), but can be re-called explicitly if the @@ -55,6 +57,10 @@ public: int m_iBeginMarker, m_iEndMarker; // only used with MODE_EDIT + // m_ColumnRenderers belongs in the protected section, but it's here in + // public so that the Lua API can access it. -Kyz + vector m_ColumnRenderers; + protected: void CacheNoteSkin( const RString &sNoteSkin ); void UncacheNoteSkin( const RString &sNoteSkin ); @@ -84,8 +90,6 @@ protected: const NoteData *m_pNoteData; - float m_fPercentFadeToFail; // -1 if not fading to fail - const PlayerState* m_pPlayerState; int m_iDrawDistanceAfterTargetsPixels; // this should be a negative number int m_iDrawDistanceBeforeTargetsPixels; // this should be a positive number @@ -101,6 +105,8 @@ protected: ~NoteDisplayCols() { delete [] display; } }; + CommonColumnRenderArgs m_ColumnRenderArgs; + /* All loaded note displays, mapped by their name. */ map m_NoteDisplays; NoteDisplayCols *m_pCurDisplay; diff --git a/src/ReceptorArrowRow.cpp b/src/ReceptorArrowRow.cpp index fd6d2deb31..8c7f6dc54c 100644 --- a/src/ReceptorArrowRow.cpp +++ b/src/ReceptorArrowRow.cpp @@ -30,6 +30,15 @@ void ReceptorArrowRow::Load( const PlayerState* pPlayerState, float fYReverseOff } } +void ReceptorArrowRow::SetColumnRenderers(vector& renderers) +{ + ASSERT_M(renderers.size() == m_ReceptorArrow.size(), "Notefield has different number of columns than receptor row."); + for(size_t c= 0; c < m_ReceptorArrow.size(); ++c) + { + m_ReceptorArrow[c]->SetFakeParent(&(renderers[c])); + } +} + ReceptorArrowRow::~ReceptorArrowRow() { for( unsigned i = 0; i < m_ReceptorArrow.size(); ++i ) diff --git a/src/ReceptorArrowRow.h b/src/ReceptorArrowRow.h index 1b93047386..ac6f081643 100644 --- a/src/ReceptorArrowRow.h +++ b/src/ReceptorArrowRow.h @@ -4,6 +4,7 @@ #include "ReceptorArrow.h" #include "ActorFrame.h" #include "GameConstantsAndTypes.h" +#include "NoteDisplay.h" class PlayerState; /** @brief A row of ReceptorArrow objects. */ @@ -16,6 +17,7 @@ public: virtual void DrawPrimitives(); void Load( const PlayerState* pPlayerState, float fYReverseOffset ); + void SetColumnRenderers(vector& renderers); void Step( int iCol, TapNoteScore score ); void SetPressed( int iCol ); From 65187a55c6dfa608137dc5bd0b648952f581ae47 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Sat, 27 Dec 2014 21:41:55 -0700 Subject: [PATCH 03/14] CubicSpline class created. Temp spline rendering stuff in AMV. --- Docs/Changelog_sm5.txt | 5 + Docs/Luadoc/Lua.xml | 3 + Docs/Luadoc/LuaDocumentation.xml | 9 ++ src/Actor.cpp | 28 ++++ src/ActorMultiVertex.cpp | 126 +++++++++++++++ src/ActorMultiVertex.h | 13 ++ src/NoteDisplay.h | 4 +- src/RageMath.cpp | 260 +++++++++++++++++++++++++++++++ src/RageMath.h | 37 +++++ 9 files changed, 483 insertions(+), 2 deletions(-) diff --git a/Docs/Changelog_sm5.txt b/Docs/Changelog_sm5.txt index 49c0335c11..33512ebde7 100644 --- a/Docs/Changelog_sm5.txt +++ b/Docs/Changelog_sm5.txt @@ -4,6 +4,11 @@ The StepMania 5 Changelog covers all post-sm-ssc changes. For a list of changes from StepMania 4 alpha 5 to sm-ssc v1.2.5, see Changelog_sm-ssc.txt. ________________________________________________________________________________ +2014/12/26 +---------- +* [NoteField] Columns turned into actors that can be fetched with + GetColumnActors. [kyzentun] + 2014/12/20 ---------- * [Preferences] AllowMultipleHighScoreWithSameName, ComboContinuesBetweenSongs diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index 53d2d79fd4..590bfa43f0 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -272,6 +272,7 @@ + @@ -301,6 +302,7 @@ + @@ -968,6 +970,7 @@ + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index fa8011dbe4..f7cb109daa 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -831,6 +831,12 @@ save yourself some time, copy this for undocumented things: Returns the Actor's parent, or nil if it doesn't have one. + + Returns the Actor's fake parent, or nil if it doesn't have one. + + + Sets the Actor's fake parent to p, or clears it if p is nil. + Returns the Actor's visibility. @@ -2953,6 +2959,9 @@ save yourself some time, copy this for undocumented things: Makes the NoteField act as if a tap note was hit in the column, with the given score and bright setting.
The callback for DidTapNote will not be called.
+ + Returns a table of the actors for the columns. + Same as SetDidTapNoteCallback, but for hold notes. Uses HoldNoteScore instead of TapNoteScore. diff --git a/src/Actor.cpp b/src/Actor.cpp index 318e06e8ab..d2ab5487a9 100644 --- a/src/Actor.cpp +++ b/src/Actor.cpp @@ -1739,6 +1739,32 @@ public: pParent->PushSelf(L); return 1; } + static int GetFakeParent(T* p, lua_State *L) + { + Actor* fake= p->GetFakeParent(); + if(fake == NULL) + { + lua_pushnil(L); + } + else + { + fake->PushSelf(L); + } + return 1; + } + static int SetFakeParent(T* p, lua_State* L) + { + if(lua_isnoneornil(L, 1)) + { + p->SetFakeParent(NULL); + } + else + { + Actor* fake= Luna::check(L, 1); + p->SetFakeParent(fake); + } + COMMON_RETURN_SELF; + } static int Draw( T* p, lua_State *L ) { LUA->YieldLua(); @@ -1910,6 +1936,8 @@ public: ADD_METHOD( GetName ); ADD_METHOD( GetParent ); + ADD_METHOD( GetFakeParent ); + ADD_METHOD( SetFakeParent ); ADD_METHOD( Draw ); } diff --git a/src/ActorMultiVertex.cpp b/src/ActorMultiVertex.cpp index c569eb3d7a..7d1b48c0ae 100644 --- a/src/ActorMultiVertex.cpp +++ b/src/ActorMultiVertex.cpp @@ -7,6 +7,7 @@ #include "RageLog.h" #include "RageDisplay.h" #include "RageTexture.h" +#include "RageTimer.h" #include "RageUtil.h" #include "ActorUtil.h" #include "Foreach.h" @@ -62,6 +63,9 @@ ActorMultiVertex::ActorMultiVertex() _EffectMode = EffectMode_Normal; _TextureMode = TextureMode_Modulate; + _using_spline= false; + _spline.redimension(3); + _spline.loop= false; } ActorMultiVertex::~ActorMultiVertex() @@ -73,6 +77,8 @@ ActorMultiVertex::ActorMultiVertex( const ActorMultiVertex &cpy ): Actor( cpy ) { #define CPY(a) a = cpy.a + CPY( _spline ); + CPY( _using_spline ); CPY( AMV_Tweens ); CPY( AMV_current ); CPY( AMV_start ); @@ -194,6 +200,75 @@ void ActorMultiVertex::SetVertexCoords( int index, float TexCoordX, float TexCoo AMV_DestTweenState().vertices[index].t = RageVector2( TexCoordX, TexCoordY ); } +bool ActorMultiVertex::GetUseSpline() +{ + return _using_spline; +} + +void ActorMultiVertex::SetUseSpline(bool use) +{ + _using_spline= use; +} + +void ActorMultiVertex::SplineSetPoint(size_t i, float x , float y , float z) +{ + vector v(3); + v[0]= x; v[1]= y; v[2]= z; + _spline.set_point(i, v); +} + +void ActorMultiVertex::SplineResize(size_t s) +{ + _spline.resize(s); +} + +size_t ActorMultiVertex::SplineSize() +{ + return _spline.size(); +} + +void ActorMultiVertex::SplineSolve() +{ + if(_spline.empty()) + { + return; + } + _spline.solve(); + float num_parts= 0.0f; + size_t num_verts= GetNumVertices(); + switch(GetDestDrawMode()) + { + case DrawMode_Quads: + break; + case DrawMode_QuadStrip: + break; + case DrawMode_Fan: + break; + case DrawMode_Strip: + break; + case DrawMode_Triangles: + break; + case DrawMode_LineStrip: + { + float conversion= static_cast(_spline.size()-1) / static_cast(num_verts-1); + if(_spline.loop) + { + conversion= static_cast(_spline.size()) / static_cast(num_verts-1); + } + for(size_t i= 0; i < num_verts; ++i) + { + float t= i * conversion; + vector p; + _spline.evaluate(t, p); + SetVertexPos(i, p[0], p[1], p[2]); + } + } + break; + case DrawMode_SymmetricQuadStrip: + break; + } +} + void ActorMultiVertex::DrawPrimitives() { Actor::SetGlobalRenderStates(); // set Actor-specified render states @@ -658,6 +733,48 @@ public: } return 1; } + DEFINE_METHOD(GetUseSpline, GetUseSpline()); + DEFINE_METHOD(SplineGetLoop, SplineGetLoop()); + static int SplineSetLoop(T* p, lua_State* L) + { + p->SplineSetLoop(lua_toboolean(L, 1)); + COMMON_RETURN_SELF; + } + static int SplineSize(T* p, lua_State* L) + { + lua_pushnumber(L, p->SplineSize()); + return 1; + } + static int SetUseSpline(T* p, lua_State* L) + { + p->SetUseSpline(lua_toboolean(L, 1)); + COMMON_RETURN_SELF; + } + static int SplineSetPoint(T* p, lua_State* L) + { + size_t i= IArg(1)-1; + if(i >= p->SplineSize()) + { + luaL_error(L, "Spline point index greater than the number of points."); + } + p->SplineSetPoint(i, FArg(2), FArg(3), FArg(4)); + COMMON_RETURN_SELF; + } + static int SplineResize(T* p, lua_State* L) + { + int s= IArg(1); + if(s < 0) + { + luaL_error(L, "Negative spline size not allowed."); + } + p->SplineResize(static_cast(s)); + COMMON_RETURN_SELF; + } + static int SplineSolve(T* p, lua_State* L) + { + p->SplineSolve(); + COMMON_RETURN_SELF; + } LunaActorMultiVertex() { @@ -679,6 +796,15 @@ public: ADD_METHOD( GetCurrFirstToDraw ); ADD_METHOD( GetCurrNumToDraw ); + ADD_METHOD(GetUseSpline); + ADD_METHOD(SetUseSpline); + ADD_METHOD(SplineGetLoop); + ADD_METHOD(SplineSetLoop); + ADD_METHOD(SplineSize); + ADD_METHOD(SplineResize); + ADD_METHOD(SplineSetPoint); + ADD_METHOD(SplineSolve); + // Copy from RageTexture ADD_METHOD( SetTexture ); ADD_METHOD( GetTexture ); diff --git a/src/ActorMultiVertex.h b/src/ActorMultiVertex.h index 176d2a7cad..ce340215ef 100644 --- a/src/ActorMultiVertex.h +++ b/src/ActorMultiVertex.h @@ -2,6 +2,7 @@ #include "Actor.h" #include "RageDisplay.h" +#include "RageMath.h" #include "RageTextureID.h" enum DrawMode @@ -102,11 +103,23 @@ public: void SetVertexColor( int index , RageColor c ); void SetVertexCoords( int index , float TexCoordX , float TexCoordY ); + bool GetUseSpline(); + void SetUseSpline(bool use); + bool SplineGetLoop() { return _spline.loop; } + void SplineSetLoop(bool loop) { _spline.loop= loop; } + void SplineSetPoint(size_t i, float x , float y , float z); + void SplineResize(size_t s); + size_t SplineSize(); + void SplineSolve(); + virtual void PushSelf( lua_State *L ); private: RageTexture* _Texture; + CubicSplineN _spline; + bool _using_spline; + vector _Vertices; vector AMV_Tweens; AMV_TweenState AMV_current; diff --git a/src/NoteDisplay.h b/src/NoteDisplay.h index 3487be8d6c..ade622f321 100644 --- a/src/NoteDisplay.h +++ b/src/NoteDisplay.h @@ -179,7 +179,7 @@ private: float m_fYReverseOffsetPixels; }; -// So, this is a bit screwy, and it's all because routine forces rendering +// So, this is a bit screwy, and it's partly because routine forces rendering // notes from different noteskins in the same column. // NoteColumnRenderer exists to hold all the data needed for rendering a // column and apply any transforms from that column's actor to the @@ -188,7 +188,7 @@ private: // actors so they can move with the rest of the column. I didn't use // ActorProxy because the receptor/ghost actors need to pull in the parent // state of their rows and the parent state of the column. -Kyz -class NoteColumnRenderer : public ActorFrame +class NoteColumnRenderer : public Actor { public: NoteDisplay* m_displays[PLAYER_INVALID+1]; diff --git a/src/RageMath.cpp b/src/RageMath.cpp index dd209974cc..8baf3b5403 100644 --- a/src/RageMath.cpp +++ b/src/RageMath.cpp @@ -5,6 +5,7 @@ */ #include "global.h" +#include "RageLog.h" #include "RageMath.h" #include "RageTypes.h" #include @@ -695,6 +696,265 @@ void RageBezier2D::SetFromBezier( m_Y.SetFromBezier( fC1Y, fC2Y, fC3Y, fC4Y ); } +// CubicSpline implementation written by Kyzentun 2014/12/27 + +void CubicSpline::solve_looped() +{ + if(check_minimum_size()) { return; } + size_t last= m_points.size(); + vector results(m_points.size()); + vector diagonals(m_points.size()); + results[0]= 3 * (m_points[1].a - m_points[last-1].a); + diagonals[0]= 4.0f; + prep_inner(last, diagonals, results); + results[last-1]= 3 * (m_points[0].a - m_points[last-2].a); + diagonals[last-1]= 4.0f; + + // The steps to solve the system of equations look like this: + // | 4 1 0 0 1 | -> | 4 0 0 0 1 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | + // | 1 4 1 0 0 | -> | 0 d 1 0 x | -> | 0 d 1 0 x | -> | 0 d 0 0 x | + // | 0 1 4 1 0 | -> | 0 1 4 1 0 | -> | 0 1 4 1 0 | -> | 0 0 d 1 x | + // | 0 0 1 4 1 | -> | 0 0 1 4 1 | -> | 0 0 1 4 1 | -> | 0 0 1 4 1 | + // | 1 0 0 1 4 | -> | 1 x 0 1 4 | -> | 0 x 0 1 q | -> | 0 x x 1 q | + // V + // | 4 0 0 0 0 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | + // | 0 d 0 0 0 | -> | 0 d 0 0 0 | -> | 0 d 0 0 0 | -> | 0 d 0 0 0 | + // | 0 0 d 1 x | -> | 0 0 d 0 x | -> | 0 0 d 0 0 | -> | 0 0 d 0 0 | + // | 0 0 1 d 1 | -> | 0 0 0 d n | -> | 0 0 0 d n | -> | 0 0 0 d 0 | + // | 0 0 x 1 r | -> | 0 0 x n r | -> | 0 0 0 n s | -> | 0 0 0 0 t | + // Each time through the loop performs two of these steps, 4 operations. + + size_t end= last-1; + size_t stop= end-1; + float cedge= 1.0f; // [ri][cl] + float redge= 1.0f; // [rl][ci] + // The loop stops before end because the case where [ri][cl] == [ri][ci+1] + // needs special handling. + for(size_t i= 0; i < stop; ++i) + { + float next_cedge= 0.0f; // [ri+1][ce] + float next_redge= 0.0f; // [re][ci+1] + // Operation: Add col[i] / -[ri][ci] to col[i+1] to zero [ri][ci+1]. + float diag_recip= 1.0f / diagonals[i]; + diagonals[i+1]-= diag_recip; + next_redge-= redge * diag_recip; + // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci]. + results[i+1]-= results[i] * diag_recip; + next_cedge-= cedge * diag_recip; + // Operation: Add col[i] * -(cedge/[ri][ci]) to col[e] to zero cedge. + diagonals[end]-= redge * (cedge / diagonals[i]); + cedge= next_cedge; // Do not use cedge after this point in the loop. + // Operation: Add row[i] * -(redge/[ri][ci]) to row[e] to zero redge. + results[end]-= results[i] * (redge / diagonals[i]); + redge= next_redge; // Do not use redge after this point in the loop. + } + // [rs][ce] is 1 - cedge, [re][cs] is 1 - redge + // Operation: Add col[s] * -([rs][ce] / [rs][cs]) to col[e] to zero redge. + diagonals[end]-= redge * ((1.0f - cedge) / diagonals[stop]); + // Operation: Add row[s] * -([re][cs] / [rs][cs]) to row[e] to zero redge. + results[end]-= results[stop] * ((1.0f - redge) / diagonals[stop]); + + set_results(last, diagonals, results); +} + +void CubicSpline::solve_straight() +{ + if(check_minimum_size()) { return; } + size_t last= m_points.size(); + vector results(m_points.size()); + vector diagonals(m_points.size()); + results[0]= 3 * (m_points[1].a - m_points[0].a); + diagonals[0]= 2.0f; + prep_inner(last, diagonals, results); + results[last-1]= 3 * (m_points[last-1].a - m_points[last-2].a); + diagonals[last-1]= 2.0f; + + // The system of equations to be solved looks like this: + // | 2 1 0 0 | = | results[0] | + // | 1 4 1 0 | = | results[1] | + // | 0 1 4 1 | = | results[2] | + // | 0 0 1 2 | = | results[3] | + // Operations are carefully chosen to only modify the values in the + // diagonals and the results, leaving the 1s unchanged. + // Operation: Add col[0] * -.5 to col[1] to zero [r0][c1]. + diagonals[1]-= .5f; + // Operation: Add row[0] * -.5 to row[1] to zero [r1][c0]. + results[1]-= results[0] * .5f; + for(size_t i= 1; i < last - 1; ++i) + { + float diag_recip= 1.0f / diagonals[i]; + // Operation: Add col[i] / -[ri][ci] to col[i+1] to zero [ri][ci+1]. + diagonals[i+1]-= diag_recip; + // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci]; + results[i]-= results[i-1] * diag_recip; + } + set_results(last, diagonals, results); +} + +bool CubicSpline::check_minimum_size() +{ + size_t last= m_points.size(); + if(last < 2) { return true; } + if(last == 2) + { + m_points[0].b= m_points[1].a - m_points[0].a; + m_points[0].c= m_points[0].d= 0.0f; + // These will be used in the looping case. + m_points[1].b= m_points[0].a - m_points[1].a; + m_points[1].c= m_points[1].d= 0.0f; + return true; + } + float a= m_points[0].a; + for(size_t i= 1; i < m_points.size(); ++i) + { + m_points[i].b= m_points[i].c= m_points[i].d= 0.0f; + if(m_points[i].a != a) { return false; } + } + return true; +} + +void CubicSpline::prep_inner(size_t last, vector& diagonals, vector& results) +{ + for(size_t i= 1; i < last - 1; ++i) + { + results[i]= 3 * (m_points[i+1].a - m_points[i-1].a); + diagonals[i]= 4.0f; + } +} + +void CubicSpline::set_results(size_t last, vector& diagonals, vector& results) +{ + // No more operations left, everything not a diagonal should be zero now. + for(size_t i= 0; i < last; ++i) + { + results[i]/= diagonals[i]; + } + // Now we can go through and set the b, c, d values of each point. + // b, c, d values of the last point are not set because they are unused. + for(size_t i= 0; i < last; ++i) + { + size_t next= (i+1) % last; + float diff= m_points[next].a - m_points[i].a; + m_points[i].b= results[i]; + m_points[i].c= (3 * diff) - (2 * results[i]) - results[next]; + m_points[i].d= (2 * -diff) + results[i] + results[next]; + } + // Solving is now complete. +} + +float CubicSpline::evaluate(float t, bool loop) +{ + if(loop) + { + t= fmodf(t, m_points.size()); + } + int flort= static_cast(t); + if(flort < 0) + { + return m_points[0].a; + } + size_t p= min(static_cast(flort), m_points.size()-1); + float tfrac= t - static_cast(flort); + float tsq= tfrac * tfrac; + float tcub= tsq * tfrac; + return m_points[p].a + (m_points[p].b * tfrac) + + (m_points[p].c * tsq) + (m_points[p].d * tcub); +} + +void CubicSpline::set_point(size_t i, float v) +{ + ASSERT_M(i < m_points.size(), "CubicSpline::set_point requires the index to be less than the number of points."); + m_points[i].a= v; +} + +void CubicSpline::resize(size_t s) +{ + m_points.resize(s); +} + +size_t CubicSpline::size() +{ + return m_points.size(); +} + +bool CubicSpline::empty() +{ + return m_points.empty(); +} + +void CubicSplineN::solve() +{ + if(loop) + { + for(spline_cont_t::iterator spline= m_splines.begin(); + spline != m_splines.end(); ++spline) + { + spline->solve_looped(); + } + } + else + { + for(spline_cont_t::iterator spline= m_splines.begin(); + spline != m_splines.end(); ++spline) + { + spline->solve_straight(); + } + } +} + +void CubicSplineN::evaluate(float t, vector& v) +{ + for(spline_cont_t::iterator spline= m_splines.begin(); + spline != m_splines.end(); ++spline) + { + v.push_back(spline->evaluate(t, loop)); + } +} + +void CubicSplineN::set_point(size_t i, vector const& v) +{ + ASSERT_M(v.size() == m_splines.size(), "CubicSplineN::set_point requires the passed point to be the same dimension as the spline."); + for(size_t n= 0; n < m_splines.size(); ++n) + { + m_splines[n].set_point(i, v[n]); + } +} + +void CubicSplineN::resize(size_t s) +{ + for(spline_cont_t::iterator spline= m_splines.begin(); + spline != m_splines.end(); ++spline) + { + spline->resize(s); + } +} + +size_t CubicSplineN::size() +{ + if(!m_splines.empty()) + { + return m_splines[0].size(); + } + return 0; +} + +bool CubicSplineN::empty() +{ + return m_splines.empty() || m_splines[0].empty(); +} + +void CubicSplineN::redimension(size_t d) +{ + m_splines.resize(d); +} + +size_t CubicSplineN::dimension() +{ + return m_splines.size(); +} + + + /* * Copyright (c) 2001-2006 Chris Danford, Glenn Maynard * All rights reserved. diff --git a/src/RageMath.h b/src/RageMath.h index 4e56aac737..4565fc80fb 100644 --- a/src/RageMath.h +++ b/src/RageMath.h @@ -85,6 +85,43 @@ private: RageQuadratic m_Y; }; +struct CubicSpline +{ + void solve_looped(); + void solve_straight(); + float evaluate(float t, bool loop); + void set_point(size_t i, float v); + void resize(size_t s); + size_t size(); + bool empty(); +private: + bool check_minimum_size(); + void prep_inner(size_t last, vector& diagonals, vector& results); + void set_results(size_t last, vector& diagonals, vector& results); + + struct SplinePoint + { + float a, b, c, d; + }; + vector m_points; +}; + +struct CubicSplineN +{ + void solve(); + void evaluate(float t, vector& v); + void set_point(size_t i, vector const& v); + void resize(size_t s); + size_t size(); + void redimension(size_t d); + size_t dimension(); + bool empty(); + typedef vector spline_cont_t; + bool loop; +private: + spline_cont_t m_splines; +}; + #endif /* From 584f1c881305f75f43b30e1958319c3cbe2a65b2 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Sun, 28 Dec 2014 05:57:07 -0700 Subject: [PATCH 04/14] Moved cubic spline implementation to its own source file. --- src/ActorMultiVertex.cpp | 1 + src/ActorMultiVertex.h | 1 + src/CubicSpline.cpp | 540 +++++++++++++++++++++++++++++++++++++++ src/CubicSpline.h | 78 ++++++ src/Makefile.am | 1 + src/RageMath.cpp | 259 ------------------- src/RageMath.h | 37 --- 7 files changed, 621 insertions(+), 296 deletions(-) create mode 100644 src/CubicSpline.cpp create mode 100644 src/CubicSpline.h diff --git a/src/ActorMultiVertex.cpp b/src/ActorMultiVertex.cpp index 7d1b48c0ae..5d3c75b00b 100644 --- a/src/ActorMultiVertex.cpp +++ b/src/ActorMultiVertex.cpp @@ -66,6 +66,7 @@ ActorMultiVertex::ActorMultiVertex() _using_spline= false; _spline.redimension(3); _spline.loop= false; + _spline.m_owned_by_actor= true; } ActorMultiVertex::~ActorMultiVertex() diff --git a/src/ActorMultiVertex.h b/src/ActorMultiVertex.h index ce340215ef..60a322f811 100644 --- a/src/ActorMultiVertex.h +++ b/src/ActorMultiVertex.h @@ -1,6 +1,7 @@ /** @brief ActorMultiVertex - A texture created from multiple textures. */ #include "Actor.h" +#include "CubicSpline.h" #include "RageDisplay.h" #include "RageMath.h" #include "RageTextureID.h" diff --git a/src/CubicSpline.cpp b/src/CubicSpline.cpp new file mode 100644 index 0000000000..cf38c462f7 --- /dev/null +++ b/src/CubicSpline.cpp @@ -0,0 +1,540 @@ +#include "global.h" +#include "CubicSpline.h" +#include "RageLog.h" +#include +using std::list; + +// Spline solving optimization: +// The tridiagonal part of the system of equations for a spline of size n is +// the same for all splines of size n. It's not affected by the positions +// of the points. +// So spline solving can be split into two parts. Part 1 solves the +// tridiagonal and stores the result. Part 2 takes the solved tridiagonal +// and applies it to the positions to find the coefficients. +// Part 1 only needs to be done when the number of points changes. So this +// could cut solve time for the same number of points substantially. +// Further optimization is to cache the part 1 results for the last 16 spline +// sizes solved, to reduce the cost of using lots of splines with a small +// number of sizes. + +struct SplineSolutionCache +{ + void solve_diagonals_straight(vector& diagonals); + void solve_diagonals_looped(vector& diagonals); +private: + void prep_inner(size_t last, vector& out); + bool find_in_cache(list >& cache, vector& out); + void add_to_cache(list >& cache, vector& out); + list > straight_diagonals; + list > looped_diagonals; +}; + +size_t const solution_cache_limit= 16; + +bool SplineSolutionCache::find_in_cache(list >& cache, vector& out) +{ + size_t out_size= out.size(); + for(list >::iterator entry= cache.begin(); + entry != cache.end(); ++entry) + { + if(out_size == entry->size()) + { + for(size_t i= 0; i < out_size; ++i) + { + out[i]= (*entry)[i]; + } + return true; + } + } + return false; +} + +void SplineSolutionCache::add_to_cache(list >& cache, vector& out) +{ + if(cache.size() >= solution_cache_limit) + { + cache.pop_back(); + } + cache.push_front(out); +} + +void SplineSolutionCache::prep_inner(size_t last, vector& out) +{ + for(size_t i= 1; i < last; ++i) + { + out[i]= 4.0f; + } +} + +void SplineSolutionCache::solve_diagonals_straight(vector& diagonals) +{ + if(find_in_cache(straight_diagonals, diagonals)) + { + return; + } + size_t last= diagonals.size(); + diagonals[0]= 2.0f; + prep_inner(last-1, diagonals); + diagonals[last-1]= 2.0f; + // Operation: Add col[0] * -.5 to col[1] to zero [r0][c1]. + diagonals[1]-= .5f; + for(size_t i= 1; i < last-1; ++i) + { + // Operation: Add col[i] / -[ri][ci] to col[i+1] to zero [ri][ci+1]. + diagonals[i+1]-= 1.0f / diagonals[i]; + } + // Solving finished. + add_to_cache(straight_diagonals, diagonals); +} + +void SplineSolutionCache::solve_diagonals_looped(vector& diagonals) +{ + if(find_in_cache(looped_diagonals, diagonals)) + { + return; + } + size_t last= diagonals.size(); + diagonals[0]= 4.0f; + prep_inner(last, diagonals); + + size_t end= last-1; + size_t stop= end-1; + float cedge= 1.0f; // [ri][cl] + float redge= 1.0f; // [rl][ci] + // The loop stops before end because the case where [ri][cl] == [ri][ci+1] + // needs special handling. + for(size_t i= 0; i < stop; ++i) + { + float next_cedge= 0.0f; // [ri+1][ce] + float next_redge= 0.0f; // [re][ci+1] + // Operation: Add col[i] / -[ri][ci] to col[i+1] to zero [ri][ci+1]. + float diag_recip= 1.0f / diagonals[i]; + diagonals[i+1]-= diag_recip; + next_redge-= redge * diag_recip; + // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci]. + next_cedge-= cedge * diag_recip; + // Operation: Add col[i] * -(cedge/[ri][ci]) to col[e] to zero cedge. + diagonals[end]-= redge * (cedge / diagonals[i]); + cedge= next_cedge; // Do not use cedge after this point in the loop. + // Operation: Add row[i] * -(redge/[ri][ci]) to row[e] to zero redge. + redge= next_redge; // Do not use redge after this point in the loop. + } + // [rs][ce] is 1 - cedge, [re][cs] is 1 - redge + // Operation: Add col[s] * -([rs][ce] / [rs][cs]) to col[e] to zero redge. + diagonals[end]-= redge * ((1.0f - cedge) / diagonals[stop]); + // Solving finished. + add_to_cache(looped_diagonals, diagonals); +} + +SplineSolutionCache solution_cache; + +void CubicSpline::solve_looped() +{ + if(check_minimum_size()) { return; } + size_t last= m_points.size(); + vector results(m_points.size()); + vector diagonals(m_points.size()); + solution_cache.solve_diagonals_looped(diagonals); + results[0]= 3 * (m_points[1].a - m_points[last-1].a); + prep_inner(last, results); + results[last-1]= 3 * (m_points[0].a - m_points[last-2].a); + + // The steps to solve the system of equations look like this: + // | 4 1 0 0 1 | -> | 4 0 0 0 1 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | + // | 1 4 1 0 0 | -> | 0 d 1 0 x | -> | 0 d 1 0 x | -> | 0 d 0 0 x | + // | 0 1 4 1 0 | -> | 0 1 4 1 0 | -> | 0 1 4 1 0 | -> | 0 0 d 1 x | + // | 0 0 1 4 1 | -> | 0 0 1 4 1 | -> | 0 0 1 4 1 | -> | 0 0 1 4 1 | + // | 1 0 0 1 4 | -> | 1 x 0 1 4 | -> | 0 x 0 1 q | -> | 0 x x 1 q | + // V + // | 4 0 0 0 0 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | + // | 0 d 0 0 0 | -> | 0 d 0 0 0 | -> | 0 d 0 0 0 | -> | 0 d 0 0 0 | + // | 0 0 d 1 x | -> | 0 0 d 0 x | -> | 0 0 d 0 0 | -> | 0 0 d 0 0 | + // | 0 0 1 d 1 | -> | 0 0 0 d n | -> | 0 0 0 d n | -> | 0 0 0 d 0 | + // | 0 0 x 1 r | -> | 0 0 x n r | -> | 0 0 0 n s | -> | 0 0 0 0 t | + // Each time through the loop performs two of these steps, 4 operations. + // All operations on diagonals are done by the solution cache, because the + // diagonals come out the same for all splines of a given size. + + size_t end= last-1; + size_t stop= end-1; + float cedge= 1.0f; // [ri][cl] + float redge= 1.0f; // [rl][ci] + // The loop stops before end because the case where [ri][cl] == [ri][ci+1] + // needs special handling. + for(size_t i= 0; i < stop; ++i) + { + float next_cedge= 0.0f; // [ri+1][ce] + float next_redge= 0.0f; // [re][ci+1] + // Operation: Add col[i] / -[ri][ci] to col[i+1] to zero [ri][ci+1]. + float diag_recip= 1.0f / diagonals[i]; + next_redge-= redge * diag_recip; + // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci]. + results[i+1]-= results[i] * diag_recip; + next_cedge-= cedge * diag_recip; + // Operation: Add col[i] * -(cedge/[ri][ci]) to col[e] to zero cedge. + cedge= next_cedge; // Do not use cedge after this point in the loop. + // Operation: Add row[i] * -(redge/[ri][ci]) to row[e] to zero redge. + results[end]-= results[i] * (redge / diagonals[i]); + redge= next_redge; // Do not use redge after this point in the loop. + } + // [rs][ce] is 1 - cedge, [re][cs] is 1 - redge + // Operation: Add row[s] * -([re][cs] / [rs][cs]) to row[e] to zero redge. + results[end]-= results[stop] * ((1.0f - redge) / diagonals[stop]); + + set_results(last, diagonals, results); +} + +void CubicSpline::solve_straight() +{ + if(check_minimum_size()) { return; } + size_t last= m_points.size(); + vector results(m_points.size()); + vector diagonals(m_points.size()); + solution_cache.solve_diagonals_straight(diagonals); + results[0]= 3 * (m_points[1].a - m_points[0].a); + prep_inner(last, results); + results[last-1]= 3 * (m_points[last-1].a - m_points[last-2].a); + + // The system of equations to be solved looks like this: + // | 2 1 0 0 | = | results[0] | + // | 1 4 1 0 | = | results[1] | + // | 0 1 4 1 | = | results[2] | + // | 0 0 1 2 | = | results[3] | + // Operations are carefully chosen to only modify the values in the + // diagonals and the results, leaving the 1s unchanged. + // All operations on diagonals are done by the solution cache, because the + // diagonals come out the same for all splines of a given size. + // Operation: Add row[0] * -.5 to row[1] to zero [r1][c0]. + results[1]-= results[0] * .5f; + for(size_t i= 1; i < last - 1; ++i) + { + // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci]; + results[i]-= results[i-1] * (1.0f / diagonals[i]); + } + set_results(last, diagonals, results); +} + +bool CubicSpline::check_minimum_size() +{ + size_t last= m_points.size(); + if(last < 2) + { + m_points[0].b= m_points[0].c= m_points[0].d= 0.0f; + return true; + } + if(last == 2) + { + m_points[0].b= m_points[1].a - m_points[0].a; + m_points[0].c= m_points[0].d= 0.0f; + // These will be used in the looping case. + m_points[1].b= m_points[0].a - m_points[1].a; + m_points[1].c= m_points[1].d= 0.0f; + return true; + } + float a= m_points[0].a; + bool all_points_identical= true; + for(size_t i= 1; i < m_points.size(); ++i) + { + m_points[i].b= m_points[i].c= m_points[i].d= 0.0f; + if(m_points[i].a != a) { all_points_identical= false; } + } + return all_points_identical; +} + +void CubicSpline::prep_inner(size_t last, vector& results) +{ + for(size_t i= 1; i < last - 1; ++i) + { + results[i]= 3 * (m_points[i+1].a - m_points[i-1].a); + } +} + +void CubicSpline::set_results(size_t last, vector& diagonals, vector& results) +{ + // No more operations left, everything not a diagonal should be zero now. + for(size_t i= 0; i < last; ++i) + { + results[i]/= diagonals[i]; + } + // Now we can go through and set the b, c, d values of each point. + // b, c, d values of the last point are not set because they are unused. + for(size_t i= 0; i < last; ++i) + { + size_t next= (i+1) % last; + float diff= m_points[next].a - m_points[i].a; + m_points[i].b= results[i]; + m_points[i].c= (3 * diff) - (2 * results[i]) - results[next]; + m_points[i].d= (2 * -diff) + results[i] + results[next]; + } + // Solving is now complete. +} + +float CubicSpline::evaluate(float t, bool loop) +{ + if(m_points.empty()) + { + return 0.0f; + } + if(loop) + { + t= fmodf(t, m_points.size()); + } + int flort= static_cast(t); + if(flort < 0) + { + return m_points[0].a; + } + size_t p= min(static_cast(flort), m_points.size()-1); + float tfrac= t - static_cast(flort); + float tsq= tfrac * tfrac; + float tcub= tsq * tfrac; + return m_points[p].a + (m_points[p].b * tfrac) + + (m_points[p].c * tsq) + (m_points[p].d * tcub); +} + +void CubicSpline::set_point(size_t i, float v) +{ + ASSERT_M(i < m_points.size(), "CubicSpline::set_point requires the index to be less than the number of points."); + m_points[i].a= v; +} + +void CubicSpline::resize(size_t s) +{ + m_points.resize(s); +} + +size_t CubicSpline::size() +{ + return m_points.size(); +} + +bool CubicSpline::empty() +{ + return m_points.empty(); +} + +void CubicSplineN::solve() +{ + if(!m_dirty) { return; } + if(loop) + { + for(spline_cont_t::iterator spline= m_splines.begin(); + spline != m_splines.end(); ++spline) + { + spline->solve_looped(); + } + } + else + { + for(spline_cont_t::iterator spline= m_splines.begin(); + spline != m_splines.end(); ++spline) + { + spline->solve_straight(); + } + } + m_dirty= false; +} + +void CubicSplineN::evaluate(float t, vector& v) +{ + for(spline_cont_t::iterator spline= m_splines.begin(); + spline != m_splines.end(); ++spline) + { + v.push_back(spline->evaluate(t, loop)); + } +} + +void CubicSplineN::set_point(size_t i, vector const& v) +{ + ASSERT_M(v.size() == m_splines.size(), "CubicSplineN::set_point requires the passed point to be the same dimension as the spline."); + for(size_t n= 0; n < m_splines.size(); ++n) + { + m_splines[n].set_point(i, v[n]); + } + m_dirty= true; +} + +void CubicSplineN::resize(size_t s) +{ + for(spline_cont_t::iterator spline= m_splines.begin(); + spline != m_splines.end(); ++spline) + { + spline->resize(s); + } + m_dirty= true; +} + +size_t CubicSplineN::size() +{ + if(!m_splines.empty()) + { + return m_splines[0].size(); + } + return 0; +} + +bool CubicSplineN::empty() +{ + return m_splines.empty() || m_splines[0].empty(); +} + +void CubicSplineN::redimension(size_t d) +{ + m_splines.resize(d); + m_dirty= true; +} + +size_t CubicSplineN::dimension() +{ + return m_splines.size(); +} + +#include "LuaBinding.h" + +struct LunaCubicSplineN : Luna +{ + static int solve(T* p, lua_State* L) + { + p->solve(); + COMMON_RETURN_SELF; + } + static int evaluate(T* p, lua_State* L) + { + vector pos; + p->evaluate(FArg(1), pos); + lua_createtable(L, pos.size(), 0); + for(size_t i= 0; i < pos.size(); ++i) + { + lua_pushnumber(L, pos[i]); + lua_rawseti(L, -2, i+1); + } + return 1; + } + static void set_point_from_stack(T* p, lua_State* L, size_t i, int s) + { + if(!lua_istable(L, s)) + { + luaL_error(L, "Spline point must be a table."); + } + size_t elements= lua_objlen(L, s); + // Too many elements is not an error because allowing it allows the user + // to reuse the same position data set after changing the dimension size. + // The same is true for too few elements. + vector pos; + for(size_t e= 0; e < elements; ++e) + { + lua_rawgeti(L, s, e+1); + pos.push_back(FArg(-1)); + } + while(pos.size() < p->dimension()) + { + pos.push_back(0.0f); + } + p->set_point(i, pos); + } + static int set_point(T* p, lua_State* L) + { + vector pos; + int i= IArg(1)-1; + if(i < 0) + { + luaL_error(L, "Cannot set spline point at index less than 1."); + } + if(i >= p->size()) + { + luaL_error(L, "Cannot set spline point at index greater than size."); + } + set_point_from_stack(p, L, static_cast(i), 2); + COMMON_RETURN_SELF; + } + static int resize(T* p, lua_State* L) + { + int siz= IArg(1); + if(siz < 0) + { + luaL_error(L, "A spline cannot have less than 0 points."); + } + p->resize(static_cast(siz)); + COMMON_RETURN_SELF; + } + static int size(T* p, lua_State* L) + { + lua_pushnumber(L, p->size()); + return 1; + } + static int redimension(T* p, lua_State* L) + { + if(p->m_owned_by_actor) + { + luaL_error(L, "This spline cannot be redimensioned because it is " + "owned by an actor that relies on it having fixed dimensions."); + } + int dim= IArg(1); + if(dim < 0) + { + luaL_error(L, "A spline cannot have less than 0 dimensions."); + } + p->redimension(static_cast(dim)); + COMMON_RETURN_SELF; + } + static int dimension(T* p, lua_State* L) + { + lua_pushnumber(L, p->dimension()); + return 1; + } + static int empty(T* p, lua_State* L) + { + lua_pushboolean(L, p->empty()); + return 1; + } + static int set_loop(T* p, lua_State* L) + { + p->loop= lua_toboolean(L, 1); + COMMON_RETURN_SELF; + } + static int get_loop(T* p, lua_State* L) + { + lua_pushboolean(L, p->loop); + return 1; + } + LunaCubicSplineN() + { + ADD_METHOD(solve); + ADD_METHOD(evaluate); + ADD_METHOD(set_point); + ADD_METHOD(resize); + ADD_METHOD(size); + ADD_METHOD(redimension); + ADD_METHOD(dimension); + ADD_METHOD(empty); + ADD_METHOD(set_loop); + ADD_METHOD(get_loop); + } +}; +LUA_REGISTER_CLASS(CubicSplineN) + +// Side note: Actually written between 2014/12/26 and 2014/12/28 +/* + * Copyright (c) 2014-2015 Eric Reese + * 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. + */ diff --git a/src/CubicSpline.h b/src/CubicSpline.h new file mode 100644 index 0000000000..1b5e16e322 --- /dev/null +++ b/src/CubicSpline.h @@ -0,0 +1,78 @@ +#ifndef CUBIC_SPLINE_H +#define CUBIC_SPLINE_H + +#include +using std::vector; +struct lua_State; + +struct CubicSpline +{ + void solve_looped(); + void solve_straight(); + float evaluate(float t, bool loop); + void set_point(size_t i, float v); + void resize(size_t s); + size_t size(); + bool empty(); +private: + bool check_minimum_size(); + void prep_inner(size_t last, vector& results); + void set_results(size_t last, vector& diagonals, vector& results); + + struct SplinePoint + { + float a, b, c, d; + }; + vector m_points; +}; + +struct CubicSplineN +{ + CubicSplineN() + :m_dirty(true), m_owned_by_actor(false) + {} + void solve(); + void evaluate(float t, vector& v); + void set_point(size_t i, vector const& v); + void resize(size_t s); + size_t size(); + void redimension(size_t d); + size_t dimension(); + bool empty(); + typedef vector spline_cont_t; + bool loop; + bool m_owned_by_actor; + + void PushSelf(lua_State* L); +private: + bool m_dirty; + spline_cont_t m_splines; +}; + +#endif + +// Side note: Actually written between 2014/12/26 and 2014/12/28 +/* + * Copyright (c) 2014-2015 Eric Reese + * 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. + */ diff --git a/src/Makefile.am b/src/Makefile.am index 19ac2d4d8a..332de6ce02 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -516,6 +516,7 @@ RageFileDriverSlice.cpp RageFileDriverSlice.h \ RageFileDriverTimeout.cpp RageFileDriverTimeout.h Rage = $(PCRE) $(Lua) $(jsoncpp) $(RageFile) $(RageSoundFileReaders) \ +CubicSpline.cpp CubicSpline.h \ RageBitmapTexture.cpp RageBitmapTexture.h \ RageDisplay.cpp RageDisplay.h \ RageDisplay_OGL.cpp RageDisplay_OGL.h \ diff --git a/src/RageMath.cpp b/src/RageMath.cpp index 8baf3b5403..93b54bc389 100644 --- a/src/RageMath.cpp +++ b/src/RageMath.cpp @@ -696,265 +696,6 @@ void RageBezier2D::SetFromBezier( m_Y.SetFromBezier( fC1Y, fC2Y, fC3Y, fC4Y ); } -// CubicSpline implementation written by Kyzentun 2014/12/27 - -void CubicSpline::solve_looped() -{ - if(check_minimum_size()) { return; } - size_t last= m_points.size(); - vector results(m_points.size()); - vector diagonals(m_points.size()); - results[0]= 3 * (m_points[1].a - m_points[last-1].a); - diagonals[0]= 4.0f; - prep_inner(last, diagonals, results); - results[last-1]= 3 * (m_points[0].a - m_points[last-2].a); - diagonals[last-1]= 4.0f; - - // The steps to solve the system of equations look like this: - // | 4 1 0 0 1 | -> | 4 0 0 0 1 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | - // | 1 4 1 0 0 | -> | 0 d 1 0 x | -> | 0 d 1 0 x | -> | 0 d 0 0 x | - // | 0 1 4 1 0 | -> | 0 1 4 1 0 | -> | 0 1 4 1 0 | -> | 0 0 d 1 x | - // | 0 0 1 4 1 | -> | 0 0 1 4 1 | -> | 0 0 1 4 1 | -> | 0 0 1 4 1 | - // | 1 0 0 1 4 | -> | 1 x 0 1 4 | -> | 0 x 0 1 q | -> | 0 x x 1 q | - // V - // | 4 0 0 0 0 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | - // | 0 d 0 0 0 | -> | 0 d 0 0 0 | -> | 0 d 0 0 0 | -> | 0 d 0 0 0 | - // | 0 0 d 1 x | -> | 0 0 d 0 x | -> | 0 0 d 0 0 | -> | 0 0 d 0 0 | - // | 0 0 1 d 1 | -> | 0 0 0 d n | -> | 0 0 0 d n | -> | 0 0 0 d 0 | - // | 0 0 x 1 r | -> | 0 0 x n r | -> | 0 0 0 n s | -> | 0 0 0 0 t | - // Each time through the loop performs two of these steps, 4 operations. - - size_t end= last-1; - size_t stop= end-1; - float cedge= 1.0f; // [ri][cl] - float redge= 1.0f; // [rl][ci] - // The loop stops before end because the case where [ri][cl] == [ri][ci+1] - // needs special handling. - for(size_t i= 0; i < stop; ++i) - { - float next_cedge= 0.0f; // [ri+1][ce] - float next_redge= 0.0f; // [re][ci+1] - // Operation: Add col[i] / -[ri][ci] to col[i+1] to zero [ri][ci+1]. - float diag_recip= 1.0f / diagonals[i]; - diagonals[i+1]-= diag_recip; - next_redge-= redge * diag_recip; - // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci]. - results[i+1]-= results[i] * diag_recip; - next_cedge-= cedge * diag_recip; - // Operation: Add col[i] * -(cedge/[ri][ci]) to col[e] to zero cedge. - diagonals[end]-= redge * (cedge / diagonals[i]); - cedge= next_cedge; // Do not use cedge after this point in the loop. - // Operation: Add row[i] * -(redge/[ri][ci]) to row[e] to zero redge. - results[end]-= results[i] * (redge / diagonals[i]); - redge= next_redge; // Do not use redge after this point in the loop. - } - // [rs][ce] is 1 - cedge, [re][cs] is 1 - redge - // Operation: Add col[s] * -([rs][ce] / [rs][cs]) to col[e] to zero redge. - diagonals[end]-= redge * ((1.0f - cedge) / diagonals[stop]); - // Operation: Add row[s] * -([re][cs] / [rs][cs]) to row[e] to zero redge. - results[end]-= results[stop] * ((1.0f - redge) / diagonals[stop]); - - set_results(last, diagonals, results); -} - -void CubicSpline::solve_straight() -{ - if(check_minimum_size()) { return; } - size_t last= m_points.size(); - vector results(m_points.size()); - vector diagonals(m_points.size()); - results[0]= 3 * (m_points[1].a - m_points[0].a); - diagonals[0]= 2.0f; - prep_inner(last, diagonals, results); - results[last-1]= 3 * (m_points[last-1].a - m_points[last-2].a); - diagonals[last-1]= 2.0f; - - // The system of equations to be solved looks like this: - // | 2 1 0 0 | = | results[0] | - // | 1 4 1 0 | = | results[1] | - // | 0 1 4 1 | = | results[2] | - // | 0 0 1 2 | = | results[3] | - // Operations are carefully chosen to only modify the values in the - // diagonals and the results, leaving the 1s unchanged. - // Operation: Add col[0] * -.5 to col[1] to zero [r0][c1]. - diagonals[1]-= .5f; - // Operation: Add row[0] * -.5 to row[1] to zero [r1][c0]. - results[1]-= results[0] * .5f; - for(size_t i= 1; i < last - 1; ++i) - { - float diag_recip= 1.0f / diagonals[i]; - // Operation: Add col[i] / -[ri][ci] to col[i+1] to zero [ri][ci+1]. - diagonals[i+1]-= diag_recip; - // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci]; - results[i]-= results[i-1] * diag_recip; - } - set_results(last, diagonals, results); -} - -bool CubicSpline::check_minimum_size() -{ - size_t last= m_points.size(); - if(last < 2) { return true; } - if(last == 2) - { - m_points[0].b= m_points[1].a - m_points[0].a; - m_points[0].c= m_points[0].d= 0.0f; - // These will be used in the looping case. - m_points[1].b= m_points[0].a - m_points[1].a; - m_points[1].c= m_points[1].d= 0.0f; - return true; - } - float a= m_points[0].a; - for(size_t i= 1; i < m_points.size(); ++i) - { - m_points[i].b= m_points[i].c= m_points[i].d= 0.0f; - if(m_points[i].a != a) { return false; } - } - return true; -} - -void CubicSpline::prep_inner(size_t last, vector& diagonals, vector& results) -{ - for(size_t i= 1; i < last - 1; ++i) - { - results[i]= 3 * (m_points[i+1].a - m_points[i-1].a); - diagonals[i]= 4.0f; - } -} - -void CubicSpline::set_results(size_t last, vector& diagonals, vector& results) -{ - // No more operations left, everything not a diagonal should be zero now. - for(size_t i= 0; i < last; ++i) - { - results[i]/= diagonals[i]; - } - // Now we can go through and set the b, c, d values of each point. - // b, c, d values of the last point are not set because they are unused. - for(size_t i= 0; i < last; ++i) - { - size_t next= (i+1) % last; - float diff= m_points[next].a - m_points[i].a; - m_points[i].b= results[i]; - m_points[i].c= (3 * diff) - (2 * results[i]) - results[next]; - m_points[i].d= (2 * -diff) + results[i] + results[next]; - } - // Solving is now complete. -} - -float CubicSpline::evaluate(float t, bool loop) -{ - if(loop) - { - t= fmodf(t, m_points.size()); - } - int flort= static_cast(t); - if(flort < 0) - { - return m_points[0].a; - } - size_t p= min(static_cast(flort), m_points.size()-1); - float tfrac= t - static_cast(flort); - float tsq= tfrac * tfrac; - float tcub= tsq * tfrac; - return m_points[p].a + (m_points[p].b * tfrac) + - (m_points[p].c * tsq) + (m_points[p].d * tcub); -} - -void CubicSpline::set_point(size_t i, float v) -{ - ASSERT_M(i < m_points.size(), "CubicSpline::set_point requires the index to be less than the number of points."); - m_points[i].a= v; -} - -void CubicSpline::resize(size_t s) -{ - m_points.resize(s); -} - -size_t CubicSpline::size() -{ - return m_points.size(); -} - -bool CubicSpline::empty() -{ - return m_points.empty(); -} - -void CubicSplineN::solve() -{ - if(loop) - { - for(spline_cont_t::iterator spline= m_splines.begin(); - spline != m_splines.end(); ++spline) - { - spline->solve_looped(); - } - } - else - { - for(spline_cont_t::iterator spline= m_splines.begin(); - spline != m_splines.end(); ++spline) - { - spline->solve_straight(); - } - } -} - -void CubicSplineN::evaluate(float t, vector& v) -{ - for(spline_cont_t::iterator spline= m_splines.begin(); - spline != m_splines.end(); ++spline) - { - v.push_back(spline->evaluate(t, loop)); - } -} - -void CubicSplineN::set_point(size_t i, vector const& v) -{ - ASSERT_M(v.size() == m_splines.size(), "CubicSplineN::set_point requires the passed point to be the same dimension as the spline."); - for(size_t n= 0; n < m_splines.size(); ++n) - { - m_splines[n].set_point(i, v[n]); - } -} - -void CubicSplineN::resize(size_t s) -{ - for(spline_cont_t::iterator spline= m_splines.begin(); - spline != m_splines.end(); ++spline) - { - spline->resize(s); - } -} - -size_t CubicSplineN::size() -{ - if(!m_splines.empty()) - { - return m_splines[0].size(); - } - return 0; -} - -bool CubicSplineN::empty() -{ - return m_splines.empty() || m_splines[0].empty(); -} - -void CubicSplineN::redimension(size_t d) -{ - m_splines.resize(d); -} - -size_t CubicSplineN::dimension() -{ - return m_splines.size(); -} - - - /* * Copyright (c) 2001-2006 Chris Danford, Glenn Maynard * All rights reserved. diff --git a/src/RageMath.h b/src/RageMath.h index 4565fc80fb..4e56aac737 100644 --- a/src/RageMath.h +++ b/src/RageMath.h @@ -85,43 +85,6 @@ private: RageQuadratic m_Y; }; -struct CubicSpline -{ - void solve_looped(); - void solve_straight(); - float evaluate(float t, bool loop); - void set_point(size_t i, float v); - void resize(size_t s); - size_t size(); - bool empty(); -private: - bool check_minimum_size(); - void prep_inner(size_t last, vector& diagonals, vector& results); - void set_results(size_t last, vector& diagonals, vector& results); - - struct SplinePoint - { - float a, b, c, d; - }; - vector m_points; -}; - -struct CubicSplineN -{ - void solve(); - void evaluate(float t, vector& v); - void set_point(size_t i, vector const& v); - void resize(size_t s); - size_t size(); - void redimension(size_t d); - size_t dimension(); - bool empty(); - typedef vector spline_cont_t; - bool loop; -private: - spline_cont_t m_splines; -}; - #endif /* From 0cb08dc8bde80e4119cad8c0689103feca0636b0 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Tue, 30 Dec 2014 13:59:06 -0700 Subject: [PATCH 05/14] Added offset mode spline to NoteColumnRenderer. --- src/CubicSpline.cpp | 22 ++--- src/CubicSpline.h | 16 ++-- src/GhostArrowRow.cpp | 9 +- src/GhostArrowRow.h | 1 + src/NoteDisplay.cpp | 184 +++++++++++++++++++++++++-------------- src/NoteDisplay.h | 61 ++++++++----- src/ReceptorArrowRow.cpp | 9 +- src/ReceptorArrowRow.h | 1 + 8 files changed, 194 insertions(+), 109 deletions(-) diff --git a/src/CubicSpline.cpp b/src/CubicSpline.cpp index cf38c462f7..685453930b 100644 --- a/src/CubicSpline.cpp +++ b/src/CubicSpline.cpp @@ -269,7 +269,7 @@ void CubicSpline::set_results(size_t last, vector& diagonals, vector max_t) { t-= max_t; } + while(t < 0.0f) { t+= max_t; } } int flort= static_cast(t); if(flort < 0) @@ -303,12 +305,12 @@ void CubicSpline::resize(size_t s) m_points.resize(s); } -size_t CubicSpline::size() +size_t CubicSpline::size() const { return m_points.size(); } -bool CubicSpline::empty() +bool CubicSpline::empty() const { return m_points.empty(); } @@ -335,9 +337,9 @@ void CubicSplineN::solve() m_dirty= false; } -void CubicSplineN::evaluate(float t, vector& v) +void CubicSplineN::evaluate(float t, vector& v) const { - for(spline_cont_t::iterator spline= m_splines.begin(); + for(spline_cont_t::const_iterator spline= m_splines.begin(); spline != m_splines.end(); ++spline) { v.push_back(spline->evaluate(t, loop)); @@ -364,7 +366,7 @@ void CubicSplineN::resize(size_t s) m_dirty= true; } -size_t CubicSplineN::size() +size_t CubicSplineN::size() const { if(!m_splines.empty()) { @@ -373,7 +375,7 @@ size_t CubicSplineN::size() return 0; } -bool CubicSplineN::empty() +bool CubicSplineN::empty() const { return m_splines.empty() || m_splines[0].empty(); } @@ -384,7 +386,7 @@ void CubicSplineN::redimension(size_t d) m_dirty= true; } -size_t CubicSplineN::dimension() +size_t CubicSplineN::dimension() const { return m_splines.size(); } @@ -440,7 +442,7 @@ struct LunaCubicSplineN : Luna { luaL_error(L, "Cannot set spline point at index less than 1."); } - if(i >= p->size()) + if(static_cast(i) >= p->size()) { luaL_error(L, "Cannot set spline point at index greater than size."); } diff --git a/src/CubicSpline.h b/src/CubicSpline.h index 1b5e16e322..2f5bd74dae 100644 --- a/src/CubicSpline.h +++ b/src/CubicSpline.h @@ -9,11 +9,11 @@ struct CubicSpline { void solve_looped(); void solve_straight(); - float evaluate(float t, bool loop); + float evaluate(float t, bool loop) const; void set_point(size_t i, float v); void resize(size_t s); - size_t size(); - bool empty(); + size_t size() const; + bool empty() const; private: bool check_minimum_size(); void prep_inner(size_t last, vector& results); @@ -29,16 +29,16 @@ private: struct CubicSplineN { CubicSplineN() - :m_dirty(true), m_owned_by_actor(false) + :m_owned_by_actor(false), m_dirty(true) {} void solve(); - void evaluate(float t, vector& v); + void evaluate(float t, vector& v) const; void set_point(size_t i, vector const& v); void resize(size_t s); - size_t size(); + size_t size() const; void redimension(size_t d); - size_t dimension(); - bool empty(); + size_t dimension() const; + bool empty() const; typedef vector spline_cont_t; bool loop; bool m_owned_by_actor; diff --git a/src/GhostArrowRow.cpp b/src/GhostArrowRow.cpp index 2afe66e1e0..70c49da3a3 100644 --- a/src/GhostArrowRow.cpp +++ b/src/GhostArrowRow.cpp @@ -42,6 +42,7 @@ void GhostArrowRow::SetColumnRenderers(vector& renderers) { m_Ghost[c]->SetFakeParent(&(renderers[c])); } + m_renderers= &renderers; } GhostArrowRow::~GhostArrowRow() @@ -55,15 +56,17 @@ void GhostArrowRow::Update( float fDeltaTime ) { for( unsigned c=0; c spline_pos; + (*m_renderers)[c].m_spline.evaluate((*m_renderers)[c].m_receptor_t, spline_pos); m_Ghost[c]->Update( fDeltaTime ); float fX = ArrowEffects::GetXPos( m_pPlayerState, c, 0 ); float fY = ArrowEffects::GetYPos( m_pPlayerState, c, 0, m_fYReverseOffsetPixels ); float fZ = ArrowEffects::GetZPos( m_pPlayerState, c, 0 ); - m_Ghost[c]->SetX( fX ); - m_Ghost[c]->SetY( fY ); - m_Ghost[c]->SetZ( fZ ); + m_Ghost[c]->SetX( fX + spline_pos[0] ); + m_Ghost[c]->SetY( fY + spline_pos[1] ); + m_Ghost[c]->SetZ( fZ + spline_pos[2] ); const float fRotation = ArrowEffects::ReceptorGetRotationZ( m_pPlayerState ); m_Ghost[c]->SetRotationZ( fRotation ); diff --git a/src/GhostArrowRow.h b/src/GhostArrowRow.h index c091b04a81..d2dc96e1b3 100644 --- a/src/GhostArrowRow.h +++ b/src/GhostArrowRow.h @@ -26,6 +26,7 @@ protected: float m_fYReverseOffsetPixels; const PlayerState* m_pPlayerState; + vector const* m_renderers; vector m_Ghost; vector m_bHoldShowing; vector m_bLastHoldShowing; diff --git a/src/NoteDisplay.cpp b/src/NoteDisplay.cpp index a9fe88813c..1e7d736933 100644 --- a/src/NoteDisplay.cpp +++ b/src/NoteDisplay.cpp @@ -342,6 +342,13 @@ bool NoteDisplay::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTarge return true; } +float NoteDisplay::BeatToTValue(CommonColumnRenderArgs const& args, float beat) const +{ + float song_beat= m_pPlayerState->GetDisplayedPosition().m_fSongBeatVisible; + float relative_beat= beat - song_beat; + return (relative_beat / args.beats_per_t) - args.receptor_t; +} + bool NoteDisplay::DrawHoldsInRange(CommonColumnRenderArgs const& args, int column, vector const& tap_set) { @@ -398,12 +405,9 @@ bool NoteDisplay::DrawHoldsInRange(CommonColumnRenderArgs const& args, end_row < *args.selection_end_marker); } - DrawHold(tn, column, start_row, is_holding, result, + 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); + in_selection_range ? args.selection_glow : args.fail_fade); bool note_upcoming = NoteRowToBeat(start_row) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat; @@ -485,12 +489,10 @@ bool NoteDisplay::DrawTapsInRange(CommonColumnRenderArgs const& args, bool is_addition = (tn.source == TapNoteSource_Addition); bool hopo_possible = (tn.bHopoPossible); bool use_addition_coloring = is_addition || hopo_possible; - DrawTap(tn, column, NoteRowToVisibleBeat(m_pPlayerState, tap_row), + 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); + in_selection_range ? args.selection_glow : args.fail_fade); any_upcoming |= NoteRowToBeat(tap_row) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat; @@ -611,12 +613,12 @@ struct StripBuffer int Free() const { return size - Used(); } }; -void NoteDisplay::DrawHoldPart( vector &vpSpr, int iCol, int fYStep, float fPercentFadeToFail, float fColorScale, bool bGlow, - float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar, - float fOverlappedTime, - float fYTop, float fYBottom, - float fYStartPos, float fYEndPos, - bool bWrapping, bool bAnchorToTop, bool bFlipTextureVertically ) +void NoteDisplay::DrawHoldPart(vector &vpSpr, + CommonColumnRenderArgs const& args, int iCol, int fYStep, + float fPercentFadeToFail, float fColorScale, bool bGlow, + float fOverlappedTime, float fYTop, float fYBottom, float fYStartPos, + float fYEndPos, bool bWrapping, bool bAnchorToTop, + bool bFlipTextureVertically, float top_t, float bottom_t) { ASSERT( !vpSpr.empty() ); @@ -681,12 +683,20 @@ void NoteDisplay::DrawHoldPart( vector &vpSpr, int iCol, int fYStep, fl bLast = true; } + float curtsy= top_t; + if(top_t != bottom_t) + { + curtsy= SCALE(fY, fYTop, fYBottom, top_t, bottom_t); + } + vector spline_pos; + args.spline->evaluate(curtsy, spline_pos); + const float fYOffset = ArrowEffects::GetYOffsetFromYPos( m_pPlayerState, iCol, fY, m_fYReverseOffsetPixels ); - const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ); + const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ) + spline_pos[2]; const float fFrameWidthScale = ArrowEffects::GetFrameWidthScale( m_pPlayerState, fYOffset, fOverlappedTime ); const float fScaledFrameWidth = fFrameWidth * fFrameWidthScale; - float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ); + float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ) + spline_pos[0]; // XXX: Actor rotations use degrees, RageFastCos/Sin use radians. Convert here. const float fRotationY = ArrowEffects::GetRotationY( m_pPlayerState, fYOffset ) * PI/180; @@ -708,15 +718,15 @@ void NoteDisplay::DrawHoldPart( vector &vpSpr, int iCol, int fYStep, fl float fTexCoordTop = SCALE( fDistFromTop, 0, fFrameHeight, rect.top, rect.bottom ); fTexCoordTop += fAddToTexCoord; - const float fAlpha = ArrowGetAlphaOrGlow( bGlow, m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar ); + const float fAlpha = ArrowGetAlphaOrGlow( bGlow, m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, args.draw_pixels_before_targets, args.fade_before_targets ); 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(fXCenter, fY, fZCenter); queue.v[1].c = color; queue.v[1].t = RageVector2(fTexCoordCenter, fTexCoordTop); - queue.v[2].p = RageVector3(fXRight, fY, fZRight); queue.v[2].c = color; queue.v[2].t = RageVector2(fTexCoordRight, fTexCoordTop); + queue.v[0].p = RageVector3(fXLeft, fY + spline_pos[1], fZLeft); queue.v[0].c = color; queue.v[0].t = RageVector2(fTexCoordLeft, fTexCoordTop); + queue.v[1].p = RageVector3(fXCenter, fY + spline_pos[1], fZCenter); queue.v[1].c = color; queue.v[1].t = RageVector2(fTexCoordCenter, fTexCoordTop); + queue.v[2].p = RageVector3(fXRight, fY + spline_pos[1], fZRight); queue.v[2].c = color; queue.v[2].t = RageVector2(fTexCoordRight, fTexCoordTop); queue.v+=3; if( queue.Free() < 3 || bLast ) @@ -742,8 +752,11 @@ void NoteDisplay::DrawHoldPart( vector &vpSpr, int iCol, int fYStep, fl } } -void NoteDisplay::DrawHoldBody( const TapNote& tn, int iCol, float fBeat, bool bIsBeingHeld, float fYHead, float fYTail, bool /* bIsAddition */, float fPercentFadeToFail, float fColorScale, bool bGlow, - float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar ) +void NoteDisplay::DrawHoldBody(const TapNote& tn, + CommonColumnRenderArgs const& args, int iCol, float fBeat, + bool bIsBeingHeld, float fYHead, float fYTail, bool bIsAddition, + float fPercentFadeToFail, float fColorScale, bool bGlow, + float top_t, float bottom_t) { vector vpSprTop; Sprite *pSpriteTop = GetHoldSprite( m_HoldTopCap, NotePart_HoldTopCap, fBeat, tn.subType == TapNoteSubType_Roll, bIsBeingHeld && !cache->m_bHoldActiveIsAddLayer ); @@ -801,48 +814,51 @@ void NoteDisplay::DrawHoldBody( const TapNote& tn, int iCol, float fBeat, bool b const float fFrameHeightTop = pSpriteTop->GetUnzoomedHeight(); const float fFrameHeightBottom = pSpriteBottom->GetUnzoomedHeight(); - float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fDrawDistanceAfterTargetsPixels, m_fYReverseOffsetPixels ); - float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fDrawDistanceBeforeTargetsPixels, m_fYReverseOffsetPixels ); + float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, + args.draw_pixels_after_targets, m_fYReverseOffsetPixels ); + float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, + args.draw_pixels_before_targets, m_fYReverseOffsetPixels ); if( bReverse ) swap( fYStartPos, fYEndPos ); bool bTopAnchor = bReverse && cache->m_bTopHoldAnchorWhenReverse; // Draw the top cap - DrawHoldPart( - vpSprTop, - iCol, fYStep, fPercentFadeToFail, fColorScale, bGlow, - fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar, + DrawHoldPart(vpSprTop, args, iCol, fYStep, fPercentFadeToFail, + fColorScale, bGlow, tn.HoldResult.fOverlappedTime, fYHead-fFrameHeightTop, fYHead, fYStartPos, fYEndPos, - false, bTopAnchor, bFlipHoldBody ); + false, bTopAnchor, bFlipHoldBody, top_t, top_t); // Draw the body - DrawHoldPart( - vpSprBody, - iCol, fYStep, fPercentFadeToFail, fColorScale, bGlow, - fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar, + DrawHoldPart(vpSprBody, args, iCol, fYStep, fPercentFadeToFail, + fColorScale, bGlow, tn.HoldResult.fOverlappedTime, fYHead, fYTail, fYStartPos, fYEndPos, - true, bTopAnchor, bFlipHoldBody ); + true, bTopAnchor, bFlipHoldBody, top_t, bottom_t); // Draw the bottom cap - DrawHoldPart( - vpSprBottom, - iCol, fYStep, fPercentFadeToFail, fColorScale, bGlow, - fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar, + DrawHoldPart(vpSprBottom, args, iCol, fYStep, fPercentFadeToFail, + fColorScale, bGlow, tn.HoldResult.fOverlappedTime, fYTail, fYTail+fFrameHeightBottom, max(fYStartPos, fYHead), fYEndPos, - false, bTopAnchor, bFlipHoldBody ); + false, bTopAnchor, bFlipHoldBody, bottom_t, bottom_t); } -void NoteDisplay::DrawHold( const TapNote &tn, int iCol, int iRow, bool bIsBeingHeld, const HoldNoteResult &Result, bool bIsAddition, float fPercentFadeToFail, - float fReverseOffsetPixels, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fDrawDistanceBeforeTargetsPixels2, float fFadeInPercentOfDrawFar ) +void NoteDisplay::DrawHold(const TapNote& tn, + CommonColumnRenderArgs const& args, int iCol, int iRow, bool bIsBeingHeld, + const HoldNoteResult &Result, bool bIsAddition, float fPercentFadeToFail) { int iEndRow = iRow + tn.iDuration; + float top_t= BeatToTValue(args, NoteRowToVisibleBeat(m_pPlayerState, iRow)); + float bottom_t= BeatToTValue(args, NoteRowToVisibleBeat(m_pPlayerState, iEndRow)); + if(bIsBeingHeld) + { + top_t= args.receptor_t; + } // bDrawGlowOnly is a little hacky. We need to draw the diffuse part and the glow part one pass at a time to minimize state changes @@ -873,8 +889,8 @@ void NoteDisplay::DrawHold( const TapNote &tn, int iCol, int iRow, bool bIsBeing if( bReverse ) swap( fStartYOffset, fEndYOffset ); - const float fYHead = ArrowEffects::GetYPos( m_pPlayerState, iCol, fStartYOffset, fReverseOffsetPixels ); - const float fYTail = ArrowEffects::GetYPos( m_pPlayerState, iCol, fEndYOffset, fReverseOffsetPixels ); + const float fYHead = ArrowEffects::GetYPos( m_pPlayerState, iCol, fStartYOffset, m_fYReverseOffsetPixels ); + const float fYTail = ArrowEffects::GetYPos( m_pPlayerState, iCol, fEndYOffset, m_fYReverseOffsetPixels ); const float fColorScale = SCALE( tn.HoldResult.fLife, 0.0f, 1.0f, cache->m_fHoldLetGoGrayPercent, 1.0f ); @@ -896,8 +912,8 @@ void NoteDisplay::DrawHold( const TapNote &tn, int iCol, int iRow, bool bIsBeing } */ - DrawHoldBody( tn, iCol, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, false, fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar ); - DrawHoldBody( tn, iCol, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, true, fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar ); + DrawHoldBody(tn, args, iCol, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, false, top_t, bottom_t); + DrawHoldBody(tn, args, iCol, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, true, top_t, bottom_t); /* These set the texture mode themselves. */ // this part was modified in pumpmania, where it flips the draw order @@ -905,25 +921,33 @@ void NoteDisplay::DrawHold( const TapNote &tn, int iCol, int iRow, bool bIsBeing if( cache->m_bHoldTailIsAboveWavyParts ) { Actor *pActor = GetHoldActor( m_HoldTail, NotePart_HoldTail, NoteRowToBeat(iRow), tn.subType == TapNoteSubType_Roll, bIsBeingHeld ); - DrawActor( tn, pActor, NotePart_HoldTail, iCol, bFlipHeadAndTail ? fStartYOffset : fEndYOffset, fBeat, bIsAddition, fPercentFadeToFail, fReverseOffsetPixels, fColorScale, fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar ); + DrawActor(tn, pActor, NotePart_HoldTail, args, iCol, bFlipHeadAndTail ? fStartYOffset : fEndYOffset, fBeat, bIsAddition, fPercentFadeToFail, fColorScale, false); } if( cache->m_bHoldHeadIsAboveWavyParts ) { Actor *pActor = GetHoldActor( m_HoldHead, NotePart_HoldHead, NoteRowToBeat(iRow), tn.subType == TapNoteSubType_Roll, bIsBeingHeld ); - DrawActor( tn, pActor, NotePart_HoldHead, iCol, bFlipHeadAndTail ? fEndYOffset : fStartYOffset, fBeat, bIsAddition, fPercentFadeToFail, fReverseOffsetPixels, fColorScale, fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar ); + DrawActor(tn, pActor, NotePart_HoldHead, args, iCol, bFlipHeadAndTail ? fEndYOffset : fStartYOffset, fBeat, bIsAddition, fPercentFadeToFail, fColorScale, bIsBeingHeld); } } -void NoteDisplay::DrawActor( const TapNote& tn, Actor* pActor, NotePart part, int iCol, float fYOffset, float fBeat, bool bIsAddition, float fPercentFadeToFail, float fReverseOffsetPixels, float fColorScale, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar ) +void NoteDisplay::DrawActor(const TapNote& tn, Actor* pActor, NotePart part, + CommonColumnRenderArgs const& args, int iCol, float fYOffset, float fBeat, + bool bIsAddition, float fPercentFadeToFail, float fColorScale, + bool is_being_held) { if (tn.type == TapNoteType_AutoKeysound && !GAMESTATE->m_bInStepEditor) return; - if( fYOffset < fDrawDistanceAfterTargetsPixels || fYOffset > fDrawDistanceBeforeTargetsPixels ) + if(fYOffset < args.draw_pixels_after_targets || + fYOffset > args.draw_pixels_before_targets) return; - const float fY = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYOffset, fReverseOffsetPixels ); - const float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ); - const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ); - const float fAlpha = ArrowEffects::GetAlpha( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar ); - const float fGlow = ArrowEffects::GetGlow( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar ); + float spline_t= BeatToTValue(args, fBeat); + if(is_being_held) { spline_t= args.receptor_t; } + vector spline_pos; + args.spline->evaluate(spline_t, spline_pos); + const float fY = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYOffset, m_fYReverseOffsetPixels ) + spline_pos[1]; + const float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ) + spline_pos[0]; + const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ) + spline_pos[2]; + const float fAlpha = ArrowEffects::GetAlpha( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, args.draw_pixels_before_targets, args.fade_before_targets ); + const float fGlow = ArrowEffects::GetGlow( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, args.draw_pixels_before_targets, args.fade_before_targets ); const RageColor diffuse = RageColor(fColorScale,fColorScale,fColorScale,fAlpha); const RageColor glow = RageColor(1,1,1,fGlow); float fRotationX = 0, fRotationZ = 0; @@ -979,13 +1003,10 @@ void NoteDisplay::DrawActor( const TapNote& tn, Actor* pActor, NotePart part, in } } -void NoteDisplay::DrawTap(const TapNote& tn, int iCol, float fBeat, - bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollStart, - bool bIsAddition, float fPercentFadeToFail, - float fReverseOffsetPixels, - float fDrawDistanceAfterTargetsPixels, - float fDrawDistanceBeforeTargetsPixels, - float fFadeInPercentOfDrawFar) +void NoteDisplay::DrawTap(const TapNote& tn, + CommonColumnRenderArgs const& args, int iCol, float fBeat, + bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollStart, + bool bIsAddition, float fPercentFadeToFail) { Actor* pActor = NULL; NotePart part = NotePart_Tap; @@ -1059,7 +1080,7 @@ void NoteDisplay::DrawTap(const TapNote& tn, int iCol, float fBeat, const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fBeat ); // this is the line that forces the (1,1,1,x) part of the noteskin diffuse -aj - DrawActor( tn, pActor, part, iCol, fYOffset, fBeat, bIsAddition, fPercentFadeToFail, fReverseOffsetPixels, 1.0f, fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar ); + DrawActor(tn, pActor, part, args, iCol, fYOffset, fBeat, bIsAddition, fPercentFadeToFail, 1.0f, false); if( tn.type == TapNoteType_Attack ) pActor->PlayCommand( "UnsetAttack" ); @@ -1067,6 +1088,11 @@ void NoteDisplay::DrawTap(const TapNote& tn, int iCol, float fBeat, void NoteColumnRenderer::DrawPrimitives() { + m_render_args->spline= &m_spline; + m_render_args->receptor_t= m_receptor_t; + m_render_args->beats_per_t= m_beats_per_t; + m_render_args->first_beat= NoteRowToBeat(m_render_args->first_row); + m_render_args->last_beat= NoteRowToBeat(m_render_args->last_row); bool any_upcoming= false; // Build lists of holds and taps for each player number, then pass those // lists to the displays to draw. @@ -1123,6 +1149,38 @@ void NoteColumnRenderer::DrawPrimitives() m_render_args->receptor_row->SetNoteUpcoming(m_column, any_upcoming); } +#include "LuaBinding.h" + +struct LunaNoteColumnRenderer : Luna +{ + static int get_spline(T* p, lua_State* L) + { + p->m_spline.PushSelf(L); + return 1; + } + DEFINE_METHOD(get_receptor_t, m_receptor_t); + DEFINE_METHOD(get_beats_per_t, m_beats_per_t); +#define SET_T(member, name) \ + static int name(T* p, lua_State* L) \ + { \ + p->member= FArg(1); \ + COMMON_RETURN_SELF; \ + } + SET_T(m_receptor_t, set_receptor_t); + SET_T(m_beats_per_t, set_beats_per_t); + + LunaNoteColumnRenderer() + { + ADD_METHOD(get_spline); + ADD_METHOD(get_receptor_t); + ADD_METHOD(get_beats_per_t); + ADD_METHOD(set_receptor_t); + ADD_METHOD(set_beats_per_t); + } +}; + +LUA_REGISTER_DERIVED_CLASS(NoteColumnRenderer, Actor) + /* * (c) 2001-2006 Brian Bugh, Ben Nordstrom, Chris Danford, Steve Checkoway * All rights reserved. diff --git a/src/NoteDisplay.h b/src/NoteDisplay.h index ade622f321..43121564b7 100644 --- a/src/NoteDisplay.h +++ b/src/NoteDisplay.h @@ -2,6 +2,7 @@ #define NOTE_DISPLAY_H #include "ActorFrame.h" +#include "CubicSpline.h" #include "NoteData.h" #include "PlayerNumber.h" #include "GameInput.h" @@ -93,10 +94,15 @@ struct CommonColumnRenderArgs ReceptorArrowRow* receptor_row; GhostArrowRow* ghost_row; NoteData const* note_data; + CubicSplineN const* spline; + float receptor_t; + float beats_per_t; + float first_beat; + float last_beat; int first_row; int last_row; - int draw_pixels_before_targets; - int draw_pixels_after_targets; + float draw_pixels_before_targets; + float draw_pixels_after_targets; int* selection_begin_marker; int* selection_end_marker; float selection_glow; @@ -116,6 +122,7 @@ public: static void Update( float fDeltaTime ); bool IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const; + float BeatToTValue(CommonColumnRenderArgs const& args, float beat) const; bool DrawHoldsInRange(CommonColumnRenderArgs const& args, int column, vector const& tap_set); @@ -134,19 +141,14 @@ public: * @param fDrawDistanceAfterTargetsPixels how much to draw after the receptors. * @param fDrawDistanceBeforeTargetsPixels how much ot draw before the receptors. * @param fFadeInPercentOfDrawFar when to start fading in. */ - void DrawTap(const TapNote& tn, int iCol, float fBeat, - bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollBeat, - bool bIsAddition, float fPercentFadeToFail, - float fReverseOffsetPixels, - float fDrawDistanceAfterTargetsPixels, - float fDrawDistanceBeforeTargetsPixels, - float fFadeInPercentOfDrawFar ); - void DrawHold( const TapNote& tn, int iCol, int iRow, bool bIsBeingHeld, const HoldNoteResult &Result, - bool bIsAddition, float fPercentFadeToFail, float fReverseOffsetPixels, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, - float fDrawDistanceBeforeTargetsPixels2, float fFadeInPercentOfDrawFar ); - + void DrawTap(const TapNote& tn, CommonColumnRenderArgs const& args, + int iCol, float fBeat, bool bOnSameRowAsHoldStart, + bool bOnSameRowAsRollBeat, bool bIsAddition, float fPercentFadeToFail); + void DrawHold(const TapNote& tn, CommonColumnRenderArgs const& args, + int iCol, int iRow, bool bIsBeingHeld, const HoldNoteResult &Result, + bool bIsAddition, float fPercentFadeToFail); + bool DrawHoldHeadForTapsOnSameRow() const; - bool DrawRollHeadForTapsOnSameRow() const; private: @@ -155,14 +157,20 @@ private: Actor *GetHoldActor( NoteColorActor nca[NUM_HoldType][NUM_ActiveType], NotePart part, float fNoteBeat, bool bIsRoll, bool bIsBeingHeld ); Sprite *GetHoldSprite( NoteColorSprite ncs[NUM_HoldType][NUM_ActiveType], NotePart part, float fNoteBeat, bool bIsRoll, bool bIsBeingHeld ); - void DrawActor( const TapNote& tn, Actor* pActor, NotePart part, int iCol, float fYOffset, float fBeat, bool bIsAddition, float fPercentFadeToFail, - float fReverseOffsetPixels, float fColorScale, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar ); - void DrawHoldBody( const TapNote& tn, int iCol, float fBeat, bool bIsBeingHeld, float fYHead, float fYTail, bool bIsAddition, float fPercentFadeToFail, - float fColorScale, - bool bGlow, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar ); - void DrawHoldPart( vector &vpSpr, int iCol, int fYStep, float fPercentFadeToFail, float fColorScale, bool bGlow, - float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar, float fOverlappedTime, - float fYTop, float fYBottom, float fYStartPos, float fYEndPos, bool bWrapping, bool bAnchorToTop, bool bFlipTextureVertically ); + void DrawActor(const TapNote& tn, Actor* pActor, NotePart part, + CommonColumnRenderArgs const& args, int iCol, float fYOffset, float fBeat, + bool bIsAddition, float fPercentFadeToFail, float fColorScale, + bool is_being_held); + void DrawHoldBody(const TapNote& tn, CommonColumnRenderArgs const& args, + int iCol, float fBeat, bool bIsBeingHeld, float fYHead, float fYTail, + bool bIsAddition, float fPercentFadeToFail, float fColorScale, + bool bGlow, float top_t, float bottom_t); + void DrawHoldPart(vector &vpSpr, + CommonColumnRenderArgs const& args, int iCol, int fYStep, + float fPercentFadeToFail, float fColorScale, bool bGlow, + float fOverlappedTime, float fYTop, float fYBottom, float fYStartPos, + float fYEndPos, bool bWrapping, bool bAnchorToTop, + bool bFlipTextureVertically, float top_t, float bottom_t); const PlayerState *m_pPlayerState; // to look up PlayerOptions NoteMetricCache_t *cache; @@ -191,11 +199,20 @@ private: class NoteColumnRenderer : public Actor { public: + NoteColumnRenderer() + { + m_spline.redimension(3); + m_spline.m_owned_by_actor= true; + } NoteDisplay* m_displays[PLAYER_INVALID+1]; CommonColumnRenderArgs* m_render_args; int m_column; + float m_receptor_t; + float m_beats_per_t; + CubicSplineN m_spline; virtual void DrawPrimitives(); + virtual void PushSelf(lua_State* L); }; #endif diff --git a/src/ReceptorArrowRow.cpp b/src/ReceptorArrowRow.cpp index 8c7f6dc54c..19bce08bfc 100644 --- a/src/ReceptorArrowRow.cpp +++ b/src/ReceptorArrowRow.cpp @@ -37,6 +37,7 @@ void ReceptorArrowRow::SetColumnRenderers(vector& renderers) { m_ReceptorArrow[c]->SetFakeParent(&(renderers[c])); } + m_renderers= &renderers; } ReceptorArrowRow::~ReceptorArrowRow() @@ -52,6 +53,8 @@ void ReceptorArrowRow::Update( float fDeltaTime ) for( unsigned c=0; c spline_pos; + (*m_renderers)[c].m_spline.evaluate((*m_renderers)[c].m_receptor_t, spline_pos); // m_fDark==1 or m_fFadeToFailPercent==1 should make fBaseAlpha==0 float fBaseAlpha = (1 - m_pPlayerState->m_PlayerOptions.GetCurrent().m_fDark); if( m_fFadeToFailPercent != -1 ) @@ -65,9 +68,9 @@ void ReceptorArrowRow::Update( float fDeltaTime ) float fX = ArrowEffects::GetXPos( m_pPlayerState, c, 0 ); const float fY = ArrowEffects::GetYPos( m_pPlayerState, c, 0, m_fYReverseOffsetPixels ); const float fZ = ArrowEffects::GetZPos( m_pPlayerState, c, 0 ); - m_ReceptorArrow[c]->SetX( fX ); - m_ReceptorArrow[c]->SetY( fY ); - m_ReceptorArrow[c]->SetZ( fZ ); + m_ReceptorArrow[c]->SetX( fX + spline_pos[0] ); + m_ReceptorArrow[c]->SetY( fY + spline_pos[1] ); + m_ReceptorArrow[c]->SetZ( fZ + spline_pos[2] ); const float fRotation = ArrowEffects::ReceptorGetRotationZ( m_pPlayerState ); m_ReceptorArrow[c]->SetRotationZ( fRotation ); diff --git a/src/ReceptorArrowRow.h b/src/ReceptorArrowRow.h index ac6f081643..a65cd407bb 100644 --- a/src/ReceptorArrowRow.h +++ b/src/ReceptorArrowRow.h @@ -30,6 +30,7 @@ protected: float m_fYReverseOffsetPixels; float m_fFadeToFailPercent; + vector const* m_renderers; vector m_ReceptorArrow; }; From 84e758c6c786a1ea59531edeb2336eccdbb2050c Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Thu, 1 Jan 2015 04:36:20 -0700 Subject: [PATCH 06/14] Added rotation and zoom splines to note columns, with the help of a couple new classes. Reorganized args to NoteDisplay functions into helper structures. Made splines have 3 modes controlling their behavior. Hold twisting is now handled by an AA rotation and hold drawing in general is now more complex. --- Docs/Luadoc/Lua.xml | 2 +- Docs/Luadoc/LuaDocumentation.xml | 2 +- src/ArrowEffects.h | 8 + src/CubicSpline.cpp | 203 +++++++++- src/CubicSpline.h | 8 + src/GhostArrowRow.cpp | 17 +- src/NoteDisplay.cpp | 642 ++++++++++++++++++++++++------- src/NoteDisplay.h | 126 ++++-- src/NoteField.cpp | 103 ++--- src/NoteField.h | 2 +- src/RageMath.cpp | 31 ++ src/RageMath.h | 3 + src/ReceptorArrowRow.cpp | 15 +- 13 files changed, 886 insertions(+), 276 deletions(-) diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index 590bfa43f0..675690c28a 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -970,7 +970,7 @@ - + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index f7cb109daa..88266af252 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -2959,7 +2959,7 @@ save yourself some time, copy this for undocumented things: Makes the NoteField act as if a tap note was hit in the column, with the given score and bright setting.
The callback for DidTapNote will not be called.
- + Returns a table of the actors for the columns. diff --git a/src/ArrowEffects.h b/src/ArrowEffects.h index 1ee054d333..8629c8bd33 100644 --- a/src/ArrowEffects.h +++ b/src/ArrowEffects.h @@ -19,6 +19,14 @@ public: return GetYOffset( pPlayerState, iCol, fNoteBeat, fThrowAway, bThrowAway, bAbsolute ); } + static void GetXYZPos(const PlayerState* player_state, int col, float y_offset, float y_reverse_offset, vector& ret, bool with_reverse= true) + { + ASSERT(ret.size() == 3); + ret[0]= GetXPos(player_state, col, y_offset); + ret[1]= GetYPos(player_state, col, y_offset, y_reverse_offset, with_reverse); + ret[2]= GetZPos(player_state, col, y_offset); + } + /** * @brief Retrieve the actual display position. * diff --git a/src/CubicSpline.cpp b/src/CubicSpline.cpp index 685453930b..8716d19774 100644 --- a/src/CubicSpline.cpp +++ b/src/CubicSpline.cpp @@ -265,6 +265,11 @@ void CubicSpline::set_results(size_t last, vector& diagonals, vector(t); if(loop) { - float max_t= m_points.size() + 1.0f; - while(t > max_t) { t-= max_t; } + float max_t= m_points.size(); + while(t >= max_t) { t-= max_t; } while(t < 0.0f) { t+= max_t; } + flort= static_cast(t); } - int flort= static_cast(t); - if(flort < 0) + else { - return m_points[0].a; + if(flort <= 0) + { + return m_points[0].a; + } + else if(static_cast(flort) >= m_points.size() - 1) + { + return m_points[m_points.size() - 1].a; + } } size_t p= min(static_cast(flort), m_points.size()-1); float tfrac= t - static_cast(flort); @@ -294,12 +307,56 @@ float CubicSpline::evaluate(float t, bool loop) const (m_points[p].c * tsq) + (m_points[p].d * tcub); } +float CubicSpline::evaluate_derivative(float t, bool loop) const +{ + if(m_points.empty()) + { + return 0.0f; + } + int flort= static_cast(t); + if(loop) + { + float max_t= m_points.size(); + while(t >= max_t) { t-= max_t; } + while(t < 0.0f) { t+= max_t; } + flort= static_cast(t); + } + else + { + if(static_cast(flort) >= m_points.size() - 1) + { + return 0.0f; + } + } + size_t p= min(static_cast(flort), m_points.size()-1); + float tfrac= t - static_cast(flort); + float tsq= tfrac * tfrac; + return m_points[p].b + (2.0f * m_points[p].c * tfrac) + + (3.0f * m_points[p].d * tsq); +} + void CubicSpline::set_point(size_t i, float v) { ASSERT_M(i < m_points.size(), "CubicSpline::set_point requires the index to be less than the number of points."); m_points[i].a= v; } +void CubicSpline::set_coefficients(size_t i, float b, float c, float d) +{ + ASSERT_M(i < m_points.size(), "CubicSpline: point index must be less than the number of points."); + m_points[i].b= b; + m_points[i].c= c; + m_points[i].d= d; +} + +void CubicSpline::get_coefficients(size_t i, float& b, float& c, float& d) +{ + ASSERT_M(i < m_points.size(), "CubicSpline: point index must be less than the number of points."); + b= m_points[i].b; + c= m_points[i].c; + d= m_points[i].d; +} + void CubicSpline::resize(size_t s) { m_points.resize(s); @@ -346,6 +403,15 @@ void CubicSplineN::evaluate(float t, vector& v) const } } +void CubicSplineN::evaluate_derivative(float t, vector& v) const +{ + for(spline_cont_t::const_iterator spline= m_splines.begin(); + spline != m_splines.end(); ++spline) + { + v.push_back(spline->evaluate_derivative(t, loop)); + } +} + void CubicSplineN::set_point(size_t i, vector const& v) { ASSERT_M(v.size() == m_splines.size(), "CubicSplineN::set_point requires the passed point to be the same dimension as the spline."); @@ -356,6 +422,30 @@ void CubicSplineN::set_point(size_t i, vector const& v) m_dirty= true; } +void CubicSplineN::set_coefficients(size_t i, vector const& b, + vector const& c, vector const& d) +{ + ASSERT_M(b.size() == c.size() && c.size() == d.size() && + d.size() == m_splines.size(), "CubicSplineN: coefficient vectors must be " + "the same dimension as the spline."); + for(size_t n= 0; n < m_splines.size(); ++n) + { + m_splines[n].set_coefficients(i, b[n], c[n], d[n]); + } +} + +void CubicSplineN::get_coefficients(size_t i, vector& b, + vector& c, vector& d) +{ + ASSERT_M(b.size() == c.size() && c.size() == d.size() && + d.size() == m_splines.size(), "CubicSplineN: coefficient vectors must be " + "the same dimension as the spline."); + for(size_t n= 0; n < m_splines.size(); ++n) + { + m_splines[n].get_coefficients(i, b[n], c[n], d[n]); + } +} + void CubicSplineN::resize(size_t s) { for(spline_cont_t::iterator spline= m_splines.begin(); @@ -412,31 +502,47 @@ struct LunaCubicSplineN : Luna } return 1; } + static int evaluate_derivative(T* p, lua_State* L) + { + vector pos; + p->evaluate_derivative(FArg(1), pos); + lua_createtable(L, pos.size(), 0); + for(size_t i= 0; i < pos.size(); ++i) + { + lua_pushnumber(L, pos[i]); + lua_rawseti(L, -2, i+1); + } + return 1; + } + static void get_element_table_from_stack(T* p, lua_State* L, int s, + size_t limit, vector& ret) + { + size_t elements= lua_objlen(L, s); + // Too many elements is not an error because allowing it allows the user + // to reuse the same position data set after changing the dimension size. + // The same is true for too few elements. + for(size_t e= 0; e < elements; ++e) + { + lua_rawgeti(L, s, e+1); + ret.push_back(FArg(-1)); + } + while(ret.size() < limit) + { + ret.push_back(0.0f); + } + } static void set_point_from_stack(T* p, lua_State* L, size_t i, int s) { if(!lua_istable(L, s)) { luaL_error(L, "Spline point must be a table."); } - size_t elements= lua_objlen(L, s); - // Too many elements is not an error because allowing it allows the user - // to reuse the same position data set after changing the dimension size. - // The same is true for too few elements. vector pos; - for(size_t e= 0; e < elements; ++e) - { - lua_rawgeti(L, s, e+1); - pos.push_back(FArg(-1)); - } - while(pos.size() < p->dimension()) - { - pos.push_back(0.0f); - } + get_element_table_from_stack(p, L, s, p->dimension(), pos); p->set_point(i, pos); } static int set_point(T* p, lua_State* L) { - vector pos; int i= IArg(1)-1; if(i < 0) { @@ -449,6 +555,62 @@ struct LunaCubicSplineN : Luna set_point_from_stack(p, L, static_cast(i), 2); COMMON_RETURN_SELF; } + static void set_coefficients_from_stack(T* p, lua_State* L, size_t i, int s) + { + if(!lua_istable(L, s) || !lua_istable(L, s+1) || !lua_istable(L, s+2)) + { + luaL_error(L, "Spline coefficient args must be three tables."); + } + size_t limit= p->dimension(); + vector b; get_element_table_from_stack(p, L, s, limit, b); + vector c; get_element_table_from_stack(p, L, s+1, limit, c); + vector d; get_element_table_from_stack(p, L, s+2, limit, d); + p->set_coefficients(i, b, c, d); + } + static int set_coefficients(T* p, lua_State* L) + { + int i= IArg(1)-1; + if(i < 0) + { + luaL_error(L, "Cannot set spline coefficients at index less than 1."); + } + if(static_cast(i) >= p->size()) + { + luaL_error(L, "Cannot set spline coefficients at index greater than size."); + } + set_coefficients_from_stack(p, L, i, 2); + COMMON_RETURN_SELF; + } + static int get_coefficients(T* p, lua_State* L) + { + int i= IArg(1)-1; + if(i < 0) + { + luaL_error(L, "Cannot get spline coefficients at index less than 1."); + } + if(static_cast(i) >= p->size()) + { + luaL_error(L, "Cannot get spline coefficients at index greater than size."); + } + size_t limit= p->dimension(); + vector > coeff(3); + coeff[0].resize(limit); + coeff[1].resize(limit); + coeff[2].resize(limit); + p->get_coefficients(i, coeff[0], coeff[1], coeff[2]); + lua_createtable(L, 3, 0); + for(size_t co= 0; co < coeff.size(); ++co) + { + lua_createtable(L, limit, 0); + for(size_t v= 0; v < limit; ++v) + { + lua_pushnumber(L, coeff[co][v]); + lua_rawseti(L, -2, v+1); + } + lua_rawseti(L, -2, co+1); + } + return 1; + } static int resize(T* p, lua_State* L) { int siz= IArg(1); @@ -503,7 +665,10 @@ struct LunaCubicSplineN : Luna { ADD_METHOD(solve); ADD_METHOD(evaluate); + ADD_METHOD(evaluate_derivative); ADD_METHOD(set_point); + ADD_METHOD(set_coefficients); + ADD_METHOD(get_coefficients); ADD_METHOD(resize); ADD_METHOD(size); ADD_METHOD(redimension); diff --git a/src/CubicSpline.h b/src/CubicSpline.h index 2f5bd74dae..1f71f7fbda 100644 --- a/src/CubicSpline.h +++ b/src/CubicSpline.h @@ -10,7 +10,10 @@ struct CubicSpline void solve_looped(); void solve_straight(); float evaluate(float t, bool loop) const; + float evaluate_derivative(float t, bool loop) const; void set_point(size_t i, float v); + void set_coefficients(size_t i, float b, float c, float d); + void get_coefficients(size_t i, float& b, float& c, float& d); void resize(size_t s); size_t size() const; bool empty() const; @@ -33,7 +36,12 @@ struct CubicSplineN {} void solve(); void evaluate(float t, vector& v) const; + void evaluate_derivative(float t, vector& v) const; void set_point(size_t i, vector const& v); + void set_coefficients(size_t i, vector const& b, + vector const& c, vector const& d); + void get_coefficients(size_t i, vector& b, + vector& c, vector& d); void resize(size_t s); size_t size() const; void redimension(size_t d); diff --git a/src/GhostArrowRow.cpp b/src/GhostArrowRow.cpp index 70c49da3a3..65535363d9 100644 --- a/src/GhostArrowRow.cpp +++ b/src/GhostArrowRow.cpp @@ -56,23 +56,8 @@ void GhostArrowRow::Update( float fDeltaTime ) { for( unsigned c=0; c spline_pos; - (*m_renderers)[c].m_spline.evaluate((*m_renderers)[c].m_receptor_t, spline_pos); m_Ghost[c]->Update( fDeltaTime ); - - float fX = ArrowEffects::GetXPos( m_pPlayerState, c, 0 ); - float fY = ArrowEffects::GetYPos( m_pPlayerState, c, 0, m_fYReverseOffsetPixels ); - float fZ = ArrowEffects::GetZPos( m_pPlayerState, c, 0 ); - - m_Ghost[c]->SetX( fX + spline_pos[0] ); - m_Ghost[c]->SetY( fY + spline_pos[1] ); - m_Ghost[c]->SetZ( fZ + spline_pos[2] ); - - const float fRotation = ArrowEffects::ReceptorGetRotationZ( m_pPlayerState ); - m_Ghost[c]->SetRotationZ( fRotation ); - - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - m_Ghost[c]->SetZoom( fZoom ); + (*m_renderers)[c].UpdateReceptorGhostStuff(m_Ghost[c]); } for( unsigned i = 0; i < m_bHoldShowing.size(); ++i ) diff --git a/src/NoteDisplay.cpp b/src/NoteDisplay.cpp index 1e7d736933..a6d0ef889f 100644 --- a/src/NoteDisplay.cpp +++ b/src/NoteDisplay.cpp @@ -17,6 +17,9 @@ #include "Foreach.h" #include "RageMath.h" +static const double PI_180= PI / 180.0; +static const double PI_180R= 180.0 / PI; + const RString& NoteNotePartToString( NotePart i ); /** @brief A foreach loop going through the different NoteParts. */ #define FOREACH_NotePart( i ) FOREACH_ENUM( NotePart, i ) @@ -43,6 +46,15 @@ XToString( NoteColorType ); StringToX( NoteColorType ); LuaXType( NoteColorType ); +static const char* NoteColumnSplineModeNames[] = { + "Disabled", + "Offset", + "Position", +}; +XToString(NoteColumnSplineMode); +StringToX(NoteColumnSplineMode); +LuaXType(NoteColumnSplineMode); + static bool IsVectorZero( const RageVector2 &v ) { return v.x == 0 && v.y == 0; @@ -277,6 +289,101 @@ XToString( ActiveType ); +float NCSplineHandler::BeatToTValue(float song_beat, float note_beat) const +{ + float relative_beat= note_beat; + // This allows someone to do something really fancy like having a spline + // that extends the length of the song. Think of arrows tracing a path + // as the song progresses. -Kyz + if(m_subtract_song_beat_from_curr) + { + relative_beat-= song_beat; + return (relative_beat / m_beats_per_t) - m_receptor_t; + } + return relative_beat / m_beats_per_t; +} + +void NCSplineHandler::EvalForBeat(float song_beat, float note_beat, vector& ret) const +{ + float t_value= BeatToTValue(song_beat, note_beat); + m_spline.evaluate(t_value, ret); +} + +void NCSplineHandler::EvalDerivForBeat(float song_beat, float note_beat, vector& ret) const +{ + float t_value= BeatToTValue(song_beat, note_beat); + m_spline.evaluate_derivative(t_value, ret); +} + +void NCSplineHandler::EvalForReceptor(float song_beat, vector& ret) const +{ + float t_value= m_receptor_t; + if(!m_subtract_song_beat_from_curr) + { + t_value= song_beat / m_beats_per_t; + } + m_spline.evaluate(t_value, ret); +} + +void NoteColumnRenderArgs::spae_pos_for_beat(PlayerState const* state, + float beat, float y_offset, float y_reverse_offset, + vector& sp_pos, vector& ae_pos) const +{ + switch(pos_handler->m_spline_mode) + { + case NCSM_Disabled: + ArrowEffects::GetXYZPos(state, column, y_offset, y_reverse_offset, ae_pos); + sp_pos.resize(3); + // Sure, resize is supposed to call the default constructor, and for + // numbers the default constructor is supposed to set it to zero, but + // I got bit for relying on that once. -Kyz + sp_pos[0]= sp_pos[1]= sp_pos[2]= 0.0f; + break; + case NCSM_Offset: + ArrowEffects::GetXYZPos(state, column, y_offset, y_reverse_offset, ae_pos); + pos_handler->EvalForBeat(song_beat, beat, sp_pos); + break; + case NCSM_Position: + pos_handler->EvalForBeat(song_beat, beat, sp_pos); + break; + } +} +void NoteColumnRenderArgs::spae_zoom_for_beat(PlayerState const* state, float beat, + vector& sp_zoom, vector& ae_zoom) const +{ + switch(zoom_handler->m_spline_mode) + { + case NCSM_Disabled: + ae_zoom[0]= ae_zoom[1]= ae_zoom[2]= ArrowEffects::GetZoom(state); + sp_zoom.resize(3); + sp_zoom[0]= sp_zoom[1]= sp_zoom[2]= 0.0f; + break; + case NCSM_Offset: + ae_zoom[0]= ae_zoom[1]= ae_zoom[2]= ArrowEffects::GetZoom(state); + zoom_handler->EvalForBeat(song_beat, beat, sp_zoom); + break; + case NCSM_Position: + zoom_handler->EvalForBeat(song_beat, beat, sp_zoom); + break; + } +} +void NoteColumnRenderArgs::SetPRZForActor(Actor* actor, + vector const& sp_pos, vector const& ae_pos, + vector const& sp_rot, vector const& ae_rot, + vector const& sp_zoom, vector const& ae_zoom) const +{ + actor->SetX(sp_pos[0] + ae_pos[0]); + actor->SetY(sp_pos[1] + ae_pos[1]); + actor->SetZ(sp_pos[2] + ae_pos[2]); + actor->SetRotationX(sp_rot[0] * PI_180R + ae_rot[0]); + actor->SetRotationY(sp_rot[1] * PI_180R + ae_rot[1]); + actor->SetRotationZ(sp_rot[2] * PI_180R + ae_rot[2]); + actor->SetZoomX(sp_zoom[0] + ae_zoom[0]); + actor->SetZoomY(sp_zoom[1] + ae_zoom[1]); + actor->SetZoomZ(sp_zoom[2] + ae_zoom[2]); +} + + NoteDisplay::NoteDisplay() { cache = new NoteMetricCache_t; @@ -342,15 +449,9 @@ bool NoteDisplay::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTarge return true; } -float NoteDisplay::BeatToTValue(CommonColumnRenderArgs const& args, float beat) const -{ - float song_beat= m_pPlayerState->GetDisplayedPosition().m_fSongBeatVisible; - float relative_beat= beat - song_beat; - return (relative_beat / args.beats_per_t) - args.receptor_t; -} - -bool NoteDisplay::DrawHoldsInRange(CommonColumnRenderArgs const& args, - int column, vector const& tap_set) +bool NoteDisplay::DrawHoldsInRange(NoteFieldRenderArgs const& field_args, + NoteColumnRenderArgs const& column_args, + vector const& tap_set) { bool any_upcoming = false; for(vector::const_iterator tapit= @@ -367,18 +468,18 @@ bool NoteDisplay::DrawHoldsInRange(CommonColumnRenderArgs const& args, float throw_away; bool start_past_peak = false; bool end_past_peak = false; - float start_y = ArrowEffects::GetYOffset(m_pPlayerState, column, + float start_y = ArrowEffects::GetYOffset(m_pPlayerState, column_args.column, NoteRowToVisibleBeat(m_pPlayerState, start_row), throw_away, start_past_peak); - float end_y = ArrowEffects::GetYOffset(m_pPlayerState, column, + float end_y = ArrowEffects::GetYOffset(m_pPlayerState, column_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 tail_visible = field_args.draw_pixels_after_targets <= end_y && + end_y <= field_args.draw_pixels_before_targets; + bool head_visible = field_args.draw_pixels_after_targets <= start_y && + start_y <= field_args.draw_pixels_before_targets; + bool straddling_visible = start_y <= field_args.draw_pixels_after_targets && + field_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)) { @@ -393,21 +494,21 @@ bool NoteDisplay::DrawHoldsInRange(CommonColumnRenderArgs const& args, const bool is_holding = tn.HoldResult.bHeld; if(hold_ghost_showing) { - args.ghost_row->SetHoldShowing(column, tn); + field_args.ghost_row->SetHoldShowing(column_args.column, tn); } - ASSERT_M(NoteRowToBeat(start_row) > -2000, ssprintf("%i %i %i", start_row, end_row, column)); + ASSERT_M(NoteRowToBeat(start_row) > -2000, ssprintf("%i %i %i", start_row, end_row, column_args.column)); bool in_selection_range = false; - if(*args.selection_begin_marker != -1 && *args.selection_end_marker != -1) + if(*field_args.selection_begin_marker != -1 && *field_args.selection_end_marker != -1) { - in_selection_range = (*args.selection_begin_marker <= start_row && - end_row < *args.selection_end_marker); + in_selection_range = (*field_args.selection_begin_marker <= start_row && + end_row < *field_args.selection_end_marker); } - DrawHold(tn, args, column, start_row, is_holding, result, + DrawHold(tn, field_args, column_args, start_row, is_holding, result, use_addition_coloring, - in_selection_range ? args.selection_glow : args.fail_fade); + in_selection_range ? field_args.selection_glow : field_args.fail_fade); bool note_upcoming = NoteRowToBeat(start_row) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat; @@ -416,8 +517,9 @@ bool NoteDisplay::DrawHoldsInRange(CommonColumnRenderArgs const& args, return any_upcoming; } -bool NoteDisplay::DrawTapsInRange(CommonColumnRenderArgs const& args, - int column, vector const& tap_set) +bool NoteDisplay::DrawTapsInRange(NoteFieldRenderArgs const& field_args, + NoteColumnRenderArgs const& column_args, + vector const& tap_set) { bool any_upcoming= false; // draw notes from furthest to closest @@ -430,8 +532,8 @@ bool NoteDisplay::DrawTapsInRange(CommonColumnRenderArgs const& args, // 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), column, - args.draw_pixels_after_targets, args.draw_pixels_before_targets)) + if(!IsOnScreen(NoteRowToBeat(tap_row), column_args.column, + field_args.draw_pixels_after_targets, field_args.draw_pixels_before_targets)) { continue; // skip } @@ -451,9 +553,9 @@ bool NoteDisplay::DrawTapsInRange(CommonColumnRenderArgs const& args, bool hold_begins_on_this_beat = false; if(DrawHoldHeadForTapsOnSameRow()) { - for(int c2= 0; c2 < args.note_data->GetNumTracks(); ++c2) + for(int c2= 0; c2 < field_args.note_data->GetNumTracks(); ++c2) { - const TapNote &tmp = args.note_data->GetTapNote(c2, tap_row); + const TapNote &tmp = field_args.note_data->GetTapNote(c2, tap_row); if(tmp.type == TapNoteType_HoldHead && tmp.subType == TapNoteSubType_Hold) { @@ -467,9 +569,9 @@ bool NoteDisplay::DrawTapsInRange(CommonColumnRenderArgs const& args, bool roll_begins_on_this_beat = false; if(DrawRollHeadForTapsOnSameRow()) { - for(int c2= 0; c2 < args.note_data->GetNumTracks(); ++c2) + for(int c2= 0; c2 < field_args.note_data->GetNumTracks(); ++c2) { - const TapNote &tmp = args.note_data->GetTapNote(c2, tap_row); + const TapNote &tmp = field_args.note_data->GetTapNote(c2, tap_row); if(tmp.type == TapNoteType_HoldHead && tmp.subType == TapNoteSubType_Roll) { @@ -480,19 +582,20 @@ bool NoteDisplay::DrawTapsInRange(CommonColumnRenderArgs const& args, } bool in_selection_range = false; - if(*args.selection_begin_marker != -1 && *args.selection_end_marker != -1) + if(*field_args.selection_begin_marker != -1 && *field_args.selection_end_marker != -1) { - in_selection_range = *args.selection_begin_marker <= tap_row && - tap_row < *args.selection_end_marker; + in_selection_range = *field_args.selection_begin_marker <= tap_row && + tap_row < *field_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), + DrawTap(tn, field_args, column_args, + 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); + in_selection_range ? field_args.selection_glow : field_args.fail_fade); any_upcoming |= NoteRowToBeat(tap_row) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat; @@ -614,28 +717,24 @@ struct StripBuffer }; void NoteDisplay::DrawHoldPart(vector &vpSpr, - CommonColumnRenderArgs const& args, int iCol, int fYStep, + NoteFieldRenderArgs const& field_args, + NoteColumnRenderArgs const& column_args, int fYStep, float fPercentFadeToFail, float fColorScale, bool bGlow, float fOverlappedTime, float fYTop, float fYBottom, float fYStartPos, float fYEndPos, bool bWrapping, bool bAnchorToTop, - bool bFlipTextureVertically, float top_t, float bottom_t) + bool bFlipTextureVertically, float top_beat, float bottom_beat) { ASSERT( !vpSpr.empty() ); + float ae_zoom= ArrowEffects::GetZoom(m_pPlayerState); Sprite *pSprite = vpSpr.front(); - FOREACH( Sprite *, vpSpr, s ) - { - (*s)->SetZoom( ArrowEffects::GetZoom(m_pPlayerState) ); - ASSERT( (*s)->GetUnzoomedWidth() == pSprite->GetUnzoomedWidth() ); - ASSERT( (*s)->GetUnzoomedHeight() == pSprite->GetUnzoomedHeight() ); - } // draw manually in small segments RectF rect = *pSprite->GetCurrentTextureCoordRect(); if( bFlipTextureVertically ) swap( rect.top, rect.bottom ); - const float fFrameWidth = pSprite->GetZoomedWidth(); - const float fFrameHeight = pSprite->GetZoomedHeight(); + const float fFrameWidth = pSprite->GetUnzoomedWidth(); + const float fFrameHeight = pSprite->GetUnzoomedHeight() * ae_zoom; /* 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 @@ -674,6 +773,10 @@ void NoteDisplay::DrawHoldPart(vector &vpSpr, const float fTexCoordRight = rect.right; const float fTexCoordCenter = (fTexCoordLeft+fTexCoordRight)/2; + // pos_z_vec will be used later to orient the hold. Read below. -Kyz + static RageVector3 const pos_z_vec(0.0f, 0.0f, 1.0f); + static RageVector3 const pos_y_vec(0.0f, 1.0f, 0.0f); + StripBuffer queue; for( float fY = fYStartPos; !bLast; fY += fYStep ) { @@ -683,50 +786,165 @@ void NoteDisplay::DrawHoldPart(vector &vpSpr, bLast = true; } - float curtsy= top_t; - if(top_t != bottom_t) + const float fYOffset = ArrowEffects::GetYOffsetFromYPos( m_pPlayerState, column_args.column, fY, m_fYReverseOffsetPixels ); + + float cur_beat= top_beat; + if(top_beat != bottom_beat) { - curtsy= SCALE(fY, fYTop, fYBottom, top_t, bottom_t); + cur_beat= SCALE(fY, fYTop, fYBottom, top_beat, bottom_beat); + } + + // Fun times ahead with vector math. If the notes are being moved by the + // position spline, the vectors used to position the edges of the strip + // need to be adjusted or the hold will vanish when the notes move + // horizontally. + // To accomplish this, we use the derivative at the current point from + // AE and the position spline. That gives us the forward vector for the + // strip, pointing to where the next center vert will be. (step 1) + // The vectors pointing left and right to the edges of the strip are + // obtained from the cross product of the forward vector and pos_z_vec. + // (unless the forward vec is too close to pos_z_vec or -pos_z_vec, in + // which case pos_y_vec is used) The result of a cross product is a + // vector perpendicular to both, so forward crossed with pos_z_vec gives + // us the left vector. Right is of course -left. (step 2) + // After that step, the left and right vectors need to be rotated around + // the forward vector axis by the y rotation value, to allow the hold to + // twist. (step 3) + // Steps will be labeled where they occur below. -Kyz + // TODO: Figure out whether it's worth the time investment to figure out + // a way to skip the complex vector handling if the spline is disabled. + + vector sp_pos; + vector sp_pos_forward; + vector sp_rot; + vector sp_zoom; + vector ae_pos(3, 0.0f); + vector ae_rot(3, 0.0f); + + // (step 1 of vector handling, part 1) + // ArrowEffects only contributes to the Y component of the vector to + // maintain the old behavior of how holds are drawn when they wave back + // and forth. -Kyz + RageVector3 render_forward(0.0f, 1.0f, 0.0f); + column_args.spae_pos_for_beat(m_pPlayerState, cur_beat, + fYOffset, m_fYReverseOffsetPixels, sp_pos, ae_pos); + // fX and fZ are sp_pos[0] + ae_pos[0] and sp_pos[2] + ae_pos[2]. -Kyz + // fY is the actual y position that should be used, not whatever spae + // fetched from ArrowEffects. -Kyz + switch(column_args.pos_handler->m_spline_mode) + { + case NCSM_Disabled: + ae_pos[1]= fY; + sp_pos_forward.resize(3); + sp_pos_forward[0]= sp_pos_forward[1]= sp_pos_forward[2]= 0.0f; + break; + case NCSM_Offset: + ae_pos[1]= fY; + column_args.pos_handler->EvalDerivForBeat(column_args.song_beat, cur_beat, sp_pos_forward); + VectorFloatNormalize(sp_pos_forward); + break; + case NCSM_Position: + ae_pos[1]= 0.0f; + render_forward.y= 0.0f; + column_args.pos_handler->EvalDerivForBeat(column_args.song_beat, cur_beat, sp_pos_forward); + VectorFloatNormalize(sp_pos_forward); + break; + } + + render_forward.x+= sp_pos_forward[0]; + render_forward.y+= sp_pos_forward[1]; + render_forward.z+= sp_pos_forward[2]; + // Normalize the vector so it'll be easy to test when determining whether + // to use pos_z_vec or pos_y_vec for the cross product in step 2. + RageVec3Normalize(&render_forward, &render_forward); + + // Holds are only affected by the x axis of the zoom spline because they + // are flat sprites. -Kyz + float render_width= fFrameWidth; + switch(column_args.zoom_handler->m_spline_mode) + { + case NCSM_Disabled: + render_width= fFrameWidth * ae_zoom; + break; + case NCSM_Offset: + column_args.zoom_handler->EvalForBeat(column_args.song_beat, cur_beat, sp_zoom); + render_width= fFrameWidth * (ae_zoom + sp_zoom[0]); + break; + case NCSM_Position: + column_args.zoom_handler->EvalForBeat(column_args.song_beat, cur_beat, sp_zoom); + render_width= fFrameWidth * sp_zoom[0]; + break; } - vector spline_pos; - args.spline->evaluate(curtsy, spline_pos); - const float fYOffset = ArrowEffects::GetYOffsetFromYPos( m_pPlayerState, iCol, fY, m_fYReverseOffsetPixels ); - const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ) + spline_pos[2]; const float fFrameWidthScale = ArrowEffects::GetFrameWidthScale( m_pPlayerState, fYOffset, fOverlappedTime ); - const float fScaledFrameWidth = fFrameWidth * fFrameWidthScale; + const float fScaledFrameWidth = render_width * fFrameWidthScale; - float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ) + spline_pos[0]; + // Can't use the same code as for taps because hold bodies can only rotate + // around the y axis. -Kyz + switch(column_args.rot_handler->m_spline_mode) + { + case NCSM_Disabled: + // XXX: Actor rotations use degrees, Math uses radians. Convert here. + ae_rot[1]= ArrowEffects::GetRotationY(m_pPlayerState, fYOffset) * PI_180; + sp_rot.resize(3); + sp_rot[0]= sp_rot[1]= sp_rot[2]= 0.0f; + break; + case NCSM_Offset: + ae_rot[1]= ArrowEffects::GetRotationY(m_pPlayerState, fYOffset) * PI_180; + column_args.rot_handler->EvalForBeat(column_args.song_beat, cur_beat, sp_rot); + break; + case NCSM_Position: + column_args.rot_handler->EvalForBeat(column_args.song_beat, cur_beat, sp_rot); + break; + } - // XXX: Actor rotations use degrees, RageFastCos/Sin use radians. Convert here. - const float fRotationY = ArrowEffects::GetRotationY( m_pPlayerState, fYOffset ) * PI/180; + RageVector3 center_vert(sp_pos[0] + ae_pos[0], + sp_pos[1] + ae_pos[1], sp_pos[2] + ae_pos[2]); - // if we're rotating, we need to modify the X and Z coords for the outer edges. - const float fRotOffsetX = (fScaledFrameWidth/2) * RageFastCos(fRotationY); - const float fRotOffsetZ = (fScaledFrameWidth/2) * RageFastSin(fRotationY); + // Special case for hold caps, which have the same top and bottom beat. + if(top_beat == bottom_beat && fY != fYStartPos) + { + center_vert.x+= render_forward.x; + center_vert.y+= render_forward.y; + center_vert.z+= render_forward.z; + } - //const float fXLeft = fX - (fScaledFrameWidth/2); - const float fXLeft = fX - fRotOffsetX; - const float fXCenter = fX; - //const float fXRight = fX + (fScaledFrameWidth/2); - const float fXRight = fX + fRotOffsetX; - const float fZLeft = fZ - fRotOffsetZ; - const float fZCenter = fZ; - const float fZRight = fZ + fRotOffsetZ; + const float render_roty= (sp_rot[1] + ae_rot[1]); + + // (step 2 of vector handling) + RageVector3 render_left; + if(abs(render_forward.z) > 0.9f) // 0.9 arbitrariliy picked. + { + RageVec3Cross(&render_left, &render_forward, &pos_y_vec); + } + else + { + RageVec3Cross(&render_left, &render_forward, &pos_z_vec); + } + RageAARotate(&render_left, &render_forward, render_roty); + const float half_width= fScaledFrameWidth * .5f; + render_left.x*= half_width; + render_left.y*= half_width; + render_left.z*= half_width; + + RageVector3 const left_vert(center_vert.x + render_left.x, + center_vert.y + render_left.y, center_vert.z + render_left.z); + RageVector3 const right_vert(center_vert.x - render_left.x, + center_vert.y - render_left.y, center_vert.z - render_left.z); const float fDistFromTop = fY - fYTop; float fTexCoordTop = SCALE( fDistFromTop, 0, fFrameHeight, rect.top, rect.bottom ); fTexCoordTop += fAddToTexCoord; - const float fAlpha = ArrowGetAlphaOrGlow( bGlow, m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, args.draw_pixels_before_targets, args.fade_before_targets ); + const float fAlpha = ArrowGetAlphaOrGlow( bGlow, m_pPlayerState, column_args.column, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, field_args.draw_pixels_before_targets, field_args.fade_before_targets ); const RageColor color = RageColor(fColorScale,fColorScale,fColorScale,fAlpha); if( fAlpha > 0 ) bAllAreTransparent = false; - queue.v[0].p = RageVector3(fXLeft, fY + spline_pos[1], fZLeft); queue.v[0].c = color; queue.v[0].t = RageVector2(fTexCoordLeft, fTexCoordTop); - queue.v[1].p = RageVector3(fXCenter, fY + spline_pos[1], fZCenter); queue.v[1].c = color; queue.v[1].t = RageVector2(fTexCoordCenter, fTexCoordTop); - queue.v[2].p = RageVector3(fXRight, fY + spline_pos[1], fZRight); queue.v[2].c = color; queue.v[2].t = RageVector2(fTexCoordRight, fTexCoordTop); + queue.v[0].p = left_vert; queue.v[0].c = color; queue.v[0].t = RageVector2(fTexCoordLeft, fTexCoordTop); + queue.v[1].p = center_vert; queue.v[1].c = color; queue.v[1].t = RageVector2(fTexCoordCenter, fTexCoordTop); + queue.v[2].p = right_vert; queue.v[2].c = color; queue.v[2].t = RageVector2(fTexCoordRight, fTexCoordTop); queue.v+=3; if( queue.Free() < 3 || bLast ) @@ -753,10 +971,11 @@ void NoteDisplay::DrawHoldPart(vector &vpSpr, } void NoteDisplay::DrawHoldBody(const TapNote& tn, - CommonColumnRenderArgs const& args, int iCol, float fBeat, + NoteFieldRenderArgs const& field_args, + NoteColumnRenderArgs const& column_args, float fBeat, bool bIsBeingHeld, float fYHead, float fYTail, bool bIsAddition, float fPercentFadeToFail, float fColorScale, bool bGlow, - float top_t, float bottom_t) + float top_beat, float bottom_beat) { vector vpSprTop; Sprite *pSpriteTop = GetHoldSprite( m_HoldTopCap, NotePart_HoldTopCap, fBeat, tn.subType == TapNoteSubType_Roll, bIsBeingHeld && !cache->m_bHoldActiveIsAddLayer ); @@ -780,7 +999,7 @@ void NoteDisplay::DrawHoldBody(const TapNote& tn, vpSprBottom.push_back( pSprBottom ); } - const bool bReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(iCol) > 0.5f; + const bool bReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(column_args.column) > 0.5f; bool bFlipHoldBody = bReverse && cache->m_bFlipHoldBodyWhenReverse; if( bFlipHoldBody ) { @@ -814,55 +1033,56 @@ void NoteDisplay::DrawHoldBody(const TapNote& tn, const float fFrameHeightTop = pSpriteTop->GetUnzoomedHeight(); const float fFrameHeightBottom = pSpriteBottom->GetUnzoomedHeight(); - float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, - args.draw_pixels_after_targets, m_fYReverseOffsetPixels ); - float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, - args.draw_pixels_before_targets, m_fYReverseOffsetPixels ); + float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, column_args.column, + field_args.draw_pixels_after_targets, m_fYReverseOffsetPixels ); + float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, column_args.column, + field_args.draw_pixels_before_targets, m_fYReverseOffsetPixels ); if( bReverse ) swap( fYStartPos, fYEndPos ); bool bTopAnchor = bReverse && cache->m_bTopHoldAnchorWhenReverse; // Draw the top cap - DrawHoldPart(vpSprTop, args, iCol, fYStep, fPercentFadeToFail, + DrawHoldPart(vpSprTop, field_args, column_args, fYStep, fPercentFadeToFail, fColorScale, bGlow, tn.HoldResult.fOverlappedTime, fYHead-fFrameHeightTop, fYHead, fYStartPos, fYEndPos, - false, bTopAnchor, bFlipHoldBody, top_t, top_t); + false, bTopAnchor, bFlipHoldBody, top_beat, top_beat); // Draw the body - DrawHoldPart(vpSprBody, args, iCol, fYStep, fPercentFadeToFail, + DrawHoldPart(vpSprBody, field_args, column_args, fYStep, fPercentFadeToFail, fColorScale, bGlow, tn.HoldResult.fOverlappedTime, fYHead, fYTail, fYStartPos, fYEndPos, - true, bTopAnchor, bFlipHoldBody, top_t, bottom_t); + true, bTopAnchor, bFlipHoldBody, top_beat, bottom_beat); // Draw the bottom cap - DrawHoldPart(vpSprBottom, args, iCol, fYStep, fPercentFadeToFail, + DrawHoldPart(vpSprBottom, field_args, column_args, fYStep, fPercentFadeToFail, fColorScale, bGlow, tn.HoldResult.fOverlappedTime, fYTail, fYTail+fFrameHeightBottom, max(fYStartPos, fYHead), fYEndPos, - false, bTopAnchor, bFlipHoldBody, bottom_t, bottom_t); + false, bTopAnchor, bFlipHoldBody, bottom_beat, bottom_beat); } void NoteDisplay::DrawHold(const TapNote& tn, - CommonColumnRenderArgs const& args, int iCol, int iRow, bool bIsBeingHeld, + NoteFieldRenderArgs const& field_args, + NoteColumnRenderArgs const& column_args, int iRow, bool bIsBeingHeld, const HoldNoteResult &Result, bool bIsAddition, float fPercentFadeToFail) { int iEndRow = iRow + tn.iDuration; - float top_t= BeatToTValue(args, NoteRowToVisibleBeat(m_pPlayerState, iRow)); - float bottom_t= BeatToTValue(args, NoteRowToVisibleBeat(m_pPlayerState, iEndRow)); + float top_beat= NoteRowToVisibleBeat(m_pPlayerState, iRow); + float bottom_beat= NoteRowToVisibleBeat(m_pPlayerState, iEndRow); if(bIsBeingHeld) { - top_t= args.receptor_t; + top_beat= column_args.song_beat; } // bDrawGlowOnly is a little hacky. We need to draw the diffuse part and the glow part one pass at a time to minimize state changes - bool bReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(iCol) > 0.5f; + bool bReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(column_args.column) > 0.5f; float fStartBeat = NoteRowToBeat( max(tn.HoldResult.iLastHeldRow, iRow) ); float fThrowAway = 0; @@ -872,11 +1092,11 @@ void NoteDisplay::DrawHold(const TapNote& tn, if( tn.HoldResult.bActive && tn.HoldResult.fLife > 0 ) ; // use the default values filled in above else - fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fStartBeat, fThrowAway, bStartIsPastPeak ); + fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, column_args.column, fStartBeat, fThrowAway, bStartIsPastPeak ); float fEndPeakYOffset = 0; bool bEndIsPastPeak = false; - float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, NoteRowToBeat(iEndRow), fEndPeakYOffset, bEndIsPastPeak ); + float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, column_args.column, NoteRowToBeat(iEndRow), fEndPeakYOffset, bEndIsPastPeak ); // In boomerang, the arrows reverse direction at Y offset value fPeakAtYOffset. // If fPeakAtYOffset lies inside of the hold we're drawing, then the we @@ -889,8 +1109,8 @@ void NoteDisplay::DrawHold(const TapNote& tn, if( bReverse ) swap( fStartYOffset, fEndYOffset ); - const float fYHead = ArrowEffects::GetYPos( m_pPlayerState, iCol, fStartYOffset, m_fYReverseOffsetPixels ); - const float fYTail = ArrowEffects::GetYPos( m_pPlayerState, iCol, fEndYOffset, m_fYReverseOffsetPixels ); + const float fYHead = ArrowEffects::GetYPos( m_pPlayerState, column_args.column, fStartYOffset, m_fYReverseOffsetPixels ); + const float fYTail = ArrowEffects::GetYPos( m_pPlayerState, column_args.column, fEndYOffset, m_fYReverseOffsetPixels ); const float fColorScale = SCALE( tn.HoldResult.fLife, 0.0f, 1.0f, cache->m_fHoldLetGoGrayPercent, 1.0f ); @@ -899,6 +1119,9 @@ void NoteDisplay::DrawHold(const TapNote& tn, /* The body and caps should have no overlap, so their order doesn't matter. * Draw the head last, so it appears on top. */ float fBeat = NoteRowToBeat(iRow); + // Side note: I don't know why these two checks were commented out and I + // didn't bother to update them when rewriting the arguments that are + // passed to the note drawing functions. -Kyz /* if( !cache->m_bHoldHeadIsAboveWavyParts ) { @@ -912,8 +1135,8 @@ void NoteDisplay::DrawHold(const TapNote& tn, } */ - DrawHoldBody(tn, args, iCol, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, false, top_t, bottom_t); - DrawHoldBody(tn, args, iCol, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, true, top_t, bottom_t); + DrawHoldBody(tn, field_args, column_args, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, false, top_beat, bottom_beat); + DrawHoldBody(tn, field_args, column_args, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, true, top_beat, bottom_beat); /* These set the texture mode themselves. */ // this part was modified in pumpmania, where it flips the draw order @@ -921,56 +1144,76 @@ void NoteDisplay::DrawHold(const TapNote& tn, if( cache->m_bHoldTailIsAboveWavyParts ) { Actor *pActor = GetHoldActor( m_HoldTail, NotePart_HoldTail, NoteRowToBeat(iRow), tn.subType == TapNoteSubType_Roll, bIsBeingHeld ); - DrawActor(tn, pActor, NotePart_HoldTail, args, iCol, bFlipHeadAndTail ? fStartYOffset : fEndYOffset, fBeat, bIsAddition, fPercentFadeToFail, fColorScale, false); + DrawActor(tn, pActor, NotePart_HoldTail, field_args, column_args, bFlipHeadAndTail ? fStartYOffset : fEndYOffset, fBeat, bIsAddition, fPercentFadeToFail, fColorScale, false); } if( cache->m_bHoldHeadIsAboveWavyParts ) { Actor *pActor = GetHoldActor( m_HoldHead, NotePart_HoldHead, NoteRowToBeat(iRow), tn.subType == TapNoteSubType_Roll, bIsBeingHeld ); - DrawActor(tn, pActor, NotePart_HoldHead, args, iCol, bFlipHeadAndTail ? fEndYOffset : fStartYOffset, fBeat, bIsAddition, fPercentFadeToFail, fColorScale, bIsBeingHeld); + DrawActor(tn, pActor, NotePart_HoldHead, field_args, column_args, bFlipHeadAndTail ? fEndYOffset : fStartYOffset, fBeat, bIsAddition, fPercentFadeToFail, fColorScale, bIsBeingHeld); } } void NoteDisplay::DrawActor(const TapNote& tn, Actor* pActor, NotePart part, - CommonColumnRenderArgs const& args, int iCol, float fYOffset, float fBeat, + NoteFieldRenderArgs const& field_args, NoteColumnRenderArgs const& column_args, float fYOffset, float fBeat, bool bIsAddition, float fPercentFadeToFail, float fColorScale, bool is_being_held) { if (tn.type == TapNoteType_AutoKeysound && !GAMESTATE->m_bInStepEditor) return; - if(fYOffset < args.draw_pixels_after_targets || - fYOffset > args.draw_pixels_before_targets) + if(fYOffset < field_args.draw_pixels_after_targets || + fYOffset > field_args.draw_pixels_before_targets) return; - float spline_t= BeatToTValue(args, fBeat); - if(is_being_held) { spline_t= args.receptor_t; } - vector spline_pos; - args.spline->evaluate(spline_t, spline_pos); - const float fY = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYOffset, m_fYReverseOffsetPixels ) + spline_pos[1]; - const float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ) + spline_pos[0]; - const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ) + spline_pos[2]; - const float fAlpha = ArrowEffects::GetAlpha( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, args.draw_pixels_before_targets, args.fade_before_targets ); - const float fGlow = ArrowEffects::GetGlow( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, args.draw_pixels_before_targets, args.fade_before_targets ); + float spline_beat= fBeat; + if(is_being_held) { spline_beat= column_args.song_beat; } + + const float fAlpha = ArrowEffects::GetAlpha( m_pPlayerState, column_args.column, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, field_args.draw_pixels_before_targets, field_args.fade_before_targets ); + const float fGlow = ArrowEffects::GetGlow( m_pPlayerState, column_args.column, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, field_args.draw_pixels_before_targets, field_args.fade_before_targets ); const RageColor diffuse = RageColor(fColorScale,fColorScale,fColorScale,fAlpha); const RageColor glow = RageColor(1,1,1,fGlow); - float fRotationX = 0, fRotationZ = 0; - const float fRotationY = ArrowEffects::GetRotationY( m_pPlayerState, fYOffset ); bool bIsHoldHead = tn.type == TapNoteType_HoldHead; bool bIsHoldCap = bIsHoldHead || tn.type == TapNoteType_HoldTail; - fRotationZ = ArrowEffects::GetRotationZ( m_pPlayerState, fBeat, bIsHoldHead ); - if( !bIsHoldCap ) - { - fRotationX = ArrowEffects::GetRotationX( m_pPlayerState, fYOffset ); - } - if( tn.type != TapNoteType_HoldHead ) fColorScale *= ArrowEffects::GetBrightness( m_pPlayerState, fBeat ); - pActor->SetRotationX( fRotationX ); - pActor->SetRotationY( fRotationY ); - pActor->SetRotationZ( fRotationZ ); - pActor->SetXY( fX, fY ); - pActor->SetZ( fZ ); - pActor->SetZoom( ArrowEffects::GetZoom(m_pPlayerState) ); + // same logical structure as in UpdateReceptorGhostStuff, I just haven't + // figured out a good way to combine them. -Kyz + vector sp_pos; + vector sp_rot; + vector sp_zoom; + vector ae_pos(3, 0.0f); + vector ae_rot(3, 0.0f); + vector ae_zoom(3, 0.0f); + column_args.spae_pos_for_beat(m_pPlayerState, spline_beat, + fYOffset, m_fYReverseOffsetPixels, sp_pos, ae_pos); + + switch(column_args.rot_handler->m_spline_mode) + { + case NCSM_Disabled: + if(!bIsHoldCap) + { + ae_rot[0]= ArrowEffects::GetRotationX(m_pPlayerState, fYOffset); + } + ae_rot[1]= ArrowEffects::GetRotationY(m_pPlayerState, fYOffset); + ae_rot[2]= ArrowEffects::GetRotationZ(m_pPlayerState, fBeat, bIsHoldHead); + sp_rot.resize(3); + sp_rot[0]= sp_rot[1]= sp_rot[2]= 0.0f; + break; + case NCSM_Offset: + if(!bIsHoldCap) + { + ae_rot[0]= ArrowEffects::GetRotationX(m_pPlayerState, fYOffset); + } + ae_rot[1]= ArrowEffects::GetRotationY(m_pPlayerState, fYOffset); + ae_rot[2]= ArrowEffects::GetRotationZ(m_pPlayerState, fBeat, bIsHoldHead); + column_args.rot_handler->EvalForBeat(column_args.song_beat, spline_beat, sp_rot); + break; + case NCSM_Position: + column_args.rot_handler->EvalForBeat(column_args.song_beat, spline_beat, sp_rot); + break; + } + column_args.spae_zoom_for_beat(m_pPlayerState, spline_beat, sp_zoom, ae_zoom); + column_args.SetPRZForActor(pActor, sp_pos, ae_pos, sp_rot, ae_rot, sp_zoom, ae_zoom); // [AJ] this two lines (and how they're handled) piss off many people: pActor->SetDiffuse( diffuse ); pActor->SetGlow( glow ); @@ -1004,7 +1247,8 @@ void NoteDisplay::DrawActor(const TapNote& tn, Actor* pActor, NotePart part, } void NoteDisplay::DrawTap(const TapNote& tn, - CommonColumnRenderArgs const& args, int iCol, float fBeat, + NoteFieldRenderArgs const& field_args, + NoteColumnRenderArgs const& column_args, float fBeat, bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollStart, bool bIsAddition, float fPercentFadeToFail) { @@ -1078,21 +1322,89 @@ void NoteDisplay::DrawTap(const TapNote& tn, pActor->HandleMessage( msg ); } - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fBeat ); + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, column_args.column, fBeat ); // this is the line that forces the (1,1,1,x) part of the noteskin diffuse -aj - DrawActor(tn, pActor, part, args, iCol, fYOffset, fBeat, bIsAddition, fPercentFadeToFail, 1.0f, false); + DrawActor(tn, pActor, part, field_args, column_args, fYOffset, fBeat, bIsAddition, fPercentFadeToFail, 1.0f, false); if( tn.type == TapNoteType_Attack ) pActor->PlayCommand( "UnsetAttack" ); } +void NoteColumnRenderer::UpdateReceptorGhostStuff(Actor* receptor) const +{ + PlayerState const* player_state= m_field_render_args->player_state; + float song_beat= player_state->GetDisplayedPosition().m_fSongBeatVisible; + // sp_* will be filled with the settings from the splines. + // ae_* will be filled with the settings from ArrowEffects. + // The two together will be applied to the actor. + // sp_* will be zeroes in NCSM_Disabled, and ae_* will be zeroes in + // NCSM_Position, so the setting step won't have to check the mode. -Kyz + // sp_* are sized by the spline evaluate function. + vector sp_pos; + vector sp_rot; + vector sp_zoom; + vector ae_pos(3, 0.0f); + vector ae_rot(3, 0.0f); + vector ae_zoom(3, 0.0f); + switch(m_pos_handler.m_spline_mode) + { + case NCSM_Disabled: + ArrowEffects::GetXYZPos(player_state, m_column, 0, m_field_render_args->reverse_offset_pixels, ae_pos); + sp_pos.resize(3); + // Sure, resize is supposed to call the default constructor, and for + // numbers the default constructor is supposed to set it to zero, but + // I got bit for relying on that once. -Kyz + sp_pos[0]= sp_pos[1]= sp_pos[2]= 0.0f; + break; + case NCSM_Offset: + ArrowEffects::GetXYZPos(player_state, m_column, 0, m_field_render_args->reverse_offset_pixels, ae_pos); + m_pos_handler.EvalForReceptor(song_beat, sp_pos); + break; + case NCSM_Position: + m_pos_handler.EvalForReceptor(song_beat, sp_pos); + break; + } + switch(m_rot_handler.m_spline_mode) + { + case NCSM_Disabled: + ae_rot[2]= ArrowEffects::ReceptorGetRotationZ(player_state); + sp_rot.resize(3); + sp_rot[0]= sp_rot[1]= sp_rot[2]= 0.0f; + break; + case NCSM_Offset: + ae_rot[2]= ArrowEffects::ReceptorGetRotationZ(player_state); + m_rot_handler.EvalForReceptor(song_beat, sp_rot); + break; + case NCSM_Position: + m_rot_handler.EvalForReceptor(song_beat, sp_rot); + break; + } + switch(m_zoom_handler.m_spline_mode) + { + case NCSM_Disabled: + ae_zoom[0]= ae_zoom[1]= ae_zoom[2]= ArrowEffects::GetZoom(player_state); + sp_zoom.resize(3); + sp_zoom[0]= sp_zoom[1]= sp_zoom[2]= 0.0f; + break; + case NCSM_Offset: + ae_zoom[0]= ae_zoom[1]= ae_zoom[2]= ArrowEffects::GetZoom(player_state); + m_zoom_handler.EvalForReceptor(song_beat, sp_zoom); + break; + case NCSM_Position: + m_zoom_handler.EvalForReceptor(song_beat, sp_zoom); + break; + } + m_column_render_args.SetPRZForActor(receptor, sp_pos, ae_pos, sp_rot, ae_rot, sp_zoom, ae_zoom); +} + void NoteColumnRenderer::DrawPrimitives() { - m_render_args->spline= &m_spline; - m_render_args->receptor_t= m_receptor_t; - m_render_args->beats_per_t= m_beats_per_t; - m_render_args->first_beat= NoteRowToBeat(m_render_args->first_row); - m_render_args->last_beat= NoteRowToBeat(m_render_args->last_row); + m_column_render_args.song_beat= m_field_render_args->player_state->GetDisplayedPosition().m_fSongBeatVisible; + m_column_render_args.pos_handler= &m_pos_handler; + m_column_render_args.rot_handler= &m_rot_handler; + m_column_render_args.zoom_handler= &m_zoom_handler; + m_field_render_args->first_beat= NoteRowToBeat(m_field_render_args->first_row); + m_field_render_args->last_beat= NoteRowToBeat(m_field_render_args->last_row); bool any_upcoming= false; // Build lists of holds and taps for each player number, then pass those // lists to the displays to draw. @@ -1101,8 +1413,8 @@ void NoteColumnRenderer::DrawPrimitives() vector > holds(PLAYER_INVALID+1); vector > taps(PLAYER_INVALID+1); NoteData::TrackMap::const_iterator begin, end; - m_render_args->note_data->GetTapNoteRangeInclusive(m_column, - m_render_args->first_row, m_render_args->last_row+1, begin, end); + m_field_render_args->note_data->GetTapNoteRangeInclusive(m_column, + m_field_render_args->first_row, m_field_render_args->last_row+1, begin, end); for(; begin != end; ++begin) { TapNote const& tn= begin->second; @@ -1133,7 +1445,7 @@ void NoteColumnRenderer::DrawPrimitives() #define DTS_INNER(pn, tap_set, draw_func, disp) \ if(!tap_set[pn].empty()) \ { \ - any_upcoming|= disp->draw_func(*m_render_args, m_column, tap_set[pn]); \ + any_upcoming|= disp->draw_func(*m_field_render_args, m_column_render_args, tap_set[pn]); \ } #define DRAW_TAP_SET(tap_set, draw_func) \ FOREACH_PlayerNumber(pn) \ @@ -1146,12 +1458,12 @@ void NoteColumnRenderer::DrawPrimitives() DTS_INNER(PLAYER_INVALID, taps, DrawTapsInRange, m_displays[PLAYER_INVALID]); #undef DTS_INNER #undef DRAW_TAP_SET - m_render_args->receptor_row->SetNoteUpcoming(m_column, any_upcoming); + m_field_render_args->receptor_row->SetNoteUpcoming(m_column, any_upcoming); } #include "LuaBinding.h" -struct LunaNoteColumnRenderer : Luna +struct LunaNCSplineHandler : Luna { static int get_spline(T* p, lua_State* L) { @@ -1166,22 +1478,66 @@ struct LunaNoteColumnRenderer : Luna p->member= FArg(1); \ COMMON_RETURN_SELF; \ } +#define SET_B(member, name) \ + static int name(T* p, lua_State* L) \ + { \ + p->member= BArg(1); \ + COMMON_RETURN_SELF; \ + } SET_T(m_receptor_t, set_receptor_t); SET_T(m_beats_per_t, set_beats_per_t); + SET_B(m_subtract_song_beat_from_curr, set_subtract_song_beat); +#undef SET_T +#undef SET_B + static int set_spline_mode(T* p, lua_State* L) + { + p->m_spline_mode= Enum::Check(L, 1); + COMMON_RETURN_SELF; + } + DEFINE_METHOD(get_spline_mode, m_spline_mode); + DEFINE_METHOD(get_subtract_song_beat, m_subtract_song_beat_from_curr); + + LunaNCSplineHandler() + { + ADD_METHOD(get_spline); + ADD_METHOD(get_beats_per_t); + ADD_METHOD(set_beats_per_t); + ADD_METHOD(get_receptor_t); + ADD_METHOD(set_receptor_t); + ADD_METHOD(get_spline_mode); + ADD_METHOD(set_spline_mode); + ADD_METHOD(get_subtract_song_beat); + ADD_METHOD(set_subtract_song_beat); + } +}; + +LUA_REGISTER_CLASS(NCSplineHandler); + +struct LunaNoteColumnRenderer : Luna +{ +#define GET_HANDLER(member, name) \ + static int name(T* p, lua_State* L) \ + { \ + p->member.PushSelf(L); \ + return 1; \ + } + GET_HANDLER(m_pos_handler, get_pos_handler); + GET_HANDLER(m_rot_handler, get_rot_handler); + GET_HANDLER(m_zoom_handler, get_zoom_handler); +#undef GET_HANDLER LunaNoteColumnRenderer() { - ADD_METHOD(get_spline); - ADD_METHOD(get_receptor_t); - ADD_METHOD(get_beats_per_t); - ADD_METHOD(set_receptor_t); - ADD_METHOD(set_beats_per_t); + ADD_METHOD(get_pos_handler); + ADD_METHOD(get_rot_handler); + ADD_METHOD(get_zoom_handler); } }; LUA_REGISTER_DERIVED_CLASS(NoteColumnRenderer, Actor) /* + * NoteColumnRenderer and associated spline stuff (c) Eric Reese 2014-2015 * (c) 2001-2006 Brian Bugh, Ben Nordstrom, Chris Danford, Steve Checkoway * All rights reserved. * diff --git a/src/NoteDisplay.h b/src/NoteDisplay.h index 43121564b7..c92e2a37c7 100644 --- a/src/NoteDisplay.h +++ b/src/NoteDisplay.h @@ -87,16 +87,24 @@ enum ActiveType #define FOREACH_ActiveType( i ) FOREACH_ENUM( ActiveType, i ) const RString &ActiveTypeToString( ActiveType at ); +enum NoteColumnSplineMode +{ + NCSM_Disabled, + NCSM_Offset, + NCSM_Position, + NUM_NoteColumnSplineMode, + NoteColumnSplineMode_Invalid +}; + // A little pod struct to carry the data the NoteField needs to pass to the // NoteDisplay during rendering. -struct CommonColumnRenderArgs +struct NoteFieldRenderArgs { + PlayerState const* player_state; // to look up PlayerOptions + float reverse_offset_pixels; ReceptorArrowRow* receptor_row; GhostArrowRow* ghost_row; NoteData const* note_data; - CubicSplineN const* spline; - float receptor_t; - float beats_per_t; float first_beat; float last_beat; int first_row; @@ -110,6 +118,52 @@ struct CommonColumnRenderArgs float fade_before_targets; }; +// NCSplineHandler exists to allow NoteColumnRenderer to have separate +// splines for position, rotation, and zoom, while concisely presenting the +// same interface for all three. +struct NCSplineHandler +{ + NCSplineHandler() + { + m_spline.redimension(3); + m_spline.m_owned_by_actor= true; + m_spline_mode= NCSM_Disabled; + m_receptor_t= 0.0f; + m_beats_per_t= 1.0f; + m_subtract_song_beat_from_curr= true; + } + float BeatToTValue(float song_beat, float note_beat) const; + void EvalForBeat(float song_beat, float note_beat, vector& ret) const; + void EvalDerivForBeat(float song_beat, float note_beat, vector& ret) const; + void EvalForReceptor(float song_beat, vector& ret) const; + + CubicSplineN m_spline; + NoteColumnSplineMode m_spline_mode; + float m_receptor_t; + float m_beats_per_t; + bool m_subtract_song_beat_from_curr; + + void PushSelf(lua_State* L); +}; + +struct NoteColumnRenderArgs +{ + void spae_pos_for_beat(PlayerState const* state, + float beat, float y_offset, float y_reverse_offset, + vector& sp_pos, vector& ae_pos) const; + void spae_zoom_for_beat(PlayerState const* state, float beat, + vector& sp_zoom, vector& ae_zoom) const; + void SetPRZForActor(Actor* actor, + vector const& sp_pos, vector const& ae_pos, + vector const& sp_rot, vector const& ae_rot, + vector const& sp_zoom, vector const& ae_zoom) const; + NCSplineHandler const* pos_handler; + NCSplineHandler const* rot_handler; + NCSplineHandler const* zoom_handler; + float song_beat; + int column; +}; + /** @brief Draws TapNotes and HoldNotes. */ class NoteDisplay { @@ -122,11 +176,12 @@ public: static void Update( float fDeltaTime ); bool IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const; - float BeatToTValue(CommonColumnRenderArgs const& args, float beat) const; - bool DrawHoldsInRange(CommonColumnRenderArgs const& args, int column, + bool DrawHoldsInRange(NoteFieldRenderArgs const& field_args, + NoteColumnRenderArgs const& column_args, vector const& tap_set); - bool DrawTapsInRange(CommonColumnRenderArgs const& args, int column, + bool DrawTapsInRange(NoteFieldRenderArgs const& field_args, + NoteColumnRenderArgs const& column_args, vector const& tap_set); /** * @brief Draw the TapNote onto the NoteField. @@ -141,11 +196,13 @@ public: * @param fDrawDistanceAfterTargetsPixels how much to draw after the receptors. * @param fDrawDistanceBeforeTargetsPixels how much ot draw before the receptors. * @param fFadeInPercentOfDrawFar when to start fading in. */ - void DrawTap(const TapNote& tn, CommonColumnRenderArgs const& args, - int iCol, float fBeat, bool bOnSameRowAsHoldStart, + void DrawTap(const TapNote& tn, NoteFieldRenderArgs const& field_args, + NoteColumnRenderArgs const& column_args, float fBeat, + bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollBeat, bool bIsAddition, float fPercentFadeToFail); - void DrawHold(const TapNote& tn, CommonColumnRenderArgs const& args, - int iCol, int iRow, bool bIsBeingHeld, const HoldNoteResult &Result, + void DrawHold(const TapNote& tn, NoteFieldRenderArgs const& field_args, + NoteColumnRenderArgs const& column_args, int iRow, bool bIsBeingHeld, + const HoldNoteResult &Result, bool bIsAddition, float fPercentFadeToFail); bool DrawHoldHeadForTapsOnSameRow() const; @@ -158,19 +215,22 @@ private: Sprite *GetHoldSprite( NoteColorSprite ncs[NUM_HoldType][NUM_ActiveType], NotePart part, float fNoteBeat, bool bIsRoll, bool bIsBeingHeld ); void DrawActor(const TapNote& tn, Actor* pActor, NotePart part, - CommonColumnRenderArgs const& args, int iCol, float fYOffset, float fBeat, + NoteFieldRenderArgs const& field_args, + NoteColumnRenderArgs const& column_args, float fYOffset, float fBeat, bool bIsAddition, float fPercentFadeToFail, float fColorScale, bool is_being_held); - void DrawHoldBody(const TapNote& tn, CommonColumnRenderArgs const& args, - int iCol, float fBeat, bool bIsBeingHeld, float fYHead, float fYTail, + void DrawHoldBody(const TapNote& tn, NoteFieldRenderArgs const& field_args, + NoteColumnRenderArgs const& column_args, float fBeat, bool bIsBeingHeld, + float fYHead, float fYTail, bool bIsAddition, float fPercentFadeToFail, float fColorScale, - bool bGlow, float top_t, float bottom_t); + bool bGlow, float top_beat, float bottom_beat); void DrawHoldPart(vector &vpSpr, - CommonColumnRenderArgs const& args, int iCol, int fYStep, + NoteFieldRenderArgs const& field_args, + NoteColumnRenderArgs const& column_args, int fYStep, float fPercentFadeToFail, float fColorScale, bool bGlow, float fOverlappedTime, float fYTop, float fYBottom, float fYStartPos, float fYEndPos, bool bWrapping, bool bAnchorToTop, - bool bFlipTextureVertically, float top_t, float bottom_t); + bool bFlipTextureVertically, float top_beat, float bottom_beat); const PlayerState *m_pPlayerState; // to look up PlayerOptions NoteMetricCache_t *cache; @@ -196,21 +256,24 @@ private: // actors so they can move with the rest of the column. I didn't use // ActorProxy because the receptor/ghost actors need to pull in the parent // state of their rows and the parent state of the column. -Kyz -class NoteColumnRenderer : public Actor -{ - public: - NoteColumnRenderer() - { - m_spline.redimension(3); - m_spline.m_owned_by_actor= true; - } - NoteDisplay* m_displays[PLAYER_INVALID+1]; - CommonColumnRenderArgs* m_render_args; - int m_column; - float m_receptor_t; - float m_beats_per_t; - CubicSplineN m_spline; +struct NoteColumnRenderer : public Actor +{ + NoteDisplay* m_displays[PLAYER_INVALID+1]; + NoteFieldRenderArgs* m_field_render_args; + NoteColumnRenderArgs m_column_render_args; + int m_column; + // m_column_render_args has pointers to these fields that are updated + // every frame instead of using m_column_render_args directly so that they + // can be moved into a tween state later. -Kyz + NCSplineHandler m_pos_handler; + NCSplineHandler m_rot_handler; + NCSplineHandler m_zoom_handler; + + // UpdateReceptorGhostStuff takes care of the logic for making the ghost + // and receptor positions follow the splines. It's called by their row + // update functions. -Kyz + void UpdateReceptorGhostStuff(Actor* receptor) const; virtual void DrawPrimitives(); virtual void PushSelf(lua_State* L); }; @@ -218,6 +281,7 @@ class NoteColumnRenderer : public Actor #endif /** + * NoteColumnRenderer and associated spline stuff (c) Eric Reese 2014-2015 * @file * @author Brian Bugh, Ben Nordstrom, Chris Danford, Steve Checkoway (c) 2001-2006 * @section LICENSE diff --git a/src/NoteField.cpp b/src/NoteField.cpp index 1ff8fc1cf7..97dd4e976b 100644 --- a/src/NoteField.cpp +++ b/src/NoteField.cpp @@ -63,11 +63,11 @@ NoteField::NoteField() // I decided to do it this way because I don't want to dig through // ScreenEdit to change all the places it touches the markers. -Kyz - m_ColumnRenderArgs.selection_begin_marker= &m_iBeginMarker; - m_ColumnRenderArgs.selection_end_marker= &m_iEndMarker; + m_FieldRenderArgs.selection_begin_marker= &m_iBeginMarker; + m_FieldRenderArgs.selection_end_marker= &m_iEndMarker; m_iBeginMarker = m_iEndMarker = -1; - m_ColumnRenderArgs.fail_fade = -1; + m_FieldRenderArgs.fail_fade = -1; m_StepCallback.SetFromNil(); m_SetPressedCallback.SetFromNil(); @@ -192,7 +192,7 @@ void NoteField::Load( m_iDrawDistanceBeforeTargetsPixels = iDrawDistanceBeforeTargetsPixels; ASSERT( m_iDrawDistanceBeforeTargetsPixels >= m_iDrawDistanceAfterTargetsPixels ); - m_ColumnRenderArgs.fail_fade = -1; + m_FieldRenderArgs.fail_fade = -1; //int i1 = m_pNoteData->GetNumTracks(); //int i2 = GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; @@ -248,9 +248,11 @@ void NoteField::Load( void NoteField::InitColumnRenderers() { - m_ColumnRenderArgs.receptor_row= &(m_pCurDisplay->m_ReceptorArrowRow); - m_ColumnRenderArgs.ghost_row= &(m_pCurDisplay->m_GhostArrowRow); - m_ColumnRenderArgs.note_data= m_pNoteData; + m_FieldRenderArgs.player_state= m_pPlayerState; + m_FieldRenderArgs.reverse_offset_pixels= m_fYReverseOffsetPixels; + m_FieldRenderArgs.receptor_row= &(m_pCurDisplay->m_ReceptorArrowRow); + m_FieldRenderArgs.ghost_row= &(m_pCurDisplay->m_GhostArrowRow); + m_FieldRenderArgs.note_data= m_pNoteData; m_ColumnRenderers.resize(GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer); for(size_t ncr= 0; ncr < m_ColumnRenderers.size(); ++ncr) { @@ -260,7 +262,8 @@ void NoteField::InitColumnRenderers() } m_ColumnRenderers[ncr].m_displays[PLAYER_INVALID]= &(m_pCurDisplay->display[ncr]); m_ColumnRenderers[ncr].m_column= ncr; - m_ColumnRenderers[ncr].m_render_args= &m_ColumnRenderArgs; + m_ColumnRenderers[ncr].m_column_render_args.column= ncr; + m_ColumnRenderers[ncr].m_field_render_args= &m_FieldRenderArgs; } m_pCurDisplay->m_ReceptorArrowRow.SetColumnRenderers(m_ColumnRenderers); m_pCurDisplay->m_GhostArrowRow.SetColumnRenderers(m_ColumnRenderers); @@ -305,11 +308,11 @@ void NoteField::Update( float fDeltaTime ) cur->m_ReceptorArrowRow.Update( fDeltaTime ); cur->m_GhostArrowRow.Update( fDeltaTime ); - if( m_ColumnRenderArgs.fail_fade >= 0 ) - m_ColumnRenderArgs.fail_fade = min( m_ColumnRenderArgs.fail_fade + fDeltaTime/FADE_FAIL_TIME, 1 ); + if( m_FieldRenderArgs.fail_fade >= 0 ) + m_FieldRenderArgs.fail_fade = min( m_FieldRenderArgs.fail_fade + fDeltaTime/FADE_FAIL_TIME, 1 ); // Update fade to failed - m_pCurDisplay->m_ReceptorArrowRow.SetFadeToFailPercent( m_ColumnRenderArgs.fail_fade ); + m_pCurDisplay->m_ReceptorArrowRow.SetFadeToFailPercent( m_FieldRenderArgs.fail_fade ); NoteDisplay::Update( fDeltaTime ); /* Update all NoteDisplays. Hack: We need to call this once per frame, not @@ -861,41 +864,41 @@ void NoteField::DrawPrimitives() const PlayerOptions ¤t_po = m_pPlayerState->m_PlayerOptions.GetCurrent(); // Adjust draw range depending on some effects - m_ColumnRenderArgs.draw_pixels_after_targets= m_iDrawDistanceAfterTargetsPixels; + m_FieldRenderArgs.draw_pixels_after_targets= m_iDrawDistanceAfterTargetsPixels; // HACK: If boomerang and centered are on, then we want to draw much // earlier so that the notes don't pop on screen. float fCenteredTimesBoomerang = current_po.m_fScrolls[PlayerOptions::SCROLL_CENTERED] * current_po.m_fAccels[PlayerOptions::ACCEL_BOOMERANG]; - m_ColumnRenderArgs.draw_pixels_after_targets += int(SCALE( fCenteredTimesBoomerang, 0.f, 1.f, 0.f, -SCREEN_HEIGHT/2 )); - m_ColumnRenderArgs.draw_pixels_before_targets = m_iDrawDistanceBeforeTargetsPixels; + m_FieldRenderArgs.draw_pixels_after_targets += int(SCALE( fCenteredTimesBoomerang, 0.f, 1.f, 0.f, -SCREEN_HEIGHT/2 )); + m_FieldRenderArgs.draw_pixels_before_targets = m_iDrawDistanceBeforeTargetsPixels; float fDrawScale = 1; fDrawScale *= 1 + 0.5f * fabsf( current_po.m_fPerspectiveTilt ); fDrawScale *= 1 + fabsf( current_po.m_fEffects[PlayerOptions::EFFECT_MINI] ); - m_ColumnRenderArgs.draw_pixels_after_targets = (int)(m_ColumnRenderArgs.draw_pixels_after_targets * fDrawScale); - m_ColumnRenderArgs.draw_pixels_before_targets = (int)(m_ColumnRenderArgs.draw_pixels_before_targets * fDrawScale); + m_FieldRenderArgs.draw_pixels_after_targets = (int)(m_FieldRenderArgs.draw_pixels_after_targets * fDrawScale); + m_FieldRenderArgs.draw_pixels_before_targets = (int)(m_FieldRenderArgs.draw_pixels_before_targets * fDrawScale); // Probe for first and last notes on the screen - float fFirstBeatToDraw = FindFirstDisplayedBeat( m_pPlayerState, m_ColumnRenderArgs.draw_pixels_after_targets ); - float fLastBeatToDraw = FindLastDisplayedBeat( m_pPlayerState, m_ColumnRenderArgs.draw_pixels_before_targets ); + float fFirstBeatToDraw = FindFirstDisplayedBeat( m_pPlayerState, m_FieldRenderArgs.draw_pixels_after_targets ); + float fLastBeatToDraw = FindLastDisplayedBeat( m_pPlayerState, m_FieldRenderArgs.draw_pixels_before_targets ); m_pPlayerState->m_fLastDrawnBeat = fLastBeatToDraw; - m_ColumnRenderArgs.first_row = BeatToNoteRow(fFirstBeatToDraw); - m_ColumnRenderArgs.last_row = BeatToNoteRow(fLastBeatToDraw); + m_FieldRenderArgs.first_row = BeatToNoteRow(fFirstBeatToDraw); + m_FieldRenderArgs.last_row = BeatToNoteRow(fLastBeatToDraw); //LOG->Trace( "start = %f.1, end = %f.1", fFirstBeatToDraw-fSongBeat, fLastBeatToDraw-fSongBeat ); - //LOG->Trace( "Drawing elements %d through %d", m_ColumnRenderArgs.first_row, m_ColumnRenderArgs.last_row ); + //LOG->Trace( "Drawing elements %d through %d", m_FieldRenderArgs.first_row, m_FieldRenderArgs.last_row ); -#define IS_ON_SCREEN( fBeat ) ( fFirstBeatToDraw <= (fBeat) && (fBeat) <= fLastBeatToDraw && IsOnScreen( fBeat, 0, m_ColumnRenderArgs.draw_pixels_after_targets, m_ColumnRenderArgs.draw_pixels_before_targets ) ) +#define IS_ON_SCREEN( fBeat ) ( fFirstBeatToDraw <= (fBeat) && (fBeat) <= fLastBeatToDraw && IsOnScreen( fBeat, 0, m_FieldRenderArgs.draw_pixels_after_targets, m_FieldRenderArgs.draw_pixels_before_targets ) ) // Draw board if( SHOW_BOARD ) { - DrawBoard( m_ColumnRenderArgs.draw_pixels_after_targets, m_ColumnRenderArgs.draw_pixels_before_targets ); + DrawBoard( m_FieldRenderArgs.draw_pixels_after_targets, m_FieldRenderArgs.draw_pixels_before_targets ); } // Draw Receptors @@ -918,7 +921,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < tSigs.size(); i++) { const TimeSignatureSegment *ts = ToTimeSignature(tSigs[i]); - int iSegmentEndRow = (i + 1 == tSigs.size()) ? m_ColumnRenderArgs.last_row : tSigs[i+1]->GetRow(); + int iSegmentEndRow = (i + 1 == tSigs.size()) ? m_FieldRenderArgs.last_row : tSigs[i+1]->GetRow(); // beat bars every 16th note int iDrawBeatBarsEveryRows = BeatToNoteRow( ((float)ts->GetDen()) / 4 ) / 4; @@ -961,7 +964,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_SCROLL]->size(); i++) { ScrollSegment *seg = ToScroll( segs[SEGMENT_SCROLL]->at(i) ); - if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) + if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -973,7 +976,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_BPM]->size(); i++) { const BPMSegment *seg = ToBPM( segs[SEGMENT_BPM]->at(i) ); - if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) + if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -985,7 +988,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_STOP]->size(); i++) { const StopSegment *seg = ToStop( segs[SEGMENT_STOP]->at(i) ); - if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) + if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -997,7 +1000,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_DELAY]->size(); i++) { const DelaySegment *seg = ToDelay( segs[SEGMENT_DELAY]->at(i) ); - if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) + if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1009,7 +1012,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_WARP]->size(); i++) { const WarpSegment *seg = ToWarp( segs[SEGMENT_WARP]->at(i) ); - if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) + if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1021,7 +1024,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_TIME_SIG]->size(); i++) { const TimeSignatureSegment *seg = ToTimeSignature( segs[SEGMENT_TIME_SIG]->at(i) ); - if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) + if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1033,7 +1036,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_TICKCOUNT]->size(); i++) { const TickcountSegment *seg = ToTickcount( segs[SEGMENT_TICKCOUNT]->at(i) ); - if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) + if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1045,7 +1048,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_COMBO]->size(); i++) { const ComboSegment *seg = ToCombo( segs[SEGMENT_COMBO]->at(i) ); - if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) + if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1057,7 +1060,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_LABEL]->size(); i++) { const LabelSegment *seg = ToLabel( segs[SEGMENT_LABEL]->at(i) ); - if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) + if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1069,7 +1072,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_SPEED]->size(); i++) { const SpeedSegment *seg = ToSpeed( segs[SEGMENT_SPEED]->at(i) ); - if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) + if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1082,7 +1085,7 @@ void NoteField::DrawPrimitives() for (i = 0; i < segs[SEGMENT_FAKE]->size(); i++) { const FakeSegment *seg = ToFake( segs[SEGMENT_FAKE]->at(i) ); - if( seg->GetRow() >= m_ColumnRenderArgs.first_row && seg->GetRow() <= m_ColumnRenderArgs.last_row ) + if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row ) { float fBeat = seg->GetBeat(); if( IS_ON_SCREEN(fBeat) ) @@ -1102,8 +1105,8 @@ void NoteField::DrawPrimitives() float fSecond = a->fStartSecond; float fBeat = timing.GetBeatFromElapsedTime( fSecond ); - if( BeatToNoteRow(fBeat) >= m_ColumnRenderArgs.first_row && - BeatToNoteRow(fBeat) <= m_ColumnRenderArgs.last_row) + if( BeatToNoteRow(fBeat) >= m_FieldRenderArgs.first_row && + BeatToNoteRow(fBeat) <= m_FieldRenderArgs.last_row) { if( IS_ON_SCREEN(fBeat) ) DrawAttackText( fBeat, *a ); @@ -1121,8 +1124,8 @@ void NoteField::DrawPrimitives() FOREACH_CONST(Attack, attacks, a) { float fBeat = timing.GetBeatFromElapsedTime(a->fStartSecond); - if (BeatToNoteRow(fBeat) >= m_ColumnRenderArgs.first_row && - BeatToNoteRow(fBeat) <= m_ColumnRenderArgs.last_row && + if (BeatToNoteRow(fBeat) >= m_FieldRenderArgs.first_row && + BeatToNoteRow(fBeat) <= m_FieldRenderArgs.last_row && IS_ON_SCREEN(fBeat)) { this->DrawAttackText(fBeat, *a); @@ -1206,20 +1209,20 @@ void NoteField::DrawPrimitives() { int iBegin = m_iBeginMarker; int iEnd = m_iEndMarker; - CLAMP( iBegin, m_ColumnRenderArgs.first_row, m_ColumnRenderArgs.last_row ); - CLAMP( iEnd, m_ColumnRenderArgs.first_row, m_ColumnRenderArgs.last_row ); + CLAMP( iBegin, m_FieldRenderArgs.first_row, m_FieldRenderArgs.last_row ); + CLAMP( iEnd, m_FieldRenderArgs.first_row, m_FieldRenderArgs.last_row ); DrawAreaHighlight( iBegin, iEnd ); } else if( m_iBeginMarker != -1 ) { - if( m_iBeginMarker >= m_ColumnRenderArgs.first_row && - m_iBeginMarker <= m_ColumnRenderArgs.last_row ) + if( m_iBeginMarker >= m_FieldRenderArgs.first_row && + m_iBeginMarker <= m_FieldRenderArgs.last_row ) DrawMarkerBar( m_iBeginMarker ); } else if( m_iEndMarker != -1 ) { - if( m_iEndMarker >= m_ColumnRenderArgs.first_row && - m_iEndMarker <= m_ColumnRenderArgs.last_row ) + if( m_iEndMarker >= m_FieldRenderArgs.first_row && + m_iEndMarker <= m_FieldRenderArgs.last_row ) DrawMarkerBar( m_iEndMarker ); } } @@ -1233,9 +1236,9 @@ void NoteField::DrawPrimitives() ssprintf("NumTracks %d != ColsPerPlayer %d",m_pNoteData->GetNumTracks(), GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer)); - m_ColumnRenderArgs.selection_glow= SCALE( + m_FieldRenderArgs.selection_glow= SCALE( RageFastCos(RageTimer::GetTimeSinceStartFast()*2), -1, 1, 0.1f, 0.3f); - m_ColumnRenderArgs.fade_before_targets= FADE_BEFORE_TARGETS_PERCENT; + m_FieldRenderArgs.fade_before_targets= FADE_BEFORE_TARGETS_PERCENT; for( int j=0; jGetNumTracks(); j++ ) // for each arrow column { @@ -1248,7 +1251,7 @@ void NoteField::DrawPrimitives() void NoteField::FadeToFail() { - m_ColumnRenderArgs.fail_fade = max( 0.0f, m_ColumnRenderArgs.fail_fade ); // this will slowly increase every Update() + m_FieldRenderArgs.fail_fade = max( 0.0f, m_FieldRenderArgs.fail_fade ); // this will slowly increase every Update() // don't fade all over again if this is called twice } @@ -1446,7 +1449,7 @@ public: return 0; } - static int GetColumnActors(T* p, lua_State* L) + static int get_column_actors(T* p, lua_State* L) { lua_createtable(L, p->m_ColumnRenderers.size(), 0); for(size_t i= 0; i < p->m_ColumnRenderers.size(); ++i) @@ -1467,7 +1470,7 @@ public: ADD_METHOD(SetPressed); ADD_METHOD(DidTapNote); ADD_METHOD(DidHoldNote); - ADD_METHOD(GetColumnActors); + ADD_METHOD(get_column_actors); } }; diff --git a/src/NoteField.h b/src/NoteField.h index c10dcbd435..1d6a0f1861 100644 --- a/src/NoteField.h +++ b/src/NoteField.h @@ -105,7 +105,7 @@ protected: ~NoteDisplayCols() { delete [] display; } }; - CommonColumnRenderArgs m_ColumnRenderArgs; + NoteFieldRenderArgs m_FieldRenderArgs; /* All loaded note displays, mapped by their name. */ map m_NoteDisplays; diff --git a/src/RageMath.cpp b/src/RageMath.cpp index 93b54bc389..05bc7dab0c 100644 --- a/src/RageMath.cpp +++ b/src/RageMath.cpp @@ -41,6 +41,22 @@ void RageVec3Normalize( RageVector3* pOut, const RageVector3* pV ) pOut->z = pV->z * scale; } +void VectorFloatNormalize(vector& v) +{ + ASSERT_M(v.size() == 3, "Can't normalize a non-3D vector."); + float scale = 1.0f / sqrtf(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); + v[0]*= scale; + v[1]*= scale; + v[2]*= scale; +} + +void RageVec3Cross(RageVector3* ret, RageVector3 const* a, RageVector3 const* b) +{ + ret->x= (a->y * b->z) - (a->z * b->y); + ret->y= ((a->x * b->z) - (a->z * b->x)); + ret->z= (a->x * b->y) - (a->y * b->x); +} + void RageVec3TransformCoord( RageVector3* pOut, const RageVector3* pV, const RageMatrix* pM ) { RageVector4 temp( pV->x, pV->y, pV->z, 1.0f ); // translate @@ -314,6 +330,21 @@ void RageMatrixRotationXYZ( RageMatrix* pOut, float rX, float rY, float rZ ) pOut->m33 = 1; } +void RageAARotate(RageVector3* inret, RageVector3 const* axis, float angle) +{ + float ha= angle/2.0f; + float ca2= RageFastCos(ha); + float sa2= RageFastSin(ha); + RageVector4 quat(axis->x * sa2, axis->y * sa2, axis->z * sa2, ca2); + RageVector4 quatc(-quat.x, -quat.y, -quat.z, ca2); + RageVector4 point(inret->x, inret->y, inret->z, 0.0f); + RageQuatMultiply(&point, quat, point); + RageQuatMultiply(&point, point, quatc); + inret->x= point.x; + inret->y= point.y; + inret->z= point.z; +} + void RageQuatMultiply( RageVector4* pOut, const RageVector4 &pA, const RageVector4 &pB ) { RageVector4 out; diff --git a/src/RageMath.h b/src/RageMath.h index 4e56aac737..cac76e9549 100644 --- a/src/RageMath.h +++ b/src/RageMath.h @@ -18,6 +18,8 @@ void RageVec3AddToBounds( const RageVector3 &p, RageVector3 &mins, RageVector3 & void RageVec2Normalize( RageVector2* pOut, const RageVector2* pV ); void RageVec3Normalize( RageVector3* pOut, const RageVector3* pV ); +void VectorFloatNormalize(vector& v); +void RageVec3Cross(RageVector3* ret, RageVector3 const* a, RageVector3 const* b); void RageVec3TransformCoord( RageVector3* pOut, const RageVector3* pV, const RageMatrix* pM ); void RageVec3TransformNormal( RageVector3* pOut, const RageVector3* pV, const RageMatrix* pM ); void RageVec4TransformCoord( RageVector4* pOut, const RageVector4* pV, const RageMatrix* pM ); @@ -34,6 +36,7 @@ void RageMatrixRotationX( RageMatrix* pOut, float fTheta ); void RageMatrixRotationY( RageMatrix* pOut, float fTheta ); void RageMatrixRotationZ( RageMatrix* pOut, float fTheta ); void RageMatrixRotationXYZ( RageMatrix* pOut, float rX, float rY, float rZ ); +void RageAARotate(RageVector3* inret, RageVector3 const* axis, float angle); void RageQuatFromHPR(RageVector4* pOut, RageVector3 hpr ); void RageQuatFromPRH(RageVector4* pOut, RageVector3 prh ); void RageMatrixFromQuat( RageMatrix* pOut, const RageVector4 q ); diff --git a/src/ReceptorArrowRow.cpp b/src/ReceptorArrowRow.cpp index 19bce08bfc..c4565fd2f1 100644 --- a/src/ReceptorArrowRow.cpp +++ b/src/ReceptorArrowRow.cpp @@ -53,8 +53,6 @@ void ReceptorArrowRow::Update( float fDeltaTime ) for( unsigned c=0; c spline_pos; - (*m_renderers)[c].m_spline.evaluate((*m_renderers)[c].m_receptor_t, spline_pos); // m_fDark==1 or m_fFadeToFailPercent==1 should make fBaseAlpha==0 float fBaseAlpha = (1 - m_pPlayerState->m_PlayerOptions.GetCurrent().m_fDark); if( m_fFadeToFailPercent != -1 ) @@ -65,18 +63,7 @@ void ReceptorArrowRow::Update( float fDeltaTime ) m_ReceptorArrow[c]->SetBaseAlpha( fBaseAlpha ); // set arrow XYZ - float fX = ArrowEffects::GetXPos( m_pPlayerState, c, 0 ); - const float fY = ArrowEffects::GetYPos( m_pPlayerState, c, 0, m_fYReverseOffsetPixels ); - const float fZ = ArrowEffects::GetZPos( m_pPlayerState, c, 0 ); - m_ReceptorArrow[c]->SetX( fX + spline_pos[0] ); - m_ReceptorArrow[c]->SetY( fY + spline_pos[1] ); - m_ReceptorArrow[c]->SetZ( fZ + spline_pos[2] ); - - const float fRotation = ArrowEffects::ReceptorGetRotationZ( m_pPlayerState ); - m_ReceptorArrow[c]->SetRotationZ( fRotation ); - - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - m_ReceptorArrow[c]->SetZoom( fZoom ); + (*m_renderers)[c].UpdateReceptorGhostStuff(m_ReceptorArrow[c]); } } From f9ba1cc4159618a53ac1b5f62d4525329b40f033 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Thu, 1 Jan 2015 16:09:36 -0700 Subject: [PATCH 07/14] Added spatial extent math to CubicSpline. --- src/CubicSpline.cpp | 118 +++++++++++++++++++++++++++++++------------- src/CubicSpline.h | 4 ++ 2 files changed, 87 insertions(+), 35 deletions(-) diff --git a/src/CubicSpline.cpp b/src/CubicSpline.cpp index 8716d19774..1d28f1caf8 100644 --- a/src/CubicSpline.cpp +++ b/src/CubicSpline.cpp @@ -128,6 +128,23 @@ void SplineSolutionCache::solve_diagonals_looped(vector& diagonals) SplineSolutionCache solution_cache; +// loop_space_difference exists to handle numbers that exist in a finite +// looped space, instead of the flat infinite space. +// To put it more concretely, loop_space_difference exists to allow a spline +// to control rotation with wrapping behavior at 0.0 and 2pi, instead of +// suddenly jerking from 2pi to 0.0. -Kyz +float loop_space_difference(float a, float b, float spatial_extent); +float loop_space_difference(float a, float b, float spatial_extent) +{ + float const plus_diff= a - (b + spatial_extent); + float const minus_diff= a - (b - spatial_extent); + if(abs(plus_diff) < abs(minus_diff)) + { + return plus_diff; + } + return minus_diff; +} + void CubicSpline::solve_looped() { if(check_minimum_size()) { return; } @@ -135,9 +152,11 @@ void CubicSpline::solve_looped() vector results(m_points.size()); vector diagonals(m_points.size()); solution_cache.solve_diagonals_looped(diagonals); - results[0]= 3 * (m_points[1].a - m_points[last-1].a); + results[0]= 3 * loop_space_difference( + m_points[1].a, m_points[last-1].a, m_spatial_extent); prep_inner(last, results); - results[last-1]= 3 * (m_points[0].a - m_points[last-2].a); + results[last-1]= 3 * loop_space_difference( + m_points[0].a, m_points[last-2].a, m_spatial_extent); // The steps to solve the system of equations look like this: // | 4 1 0 0 1 | -> | 4 0 0 0 1 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | @@ -193,7 +212,8 @@ void CubicSpline::solve_straight() solution_cache.solve_diagonals_straight(diagonals); results[0]= 3 * (m_points[1].a - m_points[0].a); prep_inner(last, results); - results[last-1]= 3 * (m_points[last-1].a - m_points[last-2].a); + results[last-1]= 3 * loop_space_difference( + m_points[last-1].a, m_points[last-2].a, m_spatial_extent); // The system of equations to be solved looks like this: // | 2 1 0 0 | = | results[0] | @@ -224,10 +244,12 @@ bool CubicSpline::check_minimum_size() } if(last == 2) { - m_points[0].b= m_points[1].a - m_points[0].a; + m_points[0].b= loop_space_difference( + m_points[1].a, m_points[0].a, m_spatial_extent); m_points[0].c= m_points[0].d= 0.0f; // These will be used in the looping case. - m_points[1].b= m_points[0].a - m_points[1].a; + m_points[1].b= loop_space_difference( + m_points[0].a, m_points[1].a, m_spatial_extent); m_points[1].c= m_points[1].d= 0.0f; return true; } @@ -245,7 +267,8 @@ void CubicSpline::prep_inner(size_t last, vector& results) { for(size_t i= 1; i < last - 1; ++i) { - results[i]= 3 * (m_points[i+1].a - m_points[i-1].a); + results[i]= 3 * loop_space_difference( + m_points[i+1].a, m_points[i-1].a, m_spatial_extent); } } @@ -261,7 +284,8 @@ void CubicSpline::set_results(size_t last, vector& diagonals, vector const& b, { m_splines[n].set_coefficients(i, b[n], c[n], d[n]); } + m_dirty= true; } void CubicSplineN::get_coefficients(size_t i, vector& b, @@ -446,6 +471,21 @@ void CubicSplineN::get_coefficients(size_t i, vector& b, } } +void CubicSplineN::set_spatial_extent(size_t i, float extent) +{ + ASSERT_M(i < m_splines.size(), "CubicSplineN: index of spline to set extent" + " of is out of range."); + m_splines[i].m_spatial_extent= extent; + m_dirty= true; +} + +float CubicSplineN::get_spatial_extent(size_t i) +{ + ASSERT_M(i < m_splines.size(), "CubicSplineN: index of spline to get extent" + " of is out of range."); + return m_splines[i].m_spatial_extent; +} + void CubicSplineN::resize(size_t s) { for(spline_cont_t::iterator spline= m_splines.begin(); @@ -485,6 +525,24 @@ size_t CubicSplineN::dimension() const struct LunaCubicSplineN : Luna { + static size_t dimension_index(T* p, lua_State* L, int s) + { + size_t i= static_cast(IArg(s)-1); + if(i >= p->dimension()) + { + luaL_error(L, "Spline dimension index out of range."); + } + return i; + } + static size_t point_index(T* p, lua_State* L, int s) + { + size_t i= static_cast(IArg(s)-1); + if(i >= p->size()) + { + luaL_error(L, "Spline point index out of range."); + } + return i; + } static int solve(T* p, lua_State* L) { p->solve(); @@ -543,16 +601,8 @@ struct LunaCubicSplineN : Luna } static int set_point(T* p, lua_State* L) { - int i= IArg(1)-1; - if(i < 0) - { - luaL_error(L, "Cannot set spline point at index less than 1."); - } - if(static_cast(i) >= p->size()) - { - luaL_error(L, "Cannot set spline point at index greater than size."); - } - set_point_from_stack(p, L, static_cast(i), 2); + size_t i= point_index(p, L, 1); + set_point_from_stack(p, L, i, 2); COMMON_RETURN_SELF; } static void set_coefficients_from_stack(T* p, lua_State* L, size_t i, int s) @@ -569,29 +619,13 @@ struct LunaCubicSplineN : Luna } static int set_coefficients(T* p, lua_State* L) { - int i= IArg(1)-1; - if(i < 0) - { - luaL_error(L, "Cannot set spline coefficients at index less than 1."); - } - if(static_cast(i) >= p->size()) - { - luaL_error(L, "Cannot set spline coefficients at index greater than size."); - } + size_t i= point_index(p, L, 1); set_coefficients_from_stack(p, L, i, 2); COMMON_RETURN_SELF; } static int get_coefficients(T* p, lua_State* L) { - int i= IArg(1)-1; - if(i < 0) - { - luaL_error(L, "Cannot get spline coefficients at index less than 1."); - } - if(static_cast(i) >= p->size()) - { - luaL_error(L, "Cannot get spline coefficients at index greater than size."); - } + size_t i= point_index(p, L, 1); size_t limit= p->dimension(); vector > coeff(3); coeff[0].resize(limit); @@ -611,6 +645,18 @@ struct LunaCubicSplineN : Luna } return 1; } + static int set_spatial_extent(T* p, lua_State* L) + { + size_t i= dimension_index(p, L, 1); + p->set_spatial_extent(i, FArg(2)); + COMMON_RETURN_SELF; + } + static int get_spatial_extent(T* p, lua_State* L) + { + size_t i= dimension_index(p, L, 1); + lua_pushnumber(L, p->get_spatial_extent(i)); + return 1; + } static int resize(T* p, lua_State* L) { int siz= IArg(1); @@ -669,6 +715,8 @@ struct LunaCubicSplineN : Luna ADD_METHOD(set_point); ADD_METHOD(set_coefficients); ADD_METHOD(get_coefficients); + ADD_METHOD(set_spatial_extent); + ADD_METHOD(get_spatial_extent); ADD_METHOD(resize); ADD_METHOD(size); ADD_METHOD(redimension); diff --git a/src/CubicSpline.h b/src/CubicSpline.h index 1f71f7fbda..8cdf877cf9 100644 --- a/src/CubicSpline.h +++ b/src/CubicSpline.h @@ -7,6 +7,7 @@ struct lua_State; struct CubicSpline { +CubicSpline() :m_spatial_extent(0.0f) {} void solve_looped(); void solve_straight(); float evaluate(float t, bool loop) const; @@ -17,6 +18,7 @@ struct CubicSpline void resize(size_t s); size_t size() const; bool empty() const; + float m_spatial_extent; private: bool check_minimum_size(); void prep_inner(size_t last, vector& results); @@ -42,6 +44,8 @@ struct CubicSplineN vector const& c, vector const& d); void get_coefficients(size_t i, vector& b, vector& c, vector& d); + void set_spatial_extent(size_t i, float extent); + float get_spatial_extent(size_t i); void resize(size_t s); size_t size() const; void redimension(size_t d); From 6d9243335d2351cd0d26aad82d24a192b4660b90 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Fri, 2 Jan 2015 12:08:53 -0700 Subject: [PATCH 08/14] Fixed bug in spline evaluate. Added create_spline and spline:destroy to allow creation of splines not owned by actors. Added polygonal mode to splines. --- src/CubicSpline.cpp | 250 +++++++++++++++++++++++++++++--------------- src/CubicSpline.h | 9 +- 2 files changed, 174 insertions(+), 85 deletions(-) diff --git a/src/CubicSpline.cpp b/src/CubicSpline.cpp index 1d28f1caf8..1c41cbacc7 100644 --- a/src/CubicSpline.cpp +++ b/src/CubicSpline.cpp @@ -1,6 +1,7 @@ #include "global.h" #include "CubicSpline.h" #include "RageLog.h" +#include "RageUtil.h" #include using std::list; @@ -136,9 +137,26 @@ SplineSolutionCache solution_cache; float loop_space_difference(float a, float b, float spatial_extent); float loop_space_difference(float a, float b, float spatial_extent) { + float const norm_diff= a - b; + if(spatial_extent == 0.0f) { return norm_diff; } float const plus_diff= a - (b + spatial_extent); float const minus_diff= a - (b - spatial_extent); - if(abs(plus_diff) < abs(minus_diff)) + float const abs_norm_diff= abs(norm_diff); + float const abs_plus_diff= abs(plus_diff); + float const abs_minus_diff= abs(minus_diff); + if(abs_norm_diff < abs_plus_diff) + { + if(abs_norm_diff < abs_minus_diff) + { + return norm_diff; + } + if(abs_plus_diff < abs_minus_diff) + { + return plus_diff; + } + return minus_diff; + } + if(abs_plus_diff < abs_minus_diff) { return plus_diff; } @@ -234,6 +252,19 @@ void CubicSpline::solve_straight() set_results(last, diagonals, results); } +void CubicSpline::solve_polygonal() +{ + if(check_minimum_size()) { return; } + size_t last= m_points.size() - 1; + for(size_t i= 0; i < last; ++i) + { + m_points[i].b= loop_space_difference( + m_points[i+1].a, m_points[i].a, m_spatial_extent); + } + m_points[last].b= loop_space_difference( + m_points[0].a, m_points[last].a, m_spatial_extent); +} + bool CubicSpline::check_minimum_size() { size_t last= m_points.size(); @@ -255,7 +286,7 @@ bool CubicSpline::check_minimum_size() } float a= m_points[0].a; bool all_points_identical= true; - for(size_t i= 1; i < m_points.size(); ++i) + for(size_t i= 0; i < m_points.size(); ++i) { m_points[i].b= m_points[i].c= m_points[i].d= 0.0f; if(m_points[i].a != a) { all_points_identical= false; } @@ -298,33 +329,46 @@ void CubicSpline::set_results(size_t last, vector& diagonals, vector(t); if(loop) { - float max_t= m_points.size(); + float max_t= static_cast(m_points.size()); while(t >= max_t) { t-= max_t; } while(t < 0.0f) { t+= max_t; } - flort= static_cast(t); + p= static_cast(t); + tfrac= t - static_cast(p); } else { - if(flort <= 0) + int flort= static_cast(t); + if(flort < 0) { - return m_points[0].a; + p= 0; + tfrac= 0; } else if(static_cast(flort) >= m_points.size() - 1) { - return m_points[m_points.size() - 1].a; + p= m_points.size() - 1; + tfrac= 0; + } + else + { + p= static_cast(flort); + tfrac= t - static_cast(p); } } - size_t p= min(static_cast(flort), m_points.size()-1); - float tfrac= t - static_cast(flort); +} + +#define RETURN_IF_EMPTY if(m_points.empty()) { return 0.0f; } +#define DECLARE_P_AND_TFRAC \ +size_t p= 0; float tfrac= 0.0f; \ +p_and_tfrac_from_t(t, loop, p, tfrac); + +float CubicSpline::evaluate(float t, bool loop) const +{ + RETURN_IF_EMPTY; + DECLARE_P_AND_TFRAC; float tsq= tfrac * tfrac; float tcub= tsq * tfrac; return m_points[p].a + (m_points[p].b * tfrac) + @@ -333,32 +377,30 @@ float CubicSpline::evaluate(float t, bool loop) const float CubicSpline::evaluate_derivative(float t, bool loop) const { - if(m_points.empty()) - { - return 0.0f; - } - int flort= static_cast(t); - if(loop) - { - float max_t= m_points.size(); - while(t >= max_t) { t-= max_t; } - while(t < 0.0f) { t+= max_t; } - flort= static_cast(t); - } - else - { - if(static_cast(flort) >= m_points.size() - 1) - { - return 0.0f; - } - } - size_t p= min(static_cast(flort), m_points.size()-1); - float tfrac= t - static_cast(flort); + RETURN_IF_EMPTY; + DECLARE_P_AND_TFRAC; float tsq= tfrac * tfrac; return m_points[p].b + (2.0f * m_points[p].c * tfrac) + (3.0f * m_points[p].d * tsq); } +float CubicSpline::evaluate_second_derivative(float t, bool loop) const +{ + RETURN_IF_EMPTY; + DECLARE_P_AND_TFRAC; + return (2.0f * m_points[p].c) + (6.0f * m_points[p].d * tfrac); +} + +float CubicSpline::evaluate_third_derivative(float t, bool loop) const +{ + RETURN_IF_EMPTY; + DECLARE_P_AND_TFRAC; + return 6.0f * m_points[p].d; +} + +#undef RETURN_IF_EMPTY +#undef DECLARE_P_AND_TFRAC + void CubicSpline::set_point(size_t i, float v) { ASSERT_M(i < m_points.size(), "CubicSpline::set_point requires the index to be less than the number of points."); @@ -399,42 +441,47 @@ bool CubicSpline::empty() const void CubicSplineN::solve() { if(!m_dirty) { return; } - if(loop) +#define SOLVE_LOOP(solvent) \ + for(spline_cont_t::iterator spline= m_splines.begin(); \ + spline != m_splines.end(); ++spline) \ + { \ + spline->solvent(); \ + } + if(polygonal) { - for(spline_cont_t::iterator spline= m_splines.begin(); - spline != m_splines.end(); ++spline) - { - spline->solve_looped(); - } + SOLVE_LOOP(solve_polygonal); } else { - for(spline_cont_t::iterator spline= m_splines.begin(); - spline != m_splines.end(); ++spline) + if(loop) { - spline->solve_straight(); + SOLVE_LOOP(solve_looped); + } + else + { + SOLVE_LOOP(solve_straight); } } +#undef SOLVE_LOOP m_dirty= false; } -void CubicSplineN::evaluate(float t, vector& v) const -{ - for(spline_cont_t::const_iterator spline= m_splines.begin(); - spline != m_splines.end(); ++spline) - { - v.push_back(spline->evaluate(t, loop)); - } +#define CSN_EVAL_SOMETHING(something) \ +void CubicSplineN::something(float t, vector& v) const \ +{ \ + for(spline_cont_t::const_iterator spline= m_splines.begin(); \ + spline != m_splines.end(); ++spline) \ + { \ + v.push_back(spline->something(t, loop)); \ + } \ } -void CubicSplineN::evaluate_derivative(float t, vector& v) const -{ - for(spline_cont_t::const_iterator spline= m_splines.begin(); - spline != m_splines.end(); ++spline) - { - v.push_back(spline->evaluate_derivative(t, loop)); - } -} +CSN_EVAL_SOMETHING(evaluate); +CSN_EVAL_SOMETHING(evaluate_derivative); +CSN_EVAL_SOMETHING(evaluate_second_derivative); +CSN_EVAL_SOMETHING(evaluate_third_derivative); + +#undef CSN_EVAL_SOMETHING void CubicSplineN::set_point(size_t i, vector const& v) { @@ -548,30 +595,25 @@ struct LunaCubicSplineN : Luna p->solve(); COMMON_RETURN_SELF; } - static int evaluate(T* p, lua_State* L) - { - vector pos; - p->evaluate(FArg(1), pos); - lua_createtable(L, pos.size(), 0); - for(size_t i= 0; i < pos.size(); ++i) - { - lua_pushnumber(L, pos[i]); - lua_rawseti(L, -2, i+1); - } - return 1; - } - static int evaluate_derivative(T* p, lua_State* L) - { - vector pos; - p->evaluate_derivative(FArg(1), pos); - lua_createtable(L, pos.size(), 0); - for(size_t i= 0; i < pos.size(); ++i) - { - lua_pushnumber(L, pos[i]); - lua_rawseti(L, -2, i+1); - } - return 1; +#define LCSN_EVAL_SOMETHING(something) \ + static int something(T* p, lua_State* L) \ + { \ + vector pos; \ + p->something(FArg(1), pos); \ + lua_createtable(L, pos.size(), 0); \ + for(size_t i= 0; i < pos.size(); ++i) \ + { \ + lua_pushnumber(L, pos[i]); \ + lua_rawseti(L, -2, i+1); \ + } \ + return 1; \ } + LCSN_EVAL_SOMETHING(evaluate); + LCSN_EVAL_SOMETHING(evaluate_derivative); + LCSN_EVAL_SOMETHING(evaluate_second_derivative); + LCSN_EVAL_SOMETHING(evaluate_third_derivative); +#undef LCSN_EVAL_SOMETHING + static void get_element_table_from_stack(T* p, lua_State* L, int s, size_t limit, vector& ret) { @@ -657,6 +699,11 @@ struct LunaCubicSplineN : Luna lua_pushnumber(L, p->get_spatial_extent(i)); return 1; } + static int get_max_t(T* p, lua_State* L) + { + lua_pushnumber(L, p->size() - 1 + p->loop); + return 1; + } static int resize(T* p, lua_State* L) { int siz= IArg(1); @@ -707,16 +754,39 @@ struct LunaCubicSplineN : Luna lua_pushboolean(L, p->loop); return 1; } + static int set_polygonal(T* p, lua_State* L) + { + p->polygonal= lua_toboolean(L, 1); + COMMON_RETURN_SELF; + } + static int get_polygonal(T* p, lua_State* L) + { + lua_pushboolean(L, p->polygonal); + return 1; + } + static int destroy(T* p, lua_State* L) + { + if(p->m_owned_by_actor) + { + luaL_error(L, "This spline cannot be destroyed because it is " + "owned by an actor that relies on it existing."); + } + SAFE_DELETE(p); + return 0; + } LunaCubicSplineN() { ADD_METHOD(solve); ADD_METHOD(evaluate); ADD_METHOD(evaluate_derivative); + ADD_METHOD(evaluate_second_derivative); + ADD_METHOD(evaluate_third_derivative); ADD_METHOD(set_point); ADD_METHOD(set_coefficients); ADD_METHOD(get_coefficients); ADD_METHOD(set_spatial_extent); ADD_METHOD(get_spatial_extent); + ADD_METHOD(get_max_t); ADD_METHOD(resize); ADD_METHOD(size); ADD_METHOD(redimension); @@ -724,9 +794,21 @@ struct LunaCubicSplineN : Luna ADD_METHOD(empty); ADD_METHOD(set_loop); ADD_METHOD(get_loop); + ADD_METHOD(set_polygonal); + ADD_METHOD(get_polygonal); + ADD_METHOD(destroy); } }; -LUA_REGISTER_CLASS(CubicSplineN) +LUA_REGISTER_CLASS(CubicSplineN); + +int LuaFunc_create_spline(lua_State* L); +int LuaFunc_create_spline(lua_State* L) +{ + CubicSplineN* spline= new CubicSplineN; + spline->PushSelf(L); + return 1; +} +LUAFUNC_REGISTER_COMMON(create_spline); // Side note: Actually written between 2014/12/26 and 2014/12/28 /* diff --git a/src/CubicSpline.h b/src/CubicSpline.h index 8cdf877cf9..e09bd8a756 100644 --- a/src/CubicSpline.h +++ b/src/CubicSpline.h @@ -10,8 +10,12 @@ struct CubicSpline CubicSpline() :m_spatial_extent(0.0f) {} void solve_looped(); void solve_straight(); + void solve_polygonal(); + void p_and_tfrac_from_t(float t, bool loop, size_t& p, float& tfrac) const; float evaluate(float t, bool loop) const; float evaluate_derivative(float t, bool loop) const; + float evaluate_second_derivative(float t, bool loop) const; + float evaluate_third_derivative(float t, bool loop) const; void set_point(size_t i, float v); void set_coefficients(size_t i, float b, float c, float d); void get_coefficients(size_t i, float& b, float& c, float& d); @@ -34,11 +38,13 @@ private: struct CubicSplineN { CubicSplineN() - :m_owned_by_actor(false), m_dirty(true) + :loop(false), polygonal(false), m_owned_by_actor(false), m_dirty(true) {} void solve(); void evaluate(float t, vector& v) const; void evaluate_derivative(float t, vector& v) const; + void evaluate_second_derivative(float t, vector& v) const; + void evaluate_third_derivative(float t, vector& v) const; void set_point(size_t i, vector const& v); void set_coefficients(size_t i, vector const& b, vector const& c, vector const& d); @@ -53,6 +59,7 @@ struct CubicSplineN bool empty() const; typedef vector spline_cont_t; bool loop; + bool polygonal; bool m_owned_by_actor; void PushSelf(lua_State* L); From dd72b368ce415d3c7d4393f60dd4c8147ab46ac5 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Sat, 3 Jan 2015 05:26:29 -0700 Subject: [PATCH 09/14] Fixed spline solving to be mathematically correct after Matt told me column operations are not actually allowed. Attempts to measure performance returned garbage results, hopefully the caching mechanism makes it too fast to measure? --- src/CubicSpline.cpp | 279 ++++++++++++++++++++++++++++---------------- 1 file changed, 179 insertions(+), 100 deletions(-) diff --git a/src/CubicSpline.cpp b/src/CubicSpline.cpp index 1c41cbacc7..c500060ec8 100644 --- a/src/CubicSpline.cpp +++ b/src/CubicSpline.cpp @@ -20,29 +20,39 @@ using std::list; struct SplineSolutionCache { - void solve_diagonals_straight(vector& diagonals); - void solve_diagonals_looped(vector& diagonals); + struct Entry + { + vector diagonals; + vector multiples; + }; + void solve_diagonals_straight(vector& diagonals, vector& multiples); + void solve_diagonals_looped(vector& diagonals, vector& multiples); private: void prep_inner(size_t last, vector& out); - bool find_in_cache(list >& cache, vector& out); - void add_to_cache(list >& cache, vector& out); - list > straight_diagonals; - list > looped_diagonals; + bool find_in_cache(list& cache, vector& outd, vector& outm); + void add_to_cache(list& cache, vector& outd, vector& outm); + list straight_diagonals; + list looped_diagonals; }; size_t const solution_cache_limit= 16; -bool SplineSolutionCache::find_in_cache(list >& cache, vector& out) +bool SplineSolutionCache::find_in_cache(list& cache, vector& outd, vector& outm) { - size_t out_size= out.size(); - for(list >::iterator entry= cache.begin(); + size_t out_size= outd.size(); + for(list::iterator entry= cache.begin(); entry != cache.end(); ++entry) { - if(out_size == entry->size()) + if(out_size == entry->diagonals.size()) { for(size_t i= 0; i < out_size; ++i) { - out[i]= (*entry)[i]; + outd[i]= entry->diagonals[i]; + } + outm.resize(entry->multiples.size()); + for(size_t i= 0; i < entry->multiples.size(); ++i) + { + outm[i]= entry->multiples[i]; } return true; } @@ -50,13 +60,15 @@ bool SplineSolutionCache::find_in_cache(list >& cache, vector >& cache, vector& out) +void SplineSolutionCache::add_to_cache(list& cache, vector& outd, vector& outm) { if(cache.size() >= solution_cache_limit) { cache.pop_back(); } - cache.push_front(out); + cache.push_front(Entry()); + cache.front().diagonals= outd; + cache.front().multiples= outm; } void SplineSolutionCache::prep_inner(size_t last, vector& out) @@ -67,64 +79,138 @@ void SplineSolutionCache::prep_inner(size_t last, vector& out) } } -void SplineSolutionCache::solve_diagonals_straight(vector& diagonals) +void SplineSolutionCache::solve_diagonals_straight(vector& diagonals, vector& multiples) { - if(find_in_cache(straight_diagonals, diagonals)) + if(find_in_cache(straight_diagonals, diagonals, multiples)) { return; } + + // Solution steps: + // Two stages: First, work downwards, zeroing the 1s below each diagonal. + // | 2 1 0 0 | -> | 2 1 0 0 | -> | 2 1 0 0 | -> | 2 1 0 0 | + // | 1 4 1 0 | -> | 0 a 1 0 | -> | 0 d 1 0 | -> | 0 a 1 0 | + // | 0 1 4 1 | -> | 0 1 4 1 | -> | 0 0 b 1 | -> | 0 0 b 1 | + // | 0 0 1 2 | -> | 0 0 1 2 | -> | 0 0 1 2 | -> | 0 0 0 c | + // Second stage: Work upwards, zeroing the 1s above each diagonal. + // V + // | 2 1 0 0 | -> | 2 1 0 0 | -> | 2 0 0 0 | + // | 0 a 1 0 | -> | 0 a 0 0 | -> | 0 a 0 0 | + // | 0 0 b 0 | -> | 0 0 b 0 | -> | 0 0 b 0 | + // | 0 0 0 c | -> | 0 0 0 c | -> | 0 0 0 c | + size_t last= diagonals.size(); diagonals[0]= 2.0f; prep_inner(last-1, diagonals); diagonals[last-1]= 2.0f; - // Operation: Add col[0] * -.5 to col[1] to zero [r0][c1]. + + // Stage one. + // Operation: Add row[0] * -.5 to row[1] to zero [r1][c0]. diagonals[1]-= .5f; + multiples.push_back(.5f); for(size_t i= 1; i < last-1; ++i) { - // Operation: Add col[i] / -[ri][ci] to col[i+1] to zero [ri][ci+1]. - diagonals[i+1]-= 1.0f / diagonals[i]; + // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci]. + float const diag_recip= 1.0f / diagonals[i]; + diagonals[i+1]-= diag_recip; + multiples.push_back(diag_recip); + } + // Stage two. + for(size_t i= last-1; i > 0; --i) + { + // Operation: Add row [i] / -[ri][ci] to row[i-1] to zero [ri-1][ci]. + multiples.push_back(1.0f / diagonals[i]); } // Solving finished. - add_to_cache(straight_diagonals, diagonals); + add_to_cache(straight_diagonals, diagonals, multiples); } -void SplineSolutionCache::solve_diagonals_looped(vector& diagonals) +void SplineSolutionCache::solve_diagonals_looped(vector& diagonals, vector& multiples) { - if(find_in_cache(looped_diagonals, diagonals)) + if(find_in_cache(looped_diagonals, diagonals, multiples)) { return; } + + // The steps to solve the system of equations look like this: + // Stage one: Zero the 1s below the diagonals. + // | 4 1 0 0 1 | -> | 4 1 0 0 1 | -> | 4 1 0 0 1 | -> | 4 1 0 0 1 | + // | 1 4 1 0 0 | -> | 0 a 1 0 u | -> | 0 a 1 0 u | -> | 0 a 1 0 u | + // | 0 1 4 1 0 | -> | 0 1 4 1 0 | -> | 0 0 b 1 v | -> | 0 0 b 1 v | + // | 0 0 1 4 1 | -> | 0 0 1 4 1 | -> | 0 0 1 4 1 | -> | 0 0 0 c w | + // | 1 0 0 1 4 | -> | 1 0 0 1 4 | -> | 1 0 0 1 4 | -> | 1 0 0 1 4 | + // V + // | 4 1 0 0 1 | + // | 0 a 1 0 u | + // | 0 0 b 1 v | + // | 0 0 0 c w | + // | 1 0 0 0 d | + // The top of the right column is left unzeroed because it will be changed + // by stage two, nullifying the effect of zeroing it. + // V Stage two: Zero the 1s above the diagonals, starting with the second + // to last row to avoid carrying effects across the left column. + // | 4 1 0 0 1 | -> | 4 1 0 0 1 | -> | 4 0 0 0 z | -> | 4 0 0 0 z | + // | 0 a 1 0 u | -> | 0 a 0 0 y | -> | 0 a 0 0 y | -> | 0 a 0 0 y | + // | 0 0 b 0 x | -> | 0 0 b 0 x | -> | 0 0 b 0 x | -> | 0 0 b 0 x | + // | 0 0 0 c w | -> | 0 0 0 c w | -> | 0 0 0 c w | -> | 0 0 0 c w | + // | 1 0 0 0 d | -> | 1 0 0 0 d | -> | 1 0 0 0 d | -> | 0 0 0 0 f | + // V Stage three: Zero the right column. + // | 4 0 0 0 0 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | + // | 0 a 0 0 y | -> | 0 a 0 0 0 | -> | 0 a 0 0 0 | -> | 0 a 0 0 0 | + // | 0 0 b 0 x | -> | 0 0 b 0 x | -> | 0 0 b 0 0 | -> | 0 0 b 0 0 | + // | 0 0 0 c w | -> | 0 0 0 c w | -> | 0 0 0 c w | -> | 0 0 0 c 0 | + // | 0 0 0 0 f | -> | 0 0 0 0 f | -> | 0 0 0 0 f | -> | 0 0 0 0 f | + size_t last= diagonals.size(); diagonals[0]= 4.0f; prep_inner(last, diagonals); + // right_column is sized to not store the diagonal . + vector right_column(diagonals.size()-1, 0.0f); + right_column[0]= 1.0f; + right_column[last-2]= 1.0f; - size_t end= last-1; - size_t stop= end-1; - float cedge= 1.0f; // [ri][cl] - float redge= 1.0f; // [rl][ci] - // The loop stops before end because the case where [ri][cl] == [ri][ci+1] - // needs special handling. - for(size_t i= 0; i < stop; ++i) + // Stage one. + for(size_t i= 0; i < last-2; ++i) { - float next_cedge= 0.0f; // [ri+1][ce] - float next_redge= 0.0f; // [re][ci+1] - // Operation: Add col[i] / -[ri][ci] to col[i+1] to zero [ri][ci+1]. - float diag_recip= 1.0f / diagonals[i]; - diagonals[i+1]-= diag_recip; - next_redge-= redge * diag_recip; // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci]. - next_cedge-= cedge * diag_recip; - // Operation: Add col[i] * -(cedge/[ri][ci]) to col[e] to zero cedge. - diagonals[end]-= redge * (cedge / diagonals[i]); - cedge= next_cedge; // Do not use cedge after this point in the loop. - // Operation: Add row[i] * -(redge/[ri][ci]) to row[e] to zero redge. - redge= next_redge; // Do not use redge after this point in the loop. + float const diag_recip= 1.0f / diagonals[i]; + diagonals[i+1]-= diag_recip; + right_column[i+1]-= right_column[i] * diag_recip; + multiples.push_back(diag_recip); } - // [rs][ce] is 1 - cedge, [re][cs] is 1 - redge - // Operation: Add col[s] * -([rs][ce] / [rs][cs]) to col[e] to zero redge. - diagonals[end]-= redge * ((1.0f - cedge) / diagonals[stop]); + // Last step of stage one needs special handling for right_column. + // Operation: Add row[l-2] / [rl-2][cl-2] to row[l-1] to zero [rl-1][cl-2]. + { + float const diag_recip= 1.0f / diagonals[last-2]; + diagonals[last-1]-= right_column[last-2] * diag_recip; + multiples.push_back(diag_recip); + } + // Stage two. + for(size_t i= last-2; i > 0; --i) + { + // Operation: Add row[i] / -[ri][ci] to row[i-1] to zero [ri-1][ci]. + float const diag_recip= 1.0f / diagonals[i]; + right_column[i-1]-= right_column[i] * diag_recip; + multiples.push_back(diag_recip); + } + // Last step of stage two. + { + // Operation: Add row[0] / [r0][c0] to row[l-1] to zero [rl-1][c0]. + float const diag_recip= 1.0f / diagonals[0]; + right_column[0]-= right_column[1] * diag_recip; + multiples.push_back(diag_recip); + } + // Stage three. + size_t const end= last-1; + for(size_t i= 0; i < end; ++i) + { + // Operation: Add row[e] * (right_column[i] / [re][ce]) to row[i] to + // zero right_column[i]. + multiples.push_back(right_column[i] / diagonals[end]); + } + // Solving finished. - add_to_cache(looped_diagonals, diagonals); + add_to_cache(looped_diagonals, diagonals, multiples); } SplineSolutionCache solution_cache; @@ -169,55 +255,46 @@ void CubicSpline::solve_looped() size_t last= m_points.size(); vector results(m_points.size()); vector diagonals(m_points.size()); - solution_cache.solve_diagonals_looped(diagonals); + vector multiples; + solution_cache.solve_diagonals_looped(diagonals, multiples); results[0]= 3 * loop_space_difference( m_points[1].a, m_points[last-1].a, m_spatial_extent); prep_inner(last, results); results[last-1]= 3 * loop_space_difference( m_points[0].a, m_points[last-2].a, m_spatial_extent); - // The steps to solve the system of equations look like this: - // | 4 1 0 0 1 | -> | 4 0 0 0 1 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | - // | 1 4 1 0 0 | -> | 0 d 1 0 x | -> | 0 d 1 0 x | -> | 0 d 0 0 x | - // | 0 1 4 1 0 | -> | 0 1 4 1 0 | -> | 0 1 4 1 0 | -> | 0 0 d 1 x | - // | 0 0 1 4 1 | -> | 0 0 1 4 1 | -> | 0 0 1 4 1 | -> | 0 0 1 4 1 | - // | 1 0 0 1 4 | -> | 1 x 0 1 4 | -> | 0 x 0 1 q | -> | 0 x x 1 q | - // V - // | 4 0 0 0 0 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | - // | 0 d 0 0 0 | -> | 0 d 0 0 0 | -> | 0 d 0 0 0 | -> | 0 d 0 0 0 | - // | 0 0 d 1 x | -> | 0 0 d 0 x | -> | 0 0 d 0 0 | -> | 0 0 d 0 0 | - // | 0 0 1 d 1 | -> | 0 0 0 d n | -> | 0 0 0 d n | -> | 0 0 0 d 0 | - // | 0 0 x 1 r | -> | 0 0 x n r | -> | 0 0 0 n s | -> | 0 0 0 0 t | - // Each time through the loop performs two of these steps, 4 operations. - // All operations on diagonals are done by the solution cache, because the - // diagonals come out the same for all splines of a given size. - - size_t end= last-1; - size_t stop= end-1; - float cedge= 1.0f; // [ri][cl] - float redge= 1.0f; // [rl][ci] - // The loop stops before end because the case where [ri][cl] == [ri][ci+1] - // needs special handling. - for(size_t i= 0; i < stop; ++i) + // Steps explained in detail in SplineSolutionCache. + // Only the operations on the results column are performed here. + // Stage one. + // SplineSolutionCache's Stage one loop ends at last-2 because it has to + // handle right_column. This does not handle right_column, so the loop + // goes to last-1. + for(size_t i= 0; i < last-1; ++i) { - float next_cedge= 0.0f; // [ri+1][ce] - float next_redge= 0.0f; // [re][ci+1] - // Operation: Add col[i] / -[ri][ci] to col[i+1] to zero [ri][ci+1]. - float diag_recip= 1.0f / diagonals[i]; - next_redge-= redge * diag_recip; - // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci]. - results[i+1]-= results[i] * diag_recip; - next_cedge-= cedge * diag_recip; - // Operation: Add col[i] * -(cedge/[ri][ci]) to col[e] to zero cedge. - cedge= next_cedge; // Do not use cedge after this point in the loop. - // Operation: Add row[i] * -(redge/[ri][ci]) to row[e] to zero redge. - results[end]-= results[i] * (redge / diagonals[i]); - redge= next_redge; // Do not use redge after this point in the loop. + // Operation: Add row[i] * -multiples[i] to row[i+1]. + results[i+1]-= results[i] * multiples[i]; } - // [rs][ce] is 1 - cedge, [re][cs] is 1 - redge - // Operation: Add row[s] * -([re][cs] / [rs][cs]) to row[e] to zero redge. - results[end]-= results[stop] * ((1.0f - redge) / diagonals[stop]); - + size_t next_mult= last-1; + // Stage two. + for(size_t i= last-2; i > 0; --i) + { + // Operation: Add row[i] * -multiples[nm] to row[i-1]. + results[i-1]-= results[i] * multiples[next_mult]; + ++next_mult; + } + // Last step of stage two. + // Operation: Add row[0] * -multiples[nm] to row[l-1]. + results[last-1]-= results[0] * multiples[next_mult]; + ++next_mult; + // Stage three. + size_t const end= last-1; + for(size_t i= 0; i < end; ++i) + { + // Operation: Add row[e] * -multiples[nm] to row[i]. + results[i]-= results[end] * multiples[next_mult]; + ++next_mult; + } + // Solving finished. set_results(last, diagonals, results); } @@ -227,28 +304,30 @@ void CubicSpline::solve_straight() size_t last= m_points.size(); vector results(m_points.size()); vector diagonals(m_points.size()); - solution_cache.solve_diagonals_straight(diagonals); + vector multiples; + solution_cache.solve_diagonals_straight(diagonals, multiples); results[0]= 3 * (m_points[1].a - m_points[0].a); prep_inner(last, results); results[last-1]= 3 * loop_space_difference( m_points[last-1].a, m_points[last-2].a, m_spatial_extent); - // The system of equations to be solved looks like this: - // | 2 1 0 0 | = | results[0] | - // | 1 4 1 0 | = | results[1] | - // | 0 1 4 1 | = | results[2] | - // | 0 0 1 2 | = | results[3] | - // Operations are carefully chosen to only modify the values in the - // diagonals and the results, leaving the 1s unchanged. - // All operations on diagonals are done by the solution cache, because the - // diagonals come out the same for all splines of a given size. - // Operation: Add row[0] * -.5 to row[1] to zero [r1][c0]. - results[1]-= results[0] * .5f; - for(size_t i= 1; i < last - 1; ++i) + // Steps explained in detail in SplineSolutionCache. + // Only the operations on the results column are performed here. + // Stage one. + for(size_t i= 0; i < last-1; ++i) { - // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci]; - results[i]-= results[i-1] * (1.0f / diagonals[i]); + // Operation: Add row[i] * -multiples[i] to row[i+1]. + results[i+1]-= results[i] * multiples[i]; } + size_t next_mult= last-1; + // Stage two. + for(size_t i= last-1; i > 0; --i) + { + // Operation: Add row[i] * -multiples[nm] to row [i-1]. + results[i-1]-= results[i] * multiples[next_mult]; + ++next_mult; + } + // Solving finished. set_results(last, diagonals, results); } From 6c722df0c6f8bb1d92cdf9d831932510cb51a282 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Sat, 3 Jan 2015 05:56:52 -0700 Subject: [PATCH 10/14] Made loop and polygonal private so that setting them would trip the dirty flag. --- src/ActorMultiVertex.cpp | 13 --------- src/ActorMultiVertex.h | 2 -- src/CubicSpline.cpp | 62 +++++++++++++++++++++++++--------------- src/CubicSpline.h | 12 ++++++-- 4 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/ActorMultiVertex.cpp b/src/ActorMultiVertex.cpp index 5d3c75b00b..e93ba2a21e 100644 --- a/src/ActorMultiVertex.cpp +++ b/src/ActorMultiVertex.cpp @@ -65,7 +65,6 @@ ActorMultiVertex::ActorMultiVertex() _TextureMode = TextureMode_Modulate; _using_spline= false; _spline.redimension(3); - _spline.loop= false; _spline.m_owned_by_actor= true; } @@ -252,10 +251,6 @@ void ActorMultiVertex::SplineSolve() case DrawMode_LineStrip: { float conversion= static_cast(_spline.size()-1) / static_cast(num_verts-1); - if(_spline.loop) - { - conversion= static_cast(_spline.size()) / static_cast(num_verts-1); - } for(size_t i= 0; i < num_verts; ++i) { float t= i * conversion; @@ -735,12 +730,6 @@ public: return 1; } DEFINE_METHOD(GetUseSpline, GetUseSpline()); - DEFINE_METHOD(SplineGetLoop, SplineGetLoop()); - static int SplineSetLoop(T* p, lua_State* L) - { - p->SplineSetLoop(lua_toboolean(L, 1)); - COMMON_RETURN_SELF; - } static int SplineSize(T* p, lua_State* L) { lua_pushnumber(L, p->SplineSize()); @@ -799,8 +788,6 @@ public: ADD_METHOD(GetUseSpline); ADD_METHOD(SetUseSpline); - ADD_METHOD(SplineGetLoop); - ADD_METHOD(SplineSetLoop); ADD_METHOD(SplineSize); ADD_METHOD(SplineResize); ADD_METHOD(SplineSetPoint); diff --git a/src/ActorMultiVertex.h b/src/ActorMultiVertex.h index 60a322f811..3742bee269 100644 --- a/src/ActorMultiVertex.h +++ b/src/ActorMultiVertex.h @@ -106,8 +106,6 @@ public: bool GetUseSpline(); void SetUseSpline(bool use); - bool SplineGetLoop() { return _spline.loop; } - void SplineSetLoop(bool loop) { _spline.loop= loop; } void SplineSetPoint(size_t i, float x , float y , float z); void SplineResize(size_t s); size_t SplineSize(); diff --git a/src/CubicSpline.cpp b/src/CubicSpline.cpp index c500060ec8..2badfe4646 100644 --- a/src/CubicSpline.cpp +++ b/src/CubicSpline.cpp @@ -526,13 +526,13 @@ void CubicSplineN::solve() { \ spline->solvent(); \ } - if(polygonal) + if(m_polygonal) { SOLVE_LOOP(solve_polygonal); } else { - if(loop) + if(m_loop) { SOLVE_LOOP(solve_looped); } @@ -551,7 +551,7 @@ void CubicSplineN::something(float t, vector& v) const \ for(spline_cont_t::const_iterator spline= m_splines.begin(); \ spline != m_splines.end(); ++spline) \ { \ - v.push_back(spline->something(t, loop)); \ + v.push_back(spline->something(t, m_loop)); \ } \ } @@ -647,6 +647,25 @@ size_t CubicSplineN::dimension() const return m_splines.size(); } +// m_dirty is set before the member so that the set_dirty that is created +// can actually be used to set the dirty flag. -Kyz +#define SET_GET_MEM(member, name) \ +void CubicSplineN::set_##name(bool b) \ +{ \ + m_dirty= true; \ + member= b; \ +} \ +bool CubicSplineN::get_##name() \ +{ \ + return member; \ +} + +SET_GET_MEM(m_loop, loop); +SET_GET_MEM(m_polygonal, polygonal); +SET_GET_MEM(m_dirty, dirty); + +#undef SET_GET_MEM + #include "LuaBinding.h" struct LunaCubicSplineN : Luna @@ -780,7 +799,7 @@ struct LunaCubicSplineN : Luna } static int get_max_t(T* p, lua_State* L) { - lua_pushnumber(L, p->size() - 1 + p->loop); + lua_pushnumber(L, p->size() - 1 + p->get_loop()); return 1; } static int resize(T* p, lua_State* L) @@ -823,26 +842,21 @@ struct LunaCubicSplineN : Luna lua_pushboolean(L, p->empty()); return 1; } - static int set_loop(T* p, lua_State* L) - { - p->loop= lua_toboolean(L, 1); - COMMON_RETURN_SELF; - } - static int get_loop(T* p, lua_State* L) - { - lua_pushboolean(L, p->loop); - return 1; - } - static int set_polygonal(T* p, lua_State* L) - { - p->polygonal= lua_toboolean(L, 1); - COMMON_RETURN_SELF; - } - static int get_polygonal(T* p, lua_State* L) - { - lua_pushboolean(L, p->polygonal); - return 1; +#define SET_GET_LUA(name) \ + static int set_##name(T* p, lua_State* L) \ + { \ + p->set_##name(lua_toboolean(L, 1)); \ + COMMON_RETURN_SELF; \ + } \ + static int get_##name(T* p, lua_State* L) \ + { \ + lua_pushboolean(L, p->get_##name()); \ + return 1; \ } + SET_GET_LUA(loop); + SET_GET_LUA(polygonal); + SET_GET_LUA(dirty); +#undef SET_GET_LUA static int destroy(T* p, lua_State* L) { if(p->m_owned_by_actor) @@ -875,6 +889,8 @@ struct LunaCubicSplineN : Luna ADD_METHOD(get_loop); ADD_METHOD(set_polygonal); ADD_METHOD(get_polygonal); + ADD_METHOD(set_dirty); + ADD_METHOD(get_dirty); ADD_METHOD(destroy); } }; diff --git a/src/CubicSpline.h b/src/CubicSpline.h index e09bd8a756..3cfdaec58d 100644 --- a/src/CubicSpline.h +++ b/src/CubicSpline.h @@ -38,7 +38,7 @@ private: struct CubicSplineN { CubicSplineN() - :loop(false), polygonal(false), m_owned_by_actor(false), m_dirty(true) + :m_owned_by_actor(false), m_loop(false), m_polygonal(false), m_dirty(true) {} void solve(); void evaluate(float t, vector& v) const; @@ -58,12 +58,18 @@ struct CubicSplineN size_t dimension() const; bool empty() const; typedef vector spline_cont_t; - bool loop; - bool polygonal; + void set_loop(bool l); + bool get_loop(); + void set_polygonal(bool p); + bool get_polygonal(); + void set_dirty(bool d); + bool get_dirty(); bool m_owned_by_actor; void PushSelf(lua_State* L); private: + bool m_loop; + bool m_polygonal; bool m_dirty; spline_cont_t m_splines; }; From 703b423dd42b26444ac8e59c056a7524c10d4e9f Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Sun, 4 Jan 2015 06:10:21 -0700 Subject: [PATCH 11/14] Moved note column splines into tween state. Renamed size/dimension spline lua functions to get/set convention. Added make_camel_aliases to 01 alias.lua to make the CamelCase aliases required by shakesoda for consistency. --- Themes/_fallback/Scripts/00 init.lua | 24 +++++ Themes/_fallback/Scripts/01 alias.lua | 56 ++++++++++ Themes/_fallback/Scripts/02 Utilities.lua | 24 ----- src/CubicSpline.cpp | 125 ++++++++++++++++++++-- src/CubicSpline.h | 12 ++- src/NoteDisplay.cpp | 109 ++++++++++++++++--- src/NoteDisplay.h | 44 ++++++-- src/RageUtil.h | 6 ++ 8 files changed, 340 insertions(+), 60 deletions(-) diff --git a/Themes/_fallback/Scripts/00 init.lua b/Themes/_fallback/Scripts/00 init.lua index 64062f0efb..a3c353f946 100644 --- a/Themes/_fallback/Scripts/00 init.lua +++ b/Themes/_fallback/Scripts/00 init.lua @@ -43,6 +43,30 @@ function math.round(n) end end +function split(delimiter, text) + local list = {} + local pos = 1 + while 1 do + local first,last = string.find(text, delimiter, pos) + if first then + table.insert(list, string.sub(text, pos, first-1)) + pos = last+1 + else + table.insert(list, string.sub(text, pos)) + break + end + end + return list +end + +function join(delimiter, list) + local ret = list[1] + for i = 2,table.getn(list) do + ret = ret .. delimiter .. list[i] + end + return ret or "" +end + -- (c) 2006 Glenn Maynard -- All rights reserved. -- diff --git a/Themes/_fallback/Scripts/01 alias.lua b/Themes/_fallback/Scripts/01 alias.lua index f482a4c03e..507d09dd2c 100644 --- a/Themes/_fallback/Scripts/01 alias.lua +++ b/Themes/_fallback/Scripts/01 alias.lua @@ -24,6 +24,62 @@ _safe = { --[[ compatibility aliases ]] +function alias_one(class, main_name, alt_name) + if type(main_name) ~= "string" then + lua.ReportScriptError("Name of function to make an alias for must be a string.") + return + end + if type(alt_name) ~= "string" then + lua.ReportScriptError("Alias name of function must be a string.") + return + end + class[alt_name]= class[main_name] +end + +function alias_set(class, set) + assert(type(class) == "table" and type(set) == "table", + "alias_set must be passed a class and a set of names to make alieases.") + for i, fun in ipairs(set) do + if type(fun) == "table" then + local main_name= fun[1] + if type(set[2]) == "table" then + for n, alt_name in ipairs(set[2]) do + alias_one(class, main_name, alt_name) + end + elseif type(set[2]) == "string" then + alias_one(class, main_name, set[2]) + end + else + lua.ReportScriptError("alias entry " .. i .. " in set passed to " .. + "alias_set is not a table.") + end + end +end + +function make_camel_aliases(class) + local name_list= {} + for name, fun in pairs(class) do + if type(fun) == "function" and type(name) == "string" then + name_list[#name_list+1]= name + end + end + for i, name in ipairs(name_list) do + local words= split("_", name) + for o, w in ipairs(words) do + words[o]= w:sub(1,1):upper() .. w:sub(2) + end + local camel_name= join("", words) + if name ~= camel_name then + alias_one(class, name, camel_name) + end + end +end + +make_camel_aliases(CubicSplineN) +make_camel_aliases(NCSplineHandler) +make_camel_aliases(NoteColumnRenderer) +make_camel_aliases(NoteField) + --[[ ActorScroller: all of these got renamed, so alias the lowercase ones if themes are going to look for them. ]] ActorScroller.getsecondtodestination = ActorScroller.GetSecondsToDestination diff --git a/Themes/_fallback/Scripts/02 Utilities.lua b/Themes/_fallback/Scripts/02 Utilities.lua index 53a3acf534..b933ce9d19 100644 --- a/Themes/_fallback/Scripts/02 Utilities.lua +++ b/Themes/_fallback/Scripts/02 Utilities.lua @@ -29,30 +29,6 @@ function TableStringLookup(t, group) return ret end -function split(delimiter, text) - local list = {} - local pos = 1 - while 1 do - local first,last = string.find(text, delimiter, pos) - if first then - table.insert(list, string.sub(text, pos, first-1)) - pos = last+1 - else - table.insert(list, string.sub(text, pos)) - break - end - end - return list -end - -function join(delimiter, list) - local ret = list[1] - for i = 2,table.getn(list) do - ret = ret .. delimiter .. list[i] - end - return ret or "" -end - function wrap(val,n) local x = val Trace( "wrap "..x.." "..n ) diff --git a/src/CubicSpline.cpp b/src/CubicSpline.cpp index 2badfe4646..5a20f34e4d 100644 --- a/src/CubicSpline.cpp +++ b/src/CubicSpline.cpp @@ -494,7 +494,7 @@ void CubicSpline::set_coefficients(size_t i, float b, float c, float d) m_points[i].d= d; } -void CubicSpline::get_coefficients(size_t i, float& b, float& c, float& d) +void CubicSpline::get_coefficients(size_t i, float& b, float& c, float& d) const { ASSERT_M(i < m_points.size(), "CubicSpline: point index must be less than the number of points."); b= m_points[i].b; @@ -502,6 +502,20 @@ void CubicSpline::get_coefficients(size_t i, float& b, float& c, float& d) d= m_points[i].d; } +void CubicSpline::set_point_and_coefficients(size_t i, float a, float b, + float c, float d) +{ + set_coefficients(i, b, c, d); + m_points[i].a= a; +} + +void CubicSpline::get_point_and_coefficients(size_t i, float& a, float& b, + float& c, float& d) const +{ + get_coefficients(i, b, c, d); + a= m_points[i].a; +} + void CubicSpline::resize(size_t s) { m_points.resize(s); @@ -517,6 +531,96 @@ bool CubicSpline::empty() const return m_points.empty(); } +void CubicSplineN::weighted_average(CubicSplineN& out, + CubicSplineN const& from, CubicSplineN const& to, float between) +{ + ASSERT_M(out.dimension() == from.dimension() && + to.dimension() == from.dimension(), + "Cannot tween splines of different dimensions."); +#define BOOLS_FROM_CLOSEST(closest) \ + out.set_loop(closest.get_loop()); \ + out.set_polygonal(closest.get_polygonal()); + if(between >= 0.5f) + { + BOOLS_FROM_CLOSEST(to); + } + else + { + BOOLS_FROM_CLOSEST(from); + } +#undef BOOLS_FROM_CLOSEST + // Behavior for splines of different sizes: Use a size between the two. + // Points that exist in both will be averaged. + // Points that only exist in one will come only from that one. + size_t const from_size= from.size(); + size_t const to_size= to.size(); + size_t out_size= to_size; + size_t limit= to_size; + if(from_size < to_size) + { + out_size= from_size + static_cast( + static_cast(to_size - from_size) * between); + } + else if(to_size < from_size) + { + limit= from_size; + out_size= to_size + static_cast( + static_cast(from_size - to_size) * between); + } + CLAMP(out_size, 0, limit); + out.resize(out_size); + + for(size_t spli= 0; spli < out.m_splines.size(); ++spli) + { + for(size_t p= 0; p < out_size; ++p) + { + float fc[4]= {0.0f, 0.0f, 0.0f, 0.0f}; + float tc[4]= {0.0f, 0.0f, 0.0f, 0.0f}; + if(p < from_size) + { + from.m_splines[spli].get_point_and_coefficients(p, fc[0], fc[1], + fc[2], fc[3]); + } + if(p < to_size) + { + to.m_splines[spli].get_point_and_coefficients(p, tc[0], tc[1], + tc[2], tc[3]); + } + else + { + for(int i= 0; i < 4; ++i) + { + tc[i]= fc[i]; + } + } + if(p >= from_size) + { + for(int i= 0; i < 4; ++i) + { + fc[i]= tc[i]; + } + } + float oc[4]= {0.0f, 0.0f, 0.0f, 0.0f}; + for(int i= 0; i < 4; ++i) + { + oc[i]= lerp(between, fc[i], tc[i]); + } + out.m_splines[spli].set_point_and_coefficients(p, oc[0], oc[1], oc[2], + oc[3]); + } + } + // The spline is not solved after averaging because my testing showed that + // it is unnecessary. + // My testing method was this: + // Spline A is generated by lerping all points and coefficients. + // Spline B is generated by lerping all points then solving. + // The coefficients for Spline A and Spline B are identical to 5 to 9 + // significant digits. Thus, solving is unnecessary. + // Additionally, solving would require a mechanism to disable solving for + // the people that wish to set their own coefficients instead of solving. + // -Kyz +} + void CubicSplineN::solve() { if(!m_dirty) { return; } @@ -655,7 +759,7 @@ void CubicSplineN::set_##name(bool b) \ m_dirty= true; \ member= b; \ } \ -bool CubicSplineN::get_##name() \ +bool CubicSplineN::get_##name() const \ { \ return member; \ } @@ -728,6 +832,7 @@ struct LunaCubicSplineN : Luna { ret.push_back(0.0f); } + ret.resize(limit); } static void set_point_from_stack(T* p, lua_State* L, size_t i, int s) { @@ -802,7 +907,7 @@ struct LunaCubicSplineN : Luna lua_pushnumber(L, p->size() - 1 + p->get_loop()); return 1; } - static int resize(T* p, lua_State* L) + static int set_size(T* p, lua_State* L) { int siz= IArg(1); if(siz < 0) @@ -812,12 +917,12 @@ struct LunaCubicSplineN : Luna p->resize(static_cast(siz)); COMMON_RETURN_SELF; } - static int size(T* p, lua_State* L) + static int get_size(T* p, lua_State* L) { lua_pushnumber(L, p->size()); return 1; } - static int redimension(T* p, lua_State* L) + static int set_dimension(T* p, lua_State* L) { if(p->m_owned_by_actor) { @@ -832,7 +937,7 @@ struct LunaCubicSplineN : Luna p->redimension(static_cast(dim)); COMMON_RETURN_SELF; } - static int dimension(T* p, lua_State* L) + static int get_dimension(T* p, lua_State* L) { lua_pushnumber(L, p->dimension()); return 1; @@ -880,10 +985,10 @@ struct LunaCubicSplineN : Luna ADD_METHOD(set_spatial_extent); ADD_METHOD(get_spatial_extent); ADD_METHOD(get_max_t); - ADD_METHOD(resize); - ADD_METHOD(size); - ADD_METHOD(redimension); - ADD_METHOD(dimension); + ADD_METHOD(set_size); + ADD_METHOD(get_size); + ADD_METHOD(set_dimension); + ADD_METHOD(get_dimension); ADD_METHOD(empty); ADD_METHOD(set_loop); ADD_METHOD(get_loop); diff --git a/src/CubicSpline.h b/src/CubicSpline.h index 3cfdaec58d..f19c2d8855 100644 --- a/src/CubicSpline.h +++ b/src/CubicSpline.h @@ -18,7 +18,9 @@ CubicSpline() :m_spatial_extent(0.0f) {} float evaluate_third_derivative(float t, bool loop) const; void set_point(size_t i, float v); void set_coefficients(size_t i, float b, float c, float d); - void get_coefficients(size_t i, float& b, float& c, float& d); + void get_coefficients(size_t i, float& b, float& c, float& d) const; + void set_point_and_coefficients(size_t i, float a, float b, float c, float d); + void get_point_and_coefficients(size_t i, float& a, float& b, float& c, float& d) const; void resize(size_t s); size_t size() const; bool empty() const; @@ -40,6 +42,8 @@ struct CubicSplineN CubicSplineN() :m_owned_by_actor(false), m_loop(false), m_polygonal(false), m_dirty(true) {} + static void weighted_average(CubicSplineN& out, CubicSplineN const& from, + CubicSplineN const& to, float between); void solve(); void evaluate(float t, vector& v) const; void evaluate_derivative(float t, vector& v) const; @@ -59,11 +63,11 @@ struct CubicSplineN bool empty() const; typedef vector spline_cont_t; void set_loop(bool l); - bool get_loop(); + bool get_loop() const; void set_polygonal(bool p); - bool get_polygonal(); + bool get_polygonal() const; void set_dirty(bool d); - bool get_dirty(); + bool get_dirty() const; bool m_owned_by_actor; void PushSelf(lua_State* L); diff --git a/src/NoteDisplay.cpp b/src/NoteDisplay.cpp index a6d0ef889f..ae4ef898e3 100644 --- a/src/NoteDisplay.cpp +++ b/src/NoteDisplay.cpp @@ -325,6 +325,25 @@ void NCSplineHandler::EvalForReceptor(float song_beat, vector& ret) const m_spline.evaluate(t_value, ret); } +void NCSplineHandler::MakeWeightedAverage(NCSplineHandler& out, + NCSplineHandler const& from, NCSplineHandler const& to, float between) +{ +#define BOOLS_FROM_CLOSEST(closest) \ + out.m_spline_mode= closest.m_spline_mode; \ + out.m_subtract_song_beat_from_curr= closest.m_subtract_song_beat_from_curr; + if(between >= 0.5f) + { + BOOLS_FROM_CLOSEST(to); + } + else + { + BOOLS_FROM_CLOSEST(from); + } +#undef BOOLS_FROM_CLOSEST + CubicSplineN::weighted_average(out.m_spline, from.m_spline, to.m_spline, + between); +} + void NoteColumnRenderArgs::spae_pos_for_beat(PlayerState const* state, float beat, float y_offset, float y_reverse_offset, vector& sp_pos, vector& ae_pos) const @@ -1346,7 +1365,7 @@ void NoteColumnRenderer::UpdateReceptorGhostStuff(Actor* receptor) const vector ae_pos(3, 0.0f); vector ae_rot(3, 0.0f); vector ae_zoom(3, 0.0f); - switch(m_pos_handler.m_spline_mode) + switch(NCR_current.m_pos_handler.m_spline_mode) { case NCSM_Disabled: ArrowEffects::GetXYZPos(player_state, m_column, 0, m_field_render_args->reverse_offset_pixels, ae_pos); @@ -1358,13 +1377,13 @@ void NoteColumnRenderer::UpdateReceptorGhostStuff(Actor* receptor) const break; case NCSM_Offset: ArrowEffects::GetXYZPos(player_state, m_column, 0, m_field_render_args->reverse_offset_pixels, ae_pos); - m_pos_handler.EvalForReceptor(song_beat, sp_pos); + NCR_current.m_pos_handler.EvalForReceptor(song_beat, sp_pos); break; case NCSM_Position: - m_pos_handler.EvalForReceptor(song_beat, sp_pos); + NCR_current.m_pos_handler.EvalForReceptor(song_beat, sp_pos); break; } - switch(m_rot_handler.m_spline_mode) + switch(NCR_current.m_rot_handler.m_spline_mode) { case NCSM_Disabled: ae_rot[2]= ArrowEffects::ReceptorGetRotationZ(player_state); @@ -1373,13 +1392,13 @@ void NoteColumnRenderer::UpdateReceptorGhostStuff(Actor* receptor) const break; case NCSM_Offset: ae_rot[2]= ArrowEffects::ReceptorGetRotationZ(player_state); - m_rot_handler.EvalForReceptor(song_beat, sp_rot); + NCR_current.m_rot_handler.EvalForReceptor(song_beat, sp_rot); break; case NCSM_Position: - m_rot_handler.EvalForReceptor(song_beat, sp_rot); + NCR_current.m_rot_handler.EvalForReceptor(song_beat, sp_rot); break; } - switch(m_zoom_handler.m_spline_mode) + switch(NCR_current.m_zoom_handler.m_spline_mode) { case NCSM_Disabled: ae_zoom[0]= ae_zoom[1]= ae_zoom[2]= ArrowEffects::GetZoom(player_state); @@ -1388,10 +1407,10 @@ void NoteColumnRenderer::UpdateReceptorGhostStuff(Actor* receptor) const break; case NCSM_Offset: ae_zoom[0]= ae_zoom[1]= ae_zoom[2]= ArrowEffects::GetZoom(player_state); - m_zoom_handler.EvalForReceptor(song_beat, sp_zoom); + NCR_current.m_zoom_handler.EvalForReceptor(song_beat, sp_zoom); break; case NCSM_Position: - m_zoom_handler.EvalForReceptor(song_beat, sp_zoom); + NCR_current.m_zoom_handler.EvalForReceptor(song_beat, sp_zoom); break; } m_column_render_args.SetPRZForActor(receptor, sp_pos, ae_pos, sp_rot, ae_rot, sp_zoom, ae_zoom); @@ -1400,9 +1419,9 @@ void NoteColumnRenderer::UpdateReceptorGhostStuff(Actor* receptor) const void NoteColumnRenderer::DrawPrimitives() { m_column_render_args.song_beat= m_field_render_args->player_state->GetDisplayedPosition().m_fSongBeatVisible; - m_column_render_args.pos_handler= &m_pos_handler; - m_column_render_args.rot_handler= &m_rot_handler; - m_column_render_args.zoom_handler= &m_zoom_handler; + m_column_render_args.pos_handler= &NCR_current.m_pos_handler; + m_column_render_args.rot_handler= &NCR_current.m_rot_handler; + m_column_render_args.zoom_handler= &NCR_current.m_zoom_handler; m_field_render_args->first_beat= NoteRowToBeat(m_field_render_args->first_row); m_field_render_args->last_beat= NoteRowToBeat(m_field_render_args->last_row); bool any_upcoming= false; @@ -1461,6 +1480,64 @@ void NoteColumnRenderer::DrawPrimitives() m_field_render_args->receptor_row->SetNoteUpcoming(m_column, any_upcoming); } +void NoteColumnRenderer::SetCurrentTweenStart() +{ + NCR_start= NCR_current; +} + +void NoteColumnRenderer::EraseHeadTween() +{ + NCR_current= NCR_Tweens[0]; + NCR_Tweens.erase(NCR_Tweens.begin()); +} + +void NoteColumnRenderer::UpdatePercentThroughTween(float between) +{ + NCR_TweenState::MakeWeightedAverage(NCR_current, NCR_start, NCR_Tweens[0], + between); +} + +void NoteColumnRenderer::BeginTweening(float time, ITween* interp) +{ + Actor::BeginTweening(time, interp); + if(!NCR_Tweens.empty()) + { + NCR_Tweens.push_back(NCR_Tweens.back()); + } + else + { + NCR_Tweens.push_back(NCR_current); + } +} + +void NoteColumnRenderer::StopTweening() +{ + NCR_Tweens.clear(); + Actor::StopTweening(); +} + +void NoteColumnRenderer::FinishTweening() +{ + if(!NCR_Tweens.empty()) + { + NCR_current= NCR_DestTweenState(); + } + Actor::FinishTweening(); +} + +void NoteColumnRenderer::NCR_TweenState::MakeWeightedAverage( + NCR_TweenState& out, NCR_TweenState const& from, NCR_TweenState const& to, + float between) +{ +#define WEIGHT_FOR_ME(me) \ + NCSplineHandler::MakeWeightedAverage(out.me, from.me, to.me, between); + WEIGHT_FOR_ME(m_pos_handler); + WEIGHT_FOR_ME(m_rot_handler); + WEIGHT_FOR_ME(m_zoom_handler); +#undef WEIGHT_FOR_ME +} + + #include "LuaBinding.h" struct LunaNCSplineHandler : Luna @@ -1518,12 +1595,12 @@ struct LunaNoteColumnRenderer : Luna #define GET_HANDLER(member, name) \ static int name(T* p, lua_State* L) \ { \ - p->member.PushSelf(L); \ + p->member->PushSelf(L); \ return 1; \ } - GET_HANDLER(m_pos_handler, get_pos_handler); - GET_HANDLER(m_rot_handler, get_rot_handler); - GET_HANDLER(m_zoom_handler, get_zoom_handler); + GET_HANDLER(GetPosHandler(), get_pos_handler); + GET_HANDLER(GetRotHandler(), get_rot_handler); + GET_HANDLER(GetZoomHandler(), get_zoom_handler); #undef GET_HANDLER LunaNoteColumnRenderer() diff --git a/src/NoteDisplay.h b/src/NoteDisplay.h index c92e2a37c7..b9d5ff5827 100644 --- a/src/NoteDisplay.h +++ b/src/NoteDisplay.h @@ -136,6 +136,8 @@ struct NCSplineHandler void EvalForBeat(float song_beat, float note_beat, vector& ret) const; void EvalDerivForBeat(float song_beat, float note_beat, vector& ret) const; void EvalForReceptor(float song_beat, vector& ret) const; + static void MakeWeightedAverage(NCSplineHandler& out, + NCSplineHandler const& from, NCSplineHandler const& to, float between); CubicSplineN m_spline; NoteColumnSplineMode m_spline_mode; @@ -263,12 +265,6 @@ struct NoteColumnRenderer : public Actor NoteFieldRenderArgs* m_field_render_args; NoteColumnRenderArgs m_column_render_args; int m_column; - // m_column_render_args has pointers to these fields that are updated - // every frame instead of using m_column_render_args directly so that they - // can be moved into a tween state later. -Kyz - NCSplineHandler m_pos_handler; - NCSplineHandler m_rot_handler; - NCSplineHandler m_zoom_handler; // UpdateReceptorGhostStuff takes care of the logic for making the ghost // and receptor positions follow the splines. It's called by their row @@ -276,6 +272,42 @@ struct NoteColumnRenderer : public Actor void UpdateReceptorGhostStuff(Actor* receptor) const; virtual void DrawPrimitives(); virtual void PushSelf(lua_State* L); + + struct NCR_TweenState + { + NCSplineHandler m_pos_handler; + NCSplineHandler m_rot_handler; + NCSplineHandler m_zoom_handler; + static void MakeWeightedAverage(NCR_TweenState& out, + NCR_TweenState const& from, NCR_TweenState const& to, float between); + bool operator==(NCR_TweenState const& other) const; + bool operator!=(NCR_TweenState const& other) const { return !operator==(other); } + }; + + NCR_TweenState& NCR_DestTweenState() + { + if(NCR_Tweens.empty()) + { return NCR_current; } + else + { return NCR_Tweens.back(); } + } + NCR_TweenState const& NCR_DestTweenState() const { return const_cast(this)->NCR_DestTweenState(); } + + virtual void SetCurrentTweenStart(); + virtual void EraseHeadTween(); + virtual void UpdatePercentThroughTween(float between); + virtual void BeginTweening(float time, ITween* interp); + virtual void StopTweening(); + virtual void FinishTweening(); + + NCSplineHandler* GetPosHandler() { return &NCR_DestTweenState().m_pos_handler; } + NCSplineHandler* GetRotHandler() { return &NCR_DestTweenState().m_rot_handler; } + NCSplineHandler* GetZoomHandler() { return &NCR_DestTweenState().m_zoom_handler; } + + private: + vector NCR_Tweens; + NCR_TweenState NCR_current; + NCR_TweenState NCR_start; }; #endif diff --git a/src/RageUtil.h b/src/RageUtil.h index 058727b85f..8cd681a048 100644 --- a/src/RageUtil.h +++ b/src/RageUtil.h @@ -61,6 +61,12 @@ inline bool CLAMP( unsigned &x, unsigned l, unsigned h ) else if (x < l) { x = l; return true; } return false; } +inline bool CLAMP( size_t &x, size_t l, size_t h ) +{ + if (x > h) { x = h; return true; } + else if (x < l) { x = l; return true; } + return false; +} inline bool CLAMP( float &x, float l, float h ) { if (x > h) { x = h; return true; } From d3bbf9cbd3c127e9e7df8f9aae6b2890bd9d187d Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Tue, 6 Jan 2015 20:14:59 -0700 Subject: [PATCH 12/14] Added SetVertsFromSplines to AMV. Moved const to the left side. Documentation for all new functions. --- Docs/Changelog_sm5.txt | 14 +- Docs/Luadoc/Lua.xml | 64 +++++++-- Docs/Luadoc/LuaDocumentation.xml | 189 +++++++++++++++++++++++-- Themes/_fallback/Scripts/01 alias.lua | 1 + src/ActorMultiVertex.cpp | 193 +++++++++++--------------- src/ActorMultiVertex.h | 21 +-- src/CubicSpline.cpp | 42 +++--- src/CubicSpline.h | 14 +- src/NoteDisplay.cpp | 69 ++++----- src/NoteDisplay.h | 63 ++++----- src/NoteField.cpp | 32 ++--- 11 files changed, 454 insertions(+), 248 deletions(-) diff --git a/Docs/Changelog_sm5.txt b/Docs/Changelog_sm5.txt index 33512ebde7..eb21d1eb8a 100644 --- a/Docs/Changelog_sm5.txt +++ b/Docs/Changelog_sm5.txt @@ -4,10 +4,22 @@ The StepMania 5 Changelog covers all post-sm-ssc changes. For a list of changes from StepMania 4 alpha 5 to sm-ssc v1.2.5, see Changelog_sm-ssc.txt. ________________________________________________________________________________ +2015/01/06 +---------- +* [ActorMultiVertex] GetSpline and SetVertsFromSplines functions added. + [kyzentun] +* [NoteColumnRenderer] Functions for fetching the spline handlers for the + column added. [kyzentun] +* [NCSplineHandler] New class for setting various parameters of a spline used + by a NoteColumnRenderer and containing the spline. [kyzentun] +* [CubicSplineN] New class that provides spline functionality. [kyzentun] + 2014/12/26 ---------- * [NoteField] Columns turned into actors that can be fetched with - GetColumnActors. [kyzentun] + get_column_actors. [kyzentun] + Tiny boost in fps when there is a large number of notes on screen. +* [NoteColumnRenderer] New class for controlling one column. [kyzentun] 2014/12/20 ---------- diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index 675690c28a..f8438e7d55 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -496,6 +496,7 @@ + @@ -506,6 +507,7 @@ +
@@ -678,6 +680,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -967,16 +994,32 @@ + + + + + + + + + + + + + + + + - - + + - - - - - - + + + + + + @@ -2215,6 +2258,11 @@ + + + + + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index 88266af252..c1b5f6698a 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -112,6 +112,9 @@ save yourself some time, copy this for undocumented things: Tries to connect to the server at sAddress. + + Creates a CubicSplineN for you to use. Make sure you destroy the CubicSplineN when you're done with it, or you will have a memory leak. + [02 Colors.lua] @@ -1521,6 +1524,22 @@ save yourself some time, copy this for undocumented things: Sets multiple vertices at once. The elements of vertices should themselves be tables, of the form provided to SetVertex. If vertices is the first argument it will start from vertex 1. If an integer is provided before vertices it will start from that vertex. It will add vertices as necessary. Example: self:SetVertices( { { { x1, y1, z1 } , { r1,g1,b1,a1 } , { tcx1,tcy1 } }; { { x2, y2, z2 } , { r2,g2,b2,a2 } , { tcx2,tcy2 } } } ) + + Sets all the drawn verts of the ActorMultiVertex by evaluating the splines.
+ ("all the drawn verts" means all the verts between FirstToDraw and NumToDraw, the verts that are set to draw in the current tween state.)
+ The parts of the ActorMultiVertex are evenly spaced along the spline in terms of t value.
+ The exact behavior depends on the draw mode.
+ DrawMode_Quads uses all 4 splines, one for each corner.
+ DrawMode_QuadStrip and DrawMode_Strip use 2 splines, one for each edge of the strip.
+ DrawMode_Fan uses one spline, for the edge verts of the fan. The first vert is not touched because it is the center.
+ DrawMode_Triangles uses 3 splines, one for each corner.
+ DrawMode_SymmetricQuadStrip uses 3 splines, one on each edge and one in the center.
+ DrawMode_LineStrip uses 1 spline.
+
+ + Returns the requested spline. Spline indices range from 1 to 4.
+ ActorMultiVertex splines are not inside the tween state, and will not change the verts until you call SetVertsFromSplines. +
Sets the number of vertices. @@ -2062,6 +2081,92 @@ save yourself some time, copy this for undocumented things: Returns the SHA-1 hash for s.
+ + + All functions in this class have camel case equivalents, use whichever naming style you prefer.
+ This spline implementation is a cubic spline.
+ A spline is a line calculated from a small set of points with mathematical smooting applied.
+ Splines can have any number of dimensions, but splines owned by actors (the ones inside NCSplineHandler and ActorMultiVertex) always have 3 dimensions.
+
+ + Solves the spline, setting the coefficients. + + + Evaluates the spline at the given t value, returning a table of the results for each dimension of the spline.
+ t can range from 0 to the value returned by get_max_t().
+ A normal spline will return its starting point for any t value less than 0 and its end point for any t value greater than the max.
+ A looped spline adjust the t value to be within the its range by adding or subtracting the max t as needed. (so if the max t is 4 and you evaluate at 5, it will return the same as if you evaluated at 1.) +
+ + Evaluates the derivative at t. + + + Evaluates the second derivative at t. + + + Evaluates the third derivative at t.
+ Second and third derivative functions exist because they're possible, not because they're expected to be useful. The fourth derivative would be 0 because the equation for evaluating the spline is "a + (b*t) + (c*t^2) + (d*t^3)". +
+ + Sets point i of the spline to the position specified by the table p. + + + Sets the coefficients of the spline at point i.
+ Each table must contain a value for each dimension of the spline.
+ Solving the spline normally should cover all normal usage, this is for people that want a spline with an abnormal behavior, so if you set the coefficients directly, expect to end up with an unsmooth shape. +
+ + Returns a table containing the tables of coefficients for the point i. + + + Sets the spatial extent of dimension d of the spline to e.
+ The spatial extent exists to handle numbers that exist in a finite looped space, instead of the flat infinite space.
+ To put it more concretely, spatial extent exists to allow a spline to control rotation with wrapping behavior at 0.0 and 2pi, instead of suddenly jerking from 2pi to 0.0. +
+ + Returns the spatial extent of dimension d of the spline. + + + Returns the max t value the spline extends to. For a normal spline, this will be size()-1. For a looped spline, this will be size(). + + + Sets the number of points in the spline. You must set the number of points before trying to set the position of any point. + + + Returns the number of points in the spline. + + + Sets the number of dimensions the spline has.
+ Splines that are owned by actors (the ones inside ActorMultiVertex and NCSplineHandler) cannot have their number of dimensions changed because the actors require them to have 3 dimensions. +
+ + Returns the number of dimensions the spline has. + + + Returns true of the spline has zero points, or false if it has more than zero points. + + + Sets whether the spline is looped. A looped spline is one where the end point is connected to the start point. + + + Returns whether the spline is looped. + + + Sets whether the spline is polygonal. If the spline is polygonal, then it will have straight lines between the points instead of curves. + + + Returns whether the spline is polygonal. + + + Sets whether the spline is dirty. A dirty spline is one that has been changed in some way that affects its shape. When solve() is called, the spline will only be solved if it is dirty. The dirty flag is automatically set by everything, so you should never have to call this function. + + + Returns whether the spline is currently dirty. + + + Destroys the spline, freeing the memory allocated for it. This can only be called on splines created with create_spline(). + +
Sets the DifficultyIcon's state from the difficulty passed in. @@ -2950,44 +3055,102 @@ save yourself some time, copy this for undocumented things: Returns a table of noteskin names for the current gametype. + + + All functions in this class have camel case equivalents, use whichever naming style you prefer.
+ The spline handler holds info on how the spline is used by the engine.
+ Each get/set pair of functions in this class is for a different aspect of the spline's behavior. +
+ + Returns the spline for this handler. + + + Returns the beats per t value of the spline. If the beats_per_t is 4, then a note must be on screen for 4 beats to traverse from one point on the spline to the next. + + + Sets the beats per t value for the spline. + + + Returns the t value that receptors are evaluated at. + + + the t value that receptors are evaluated at. + + + Returns the mode the spline is set to.
+ "NoteColumnSplineMode_Disabled" means the spline will not affect the notes or receptors at all.
+ "NoteColumnSplineMode_Offset" means the spline will added to the effects from the mods.
+ "NoteColumnSplineMode_Position" means only the spline affect the notes and mods will be ignored. (but only mods that affect the same aspect of the note as the spline will be disabled. So a rotation spline won't disable Mini or Tiny, but a zoom spline will, and a zoom spline won't disable Dizzy, Twirl, or Roll, but a rotation spline will.) +
+ + Sets the current spline mode for this handler. + + + Returns whether the current song beat is subtracted from a note's beat when calculating the t value to use on the spline. + + + Sets whether the current song beat is subtracted from a note's beat when calculating the t value to use on the spline. + +
+ + + All functions in this class have camel case equivalents, use whichever naming style you prefer.
+ Position, rotation, and zoom each have separate spline handlers to allow them to have separate independent behavior.
+ It is important to note that the spline handlers are inside the tween state, so whenever you start a new tween on the actor, you need to refetch the spline handlers.
+
+ + Returns the handler for the position spline. + + + Returns the handler for the rotation spline.
+ The rotation applied by the rotation spline is in radians.
+ For convenience, the spatial extent of the rotation spline defaults to 2pi. +
+ + Returns the handler for the zoom spline. + +
- + + All functions in this class have camel case equivalents, use whichever naming style you prefer.
+
+ Makes the NoteField act as if a hold note was hit in the column, with the given score and bright setting.
- The callback for DidHoldNote will not be called. + The callback for did_hold_note will not be called.
- + Makes the NoteField act as if a tap note was hit in the column, with the given score and bright setting.
- The callback for DidTapNote will not be called. + The callback for did_tap_note will not be called.
- - Returns a table of the actors for the columns. + + Returns a table of the actors for the columns. This means that each column is an actor, so you can move it around or animate it like an actor. See the NoteColumnRenderer class for a list of special functions for the column's actor. - + Same as SetDidTapNoteCallback, but for hold notes. Uses HoldNoteScore instead of TapNoteScore. - + Sets the function that the NoteField will call whenever a tap note is hit.
The callback function is passed the column, the TapNoteScore, and whether the explosion will be bright.
The callback function can return changed values for the NoteField to use instead of the ones that were passed.
Pass nil instead of a function to clear the callback.
- + Makes the NoteField act as if a press occurred in the column.
- The callback for SetPressed will not be called. + The callback for set_pressed will not be called.
- + Sets the function that the NoteField will call whenever a press occurs.
The callback function is passed the column for the press.
The callback function can return changed values for the NoteField to use instead of the ones that were passed.
Pass nil instead of a function to clear the callback.
- + Sets the function that the NoteField will call whenever a step occurs.
The callback function is passed the column and the TapNoteScore for the step.
The callback function can return changed values for the NoteField to use instead of the ones that were passed.
Pass nil instead of a function to clear the callback.
- + Makes the NoteField act as if a step occurred in the column with the given score. The callback for Step will not be called. diff --git a/Themes/_fallback/Scripts/01 alias.lua b/Themes/_fallback/Scripts/01 alias.lua index 507d09dd2c..3ab5d577f9 100644 --- a/Themes/_fallback/Scripts/01 alias.lua +++ b/Themes/_fallback/Scripts/01 alias.lua @@ -33,6 +33,7 @@ function alias_one(class, main_name, alt_name) lua.ReportScriptError("Alias name of function must be a string.") return end + if class[alt_name] then return end class[alt_name]= class[main_name] end diff --git a/src/ActorMultiVertex.cpp b/src/ActorMultiVertex.cpp index e93ba2a21e..7794d069c4 100644 --- a/src/ActorMultiVertex.cpp +++ b/src/ActorMultiVertex.cpp @@ -63,9 +63,12 @@ ActorMultiVertex::ActorMultiVertex() _EffectMode = EffectMode_Normal; _TextureMode = TextureMode_Modulate; - _using_spline= false; - _spline.redimension(3); - _spline.m_owned_by_actor= true; + _splines.resize(num_vert_splines); + for(size_t i= 0; i < num_vert_splines; ++i) + { + _splines[i].redimension(3); + _splines[i].m_owned_by_actor= true; + } } ActorMultiVertex::~ActorMultiVertex() @@ -77,13 +80,12 @@ ActorMultiVertex::ActorMultiVertex( const ActorMultiVertex &cpy ): Actor( cpy ) { #define CPY(a) a = cpy.a - CPY( _spline ); - CPY( _using_spline ); CPY( AMV_Tweens ); CPY( AMV_current ); CPY( AMV_start ); CPY( _EffectMode ); CPY( _TextureMode ); + CPY( _splines ); #undef CPY if( cpy._Texture != NULL ) @@ -200,71 +202,6 @@ void ActorMultiVertex::SetVertexCoords( int index, float TexCoordX, float TexCoo AMV_DestTweenState().vertices[index].t = RageVector2( TexCoordX, TexCoordY ); } -bool ActorMultiVertex::GetUseSpline() -{ - return _using_spline; -} - -void ActorMultiVertex::SetUseSpline(bool use) -{ - _using_spline= use; -} - -void ActorMultiVertex::SplineSetPoint(size_t i, float x , float y , float z) -{ - vector v(3); - v[0]= x; v[1]= y; v[2]= z; - _spline.set_point(i, v); -} - -void ActorMultiVertex::SplineResize(size_t s) -{ - _spline.resize(s); -} - -size_t ActorMultiVertex::SplineSize() -{ - return _spline.size(); -} - -void ActorMultiVertex::SplineSolve() -{ - if(_spline.empty()) - { - return; - } - _spline.solve(); - float num_parts= 0.0f; - size_t num_verts= GetNumVertices(); - switch(GetDestDrawMode()) - { - case DrawMode_Quads: - break; - case DrawMode_QuadStrip: - break; - case DrawMode_Fan: - break; - case DrawMode_Strip: - break; - case DrawMode_Triangles: - break; - case DrawMode_LineStrip: - { - float conversion= static_cast(_spline.size()-1) / static_cast(num_verts-1); - for(size_t i= 0; i < num_verts; ++i) - { - float t= i * conversion; - vector p; - _spline.evaluate(t, p); - SetVertexPos(i, p[0], p[1], p[2]); - } - } - break; - case DrawMode_SymmetricQuadStrip: - break; - } -} - void ActorMultiVertex::DrawPrimitives() { Actor::SetGlobalRenderStates(); // set Actor-specified render states @@ -380,6 +317,61 @@ bool ActorMultiVertex::EarlyAbortDraw() const return false; } +void ActorMultiVertex::SetVertsFromSplinesInternal(size_t num_splines, size_t offset) +{ + vector& verts= AMV_DestTweenState().vertices; + size_t first= AMV_DestTweenState().FirstToDraw + offset; + size_t num_verts= AMV_DestTweenState().GetSafeNumToDraw(AMV_DestTweenState()._DrawMode, AMV_DestTweenState().NumToDraw); + float tper[num_splines]= {0.0f}; + float num_parts= static_cast(num_verts - offset) / + static_cast(num_splines); + for(size_t i= 0; i < num_splines; ++i) + { + tper[i]= _splines[i].get_max_t() / num_parts; + } + for(size_t v= 0; v < num_verts; ++v) + { + vector pos; + const int spi= v%num_splines; + _splines[spi].evaluate(static_cast(v) * tper[spi], pos); + verts[v+first].p.x= pos[0]; + verts[v+first].p.y= pos[1]; + verts[v+first].p.z= pos[2]; + } +} + +void ActorMultiVertex::SetVertsFromSplines() +{ + if(AMV_DestTweenState().vertices.empty()) { return; } + switch(AMV_DestTweenState()._DrawMode) + { + case DrawMode_Quads: + SetVertsFromSplinesInternal(4, 0); + break; + case DrawMode_QuadStrip: + case DrawMode_Strip: + SetVertsFromSplinesInternal(2, 0); + break; + case DrawMode_Fan: + // Skip the first vert because it is the center of the fan. -Kyz + SetVertsFromSplinesInternal(1, 1); + break; + case DrawMode_Triangles: + case DrawMode_SymmetricQuadStrip: + SetVertsFromSplinesInternal(3, 0); + break; + case DrawMode_LineStrip: + SetVertsFromSplinesInternal(1, 0); + break; + } +} + +CubicSplineN* ActorMultiVertex::GetSpline(size_t i) +{ + ASSERT(i < num_vert_splines); + return &(_splines[i]); +} + void ActorMultiVertex::SetCurrentTweenStart() { AMV_start= AMV_current; @@ -709,6 +701,23 @@ public: COMMON_RETURN_SELF; } + static int GetSpline(T* p, lua_State* L) + { + size_t i= static_cast(IArg(1)-1); + if(i >= ActorMultiVertex::num_vert_splines) + { + luaL_error(L, "Spline index must be greater than 0 and less than or equal to %zu.", ActorMultiVertex::num_vert_splines); + } + p->GetSpline(i)->PushSelf(L); + return 1; + } + + static int SetVertsFromSplines(T* p, lua_State* L) + { + p->SetVertsFromSplines(); + COMMON_RETURN_SELF; + } + static int SetTexture( T* p, lua_State *L ) { RageTexture *Texture = Luna::check(L, 1); @@ -729,42 +738,6 @@ public: } return 1; } - DEFINE_METHOD(GetUseSpline, GetUseSpline()); - static int SplineSize(T* p, lua_State* L) - { - lua_pushnumber(L, p->SplineSize()); - return 1; - } - static int SetUseSpline(T* p, lua_State* L) - { - p->SetUseSpline(lua_toboolean(L, 1)); - COMMON_RETURN_SELF; - } - static int SplineSetPoint(T* p, lua_State* L) - { - size_t i= IArg(1)-1; - if(i >= p->SplineSize()) - { - luaL_error(L, "Spline point index greater than the number of points."); - } - p->SplineSetPoint(i, FArg(2), FArg(3), FArg(4)); - COMMON_RETURN_SELF; - } - static int SplineResize(T* p, lua_State* L) - { - int s= IArg(1); - if(s < 0) - { - luaL_error(L, "Negative spline size not allowed."); - } - p->SplineResize(static_cast(s)); - COMMON_RETURN_SELF; - } - static int SplineSolve(T* p, lua_State* L) - { - p->SplineSolve(); - COMMON_RETURN_SELF; - } LunaActorMultiVertex() { @@ -786,12 +759,8 @@ public: ADD_METHOD( GetCurrFirstToDraw ); ADD_METHOD( GetCurrNumToDraw ); - ADD_METHOD(GetUseSpline); - ADD_METHOD(SetUseSpline); - ADD_METHOD(SplineSize); - ADD_METHOD(SplineResize); - ADD_METHOD(SplineSetPoint); - ADD_METHOD(SplineSolve); + ADD_METHOD( GetSpline ); + ADD_METHOD( SetVertsFromSplines ); // Copy from RageTexture ADD_METHOD( SetTexture ); diff --git a/src/ActorMultiVertex.h b/src/ActorMultiVertex.h index 3742bee269..5b598cb310 100644 --- a/src/ActorMultiVertex.h +++ b/src/ActorMultiVertex.h @@ -28,6 +28,7 @@ class RageTexture; class ActorMultiVertex: public Actor { public: + static const size_t num_vert_splines= 4; ActorMultiVertex(); ActorMultiVertex( const ActorMultiVertex &cpy ); virtual ~ActorMultiVertex(); @@ -37,7 +38,9 @@ public: struct AMV_TweenState { - AMV_TweenState(): _DrawMode(DrawMode_Invalid), FirstToDraw(0), NumToDraw(-1), line_width(1.0f) {} + AMV_TweenState(): _DrawMode(DrawMode_Invalid), FirstToDraw(0), + NumToDraw(-1), line_width(1.0f) + {} static void MakeWeightedAverage(AMV_TweenState& average_out, const AMV_TweenState& ts1, const AMV_TweenState& ts2, float percent_between); bool operator==(const AMV_TweenState& other) const; bool operator!=(const AMV_TweenState& other) const { return !operator==(other); } @@ -104,21 +107,15 @@ public: void SetVertexColor( int index , RageColor c ); void SetVertexCoords( int index , float TexCoordX , float TexCoordY ); - bool GetUseSpline(); - void SetUseSpline(bool use); - void SplineSetPoint(size_t i, float x , float y , float z); - void SplineResize(size_t s); - size_t SplineSize(); - void SplineSolve(); + inline void SetVertsFromSplinesInternal(size_t num_splines, size_t start_vert); + void SetVertsFromSplines(); + CubicSplineN* GetSpline(size_t i); virtual void PushSelf( lua_State *L ); private: RageTexture* _Texture; - CubicSplineN _spline; - bool _using_spline; - vector _Vertices; vector AMV_Tweens; AMV_TweenState AMV_current; @@ -129,6 +126,10 @@ private: EffectMode _EffectMode; TextureMode _TextureMode; + + // Four splines for controlling vert positions, because quads drawmode + // requires four. -Kyz + vector _splines; }; /** diff --git a/src/CubicSpline.cpp b/src/CubicSpline.cpp index 5a20f34e4d..142f305aa0 100644 --- a/src/CubicSpline.cpp +++ b/src/CubicSpline.cpp @@ -35,7 +35,7 @@ private: list looped_diagonals; }; -size_t const solution_cache_limit= 16; +const size_t solution_cache_limit= 16; bool SplineSolutionCache::find_in_cache(list& cache, vector& outd, vector& outm) { @@ -111,7 +111,7 @@ void SplineSolutionCache::solve_diagonals_straight(vector& diagonals, vec for(size_t i= 1; i < last-1; ++i) { // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci]. - float const diag_recip= 1.0f / diagonals[i]; + const float diag_recip= 1.0f / diagonals[i]; diagonals[i+1]-= diag_recip; multiples.push_back(diag_recip); } @@ -173,7 +173,7 @@ void SplineSolutionCache::solve_diagonals_looped(vector& diagonals, vecto for(size_t i= 0; i < last-2; ++i) { // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci]. - float const diag_recip= 1.0f / diagonals[i]; + const float diag_recip= 1.0f / diagonals[i]; diagonals[i+1]-= diag_recip; right_column[i+1]-= right_column[i] * diag_recip; multiples.push_back(diag_recip); @@ -181,7 +181,7 @@ void SplineSolutionCache::solve_diagonals_looped(vector& diagonals, vecto // Last step of stage one needs special handling for right_column. // Operation: Add row[l-2] / [rl-2][cl-2] to row[l-1] to zero [rl-1][cl-2]. { - float const diag_recip= 1.0f / diagonals[last-2]; + const float diag_recip= 1.0f / diagonals[last-2]; diagonals[last-1]-= right_column[last-2] * diag_recip; multiples.push_back(diag_recip); } @@ -189,19 +189,19 @@ void SplineSolutionCache::solve_diagonals_looped(vector& diagonals, vecto for(size_t i= last-2; i > 0; --i) { // Operation: Add row[i] / -[ri][ci] to row[i-1] to zero [ri-1][ci]. - float const diag_recip= 1.0f / diagonals[i]; + const float diag_recip= 1.0f / diagonals[i]; right_column[i-1]-= right_column[i] * diag_recip; multiples.push_back(diag_recip); } // Last step of stage two. { // Operation: Add row[0] / [r0][c0] to row[l-1] to zero [rl-1][c0]. - float const diag_recip= 1.0f / diagonals[0]; + const float diag_recip= 1.0f / diagonals[0]; right_column[0]-= right_column[1] * diag_recip; multiples.push_back(diag_recip); } // Stage three. - size_t const end= last-1; + const size_t end= last-1; for(size_t i= 0; i < end; ++i) { // Operation: Add row[e] * (right_column[i] / [re][ce]) to row[i] to @@ -223,13 +223,13 @@ SplineSolutionCache solution_cache; float loop_space_difference(float a, float b, float spatial_extent); float loop_space_difference(float a, float b, float spatial_extent) { - float const norm_diff= a - b; + const float norm_diff= a - b; if(spatial_extent == 0.0f) { return norm_diff; } - float const plus_diff= a - (b + spatial_extent); - float const minus_diff= a - (b - spatial_extent); - float const abs_norm_diff= abs(norm_diff); - float const abs_plus_diff= abs(plus_diff); - float const abs_minus_diff= abs(minus_diff); + const float plus_diff= a - (b + spatial_extent); + const float minus_diff= a - (b - spatial_extent); + const float abs_norm_diff= abs(norm_diff); + const float abs_plus_diff= abs(plus_diff); + const float abs_minus_diff= abs(minus_diff); if(abs_norm_diff < abs_plus_diff) { if(abs_norm_diff < abs_minus_diff) @@ -287,7 +287,7 @@ void CubicSpline::solve_looped() results[last-1]-= results[0] * multiples[next_mult]; ++next_mult; // Stage three. - size_t const end= last-1; + const size_t end= last-1; for(size_t i= 0; i < end; ++i) { // Operation: Add row[e] * -multiples[nm] to row[i]. @@ -532,7 +532,7 @@ bool CubicSpline::empty() const } void CubicSplineN::weighted_average(CubicSplineN& out, - CubicSplineN const& from, CubicSplineN const& to, float between) + const CubicSplineN& from, CubicSplineN const& to, float between) { ASSERT_M(out.dimension() == from.dimension() && to.dimension() == from.dimension(), @@ -552,8 +552,8 @@ void CubicSplineN::weighted_average(CubicSplineN& out, // Behavior for splines of different sizes: Use a size between the two. // Points that exist in both will be averaged. // Points that only exist in one will come only from that one. - size_t const from_size= from.size(); - size_t const to_size= to.size(); + const size_t from_size= from.size(); + const size_t to_size= to.size(); size_t out_size= to_size; size_t limit= to_size; if(from_size < to_size) @@ -666,7 +666,7 @@ CSN_EVAL_SOMETHING(evaluate_third_derivative); #undef CSN_EVAL_SOMETHING -void CubicSplineN::set_point(size_t i, vector const& v) +void CubicSplineN::set_point(size_t i, const vector& v) { ASSERT_M(v.size() == m_splines.size(), "CubicSplineN::set_point requires the passed point to be the same dimension as the spline."); for(size_t n= 0; n < m_splines.size(); ++n) @@ -676,8 +676,8 @@ void CubicSplineN::set_point(size_t i, vector const& v) m_dirty= true; } -void CubicSplineN::set_coefficients(size_t i, vector const& b, - vector const& c, vector const& d) +void CubicSplineN::set_coefficients(size_t i, const vector& b, + const vector& c, const vector& d) { ASSERT_M(b.size() == c.size() && c.size() == d.size() && d.size() == m_splines.size(), "CubicSplineN: coefficient vectors must be " @@ -904,7 +904,7 @@ struct LunaCubicSplineN : Luna } static int get_max_t(T* p, lua_State* L) { - lua_pushnumber(L, p->size() - 1 + p->get_loop()); + lua_pushnumber(L, p->get_max_t()); return 1; } static int set_size(T* p, lua_State* L) diff --git a/src/CubicSpline.h b/src/CubicSpline.h index f19c2d8855..aae6e702e9 100644 --- a/src/CubicSpline.h +++ b/src/CubicSpline.h @@ -42,16 +42,16 @@ struct CubicSplineN CubicSplineN() :m_owned_by_actor(false), m_loop(false), m_polygonal(false), m_dirty(true) {} - static void weighted_average(CubicSplineN& out, CubicSplineN const& from, - CubicSplineN const& to, float between); + static void weighted_average(CubicSplineN& out, const CubicSplineN& from, + const CubicSplineN& to, float between); void solve(); void evaluate(float t, vector& v) const; void evaluate_derivative(float t, vector& v) const; void evaluate_second_derivative(float t, vector& v) const; void evaluate_third_derivative(float t, vector& v) const; - void set_point(size_t i, vector const& v); - void set_coefficients(size_t i, vector const& b, - vector const& c, vector const& d); + void set_point(size_t i, const vector& v); + void set_coefficients(size_t i, const vector& b, + const vector& c, const vector& d); void get_coefficients(size_t i, vector& b, vector& c, vector& d); void set_spatial_extent(size_t i, float extent); @@ -61,6 +61,10 @@ struct CubicSplineN void redimension(size_t d); size_t dimension() const; bool empty() const; + float get_max_t() const { + if(m_loop) { return static_cast(size()); } + else { return static_cast(size()-1); } + } typedef vector spline_cont_t; void set_loop(bool l); bool get_loop() const; diff --git a/src/NoteDisplay.cpp b/src/NoteDisplay.cpp index ae4ef898e3..22718ad273 100644 --- a/src/NoteDisplay.cpp +++ b/src/NoteDisplay.cpp @@ -326,7 +326,7 @@ void NCSplineHandler::EvalForReceptor(float song_beat, vector& ret) const } void NCSplineHandler::MakeWeightedAverage(NCSplineHandler& out, - NCSplineHandler const& from, NCSplineHandler const& to, float between) + const NCSplineHandler& from, const NCSplineHandler& to, float between) { #define BOOLS_FROM_CLOSEST(closest) \ out.m_spline_mode= closest.m_spline_mode; \ @@ -344,7 +344,7 @@ void NCSplineHandler::MakeWeightedAverage(NCSplineHandler& out, between); } -void NoteColumnRenderArgs::spae_pos_for_beat(PlayerState const* state, +void NoteColumnRenderArgs::spae_pos_for_beat(const PlayerState* state, float beat, float y_offset, float y_reverse_offset, vector& sp_pos, vector& ae_pos) const { @@ -367,7 +367,7 @@ void NoteColumnRenderArgs::spae_pos_for_beat(PlayerState const* state, break; } } -void NoteColumnRenderArgs::spae_zoom_for_beat(PlayerState const* state, float beat, +void NoteColumnRenderArgs::spae_zoom_for_beat(const PlayerState* state, float beat, vector& sp_zoom, vector& ae_zoom) const { switch(zoom_handler->m_spline_mode) @@ -387,9 +387,9 @@ void NoteColumnRenderArgs::spae_zoom_for_beat(PlayerState const* state, float be } } void NoteColumnRenderArgs::SetPRZForActor(Actor* actor, - vector const& sp_pos, vector const& ae_pos, - vector const& sp_rot, vector const& ae_rot, - vector const& sp_zoom, vector const& ae_zoom) const + const vector& sp_pos, const vector& ae_pos, + const vector& sp_rot, const vector& ae_rot, + const vector& sp_zoom, const vector& ae_zoom) const { actor->SetX(sp_pos[0] + ae_pos[0]); actor->SetY(sp_pos[1] + ae_pos[1]); @@ -468,16 +468,16 @@ bool NoteDisplay::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTarge return true; } -bool NoteDisplay::DrawHoldsInRange(NoteFieldRenderArgs const& field_args, - NoteColumnRenderArgs const& column_args, - vector const& tap_set) +bool NoteDisplay::DrawHoldsInRange(const NoteFieldRenderArgs& field_args, + const NoteColumnRenderArgs& column_args, + const vector& 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; + const TapNote& tn= (*tapit)->second; + const HoldNoteResult& result= tn.HoldResult; int start_row= (*tapit)->first; int end_row = start_row + tn.iDuration; @@ -536,9 +536,9 @@ bool NoteDisplay::DrawHoldsInRange(NoteFieldRenderArgs const& field_args, return any_upcoming; } -bool NoteDisplay::DrawTapsInRange(NoteFieldRenderArgs const& field_args, - NoteColumnRenderArgs const& column_args, - vector const& tap_set) +bool NoteDisplay::DrawTapsInRange(const NoteFieldRenderArgs& field_args, + const NoteColumnRenderArgs& column_args, + const vector& tap_set) { bool any_upcoming= false; // draw notes from furthest to closest @@ -546,7 +546,7 @@ bool NoteDisplay::DrawTapsInRange(NoteFieldRenderArgs const& field_args, tap_set.begin(); tapit != tap_set.end(); ++tapit) { int tap_row= (*tapit)->first; - TapNote const& tn= (*tapit)->second; + const TapNote& tn= (*tapit)->second; // TRICKY: If boomerang is on, then all notes in the range // [first_row,last_row] aren't necessarily visible. @@ -736,8 +736,8 @@ struct StripBuffer }; void NoteDisplay::DrawHoldPart(vector &vpSpr, - NoteFieldRenderArgs const& field_args, - NoteColumnRenderArgs const& column_args, int fYStep, + const NoteFieldRenderArgs& field_args, + const NoteColumnRenderArgs& column_args, int fYStep, float fPercentFadeToFail, float fColorScale, bool bGlow, float fOverlappedTime, float fYTop, float fYBottom, float fYStartPos, float fYEndPos, bool bWrapping, bool bAnchorToTop, @@ -793,8 +793,8 @@ void NoteDisplay::DrawHoldPart(vector &vpSpr, const float fTexCoordCenter = (fTexCoordLeft+fTexCoordRight)/2; // pos_z_vec will be used later to orient the hold. Read below. -Kyz - static RageVector3 const pos_z_vec(0.0f, 0.0f, 1.0f); - static RageVector3 const pos_y_vec(0.0f, 1.0f, 0.0f); + static const RageVector3 pos_z_vec(0.0f, 0.0f, 1.0f); + static const RageVector3 pos_y_vec(0.0f, 1.0f, 0.0f); StripBuffer queue; for( float fY = fYStartPos; !bLast; fY += fYStep ) @@ -946,9 +946,9 @@ void NoteDisplay::DrawHoldPart(vector &vpSpr, render_left.y*= half_width; render_left.z*= half_width; - RageVector3 const left_vert(center_vert.x + render_left.x, + const RageVector3 left_vert(center_vert.x + render_left.x, center_vert.y + render_left.y, center_vert.z + render_left.z); - RageVector3 const right_vert(center_vert.x - render_left.x, + const RageVector3 right_vert(center_vert.x - render_left.x, center_vert.y - render_left.y, center_vert.z - render_left.z); const float fDistFromTop = fY - fYTop; @@ -990,8 +990,8 @@ void NoteDisplay::DrawHoldPart(vector &vpSpr, } void NoteDisplay::DrawHoldBody(const TapNote& tn, - NoteFieldRenderArgs const& field_args, - NoteColumnRenderArgs const& column_args, float fBeat, + const NoteFieldRenderArgs& field_args, + const NoteColumnRenderArgs& column_args, float fBeat, bool bIsBeingHeld, float fYHead, float fYTail, bool bIsAddition, float fPercentFadeToFail, float fColorScale, bool bGlow, float top_beat, float bottom_beat) @@ -1087,8 +1087,8 @@ void NoteDisplay::DrawHoldBody(const TapNote& tn, } void NoteDisplay::DrawHold(const TapNote& tn, - NoteFieldRenderArgs const& field_args, - NoteColumnRenderArgs const& column_args, int iRow, bool bIsBeingHeld, + const NoteFieldRenderArgs& field_args, + const NoteColumnRenderArgs& column_args, int iRow, bool bIsBeingHeld, const HoldNoteResult &Result, bool bIsAddition, float fPercentFadeToFail) { int iEndRow = iRow + tn.iDuration; @@ -1173,7 +1173,7 @@ void NoteDisplay::DrawHold(const TapNote& tn, } void NoteDisplay::DrawActor(const TapNote& tn, Actor* pActor, NotePart part, - NoteFieldRenderArgs const& field_args, NoteColumnRenderArgs const& column_args, float fYOffset, float fBeat, + const NoteFieldRenderArgs& field_args, const NoteColumnRenderArgs& column_args, float fYOffset, float fBeat, bool bIsAddition, float fPercentFadeToFail, float fColorScale, bool is_being_held) { @@ -1266,8 +1266,8 @@ void NoteDisplay::DrawActor(const TapNote& tn, Actor* pActor, NotePart part, } void NoteDisplay::DrawTap(const TapNote& tn, - NoteFieldRenderArgs const& field_args, - NoteColumnRenderArgs const& column_args, float fBeat, + const NoteFieldRenderArgs& field_args, + const NoteColumnRenderArgs& column_args, float fBeat, bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollStart, bool bIsAddition, float fPercentFadeToFail) { @@ -1351,7 +1351,7 @@ void NoteDisplay::DrawTap(const TapNote& tn, void NoteColumnRenderer::UpdateReceptorGhostStuff(Actor* receptor) const { - PlayerState const* player_state= m_field_render_args->player_state; + const PlayerState* player_state= m_field_render_args->player_state; float song_beat= player_state->GetDisplayedPosition().m_fSongBeatVisible; // sp_* will be filled with the settings from the splines. // ae_* will be filled with the settings from ArrowEffects. @@ -1436,7 +1436,7 @@ void NoteColumnRenderer::DrawPrimitives() m_field_render_args->first_row, m_field_render_args->last_row+1, begin, end); for(; begin != end; ++begin) { - TapNote const& tn= begin->second; + const TapNote& tn= begin->second; switch(tn.type) { case TapNoteType_Empty: @@ -1525,8 +1525,15 @@ void NoteColumnRenderer::FinishTweening() Actor::FinishTweening(); } +NoteColumnRenderer::NCR_TweenState::NCR_TweenState() +{ + m_rot_handler.m_spline.set_spatial_extent(0, PI*2.0f); + m_rot_handler.m_spline.set_spatial_extent(1, PI*2.0f); + m_rot_handler.m_spline.set_spatial_extent(2, PI*2.0f); +} + void NoteColumnRenderer::NCR_TweenState::MakeWeightedAverage( - NCR_TweenState& out, NCR_TweenState const& from, NCR_TweenState const& to, + NCR_TweenState& out, const NCR_TweenState& from, const NCR_TweenState& to, float between) { #define WEIGHT_FOR_ME(me) \ diff --git a/src/NoteDisplay.h b/src/NoteDisplay.h index b9d5ff5827..df34bb82db 100644 --- a/src/NoteDisplay.h +++ b/src/NoteDisplay.h @@ -100,11 +100,11 @@ enum NoteColumnSplineMode // NoteDisplay during rendering. struct NoteFieldRenderArgs { - PlayerState const* player_state; // to look up PlayerOptions + const PlayerState* player_state; // to look up PlayerOptions float reverse_offset_pixels; ReceptorArrowRow* receptor_row; GhostArrowRow* ghost_row; - NoteData const* note_data; + const NoteData* note_data; float first_beat; float last_beat; int first_row; @@ -137,7 +137,7 @@ struct NCSplineHandler void EvalDerivForBeat(float song_beat, float note_beat, vector& ret) const; void EvalForReceptor(float song_beat, vector& ret) const; static void MakeWeightedAverage(NCSplineHandler& out, - NCSplineHandler const& from, NCSplineHandler const& to, float between); + const NCSplineHandler& from, const NCSplineHandler& to, float between); CubicSplineN m_spline; NoteColumnSplineMode m_spline_mode; @@ -150,18 +150,18 @@ struct NCSplineHandler struct NoteColumnRenderArgs { - void spae_pos_for_beat(PlayerState const* state, + void spae_pos_for_beat(const PlayerState* state, float beat, float y_offset, float y_reverse_offset, vector& sp_pos, vector& ae_pos) const; - void spae_zoom_for_beat(PlayerState const* state, float beat, + void spae_zoom_for_beat(const PlayerState* state, float beat, vector& sp_zoom, vector& ae_zoom) const; void SetPRZForActor(Actor* actor, - vector const& sp_pos, vector const& ae_pos, - vector const& sp_rot, vector const& ae_rot, - vector const& sp_zoom, vector const& ae_zoom) const; - NCSplineHandler const* pos_handler; - NCSplineHandler const* rot_handler; - NCSplineHandler const* zoom_handler; + const vector& sp_pos, const vector& ae_pos, + const vector& sp_rot, const vector& ae_rot, + const vector& sp_zoom, const vector& ae_zoom) const; + const NCSplineHandler* pos_handler; + const NCSplineHandler* rot_handler; + const NCSplineHandler* zoom_handler; float song_beat; int column; }; @@ -179,12 +179,12 @@ public: bool IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const; - bool DrawHoldsInRange(NoteFieldRenderArgs const& field_args, - NoteColumnRenderArgs const& column_args, - vector const& tap_set); - bool DrawTapsInRange(NoteFieldRenderArgs const& field_args, - NoteColumnRenderArgs const& column_args, - vector const& tap_set); + bool DrawHoldsInRange(const NoteFieldRenderArgs& field_args, + const NoteColumnRenderArgs& column_args, + const vector& tap_set); + bool DrawTapsInRange(const NoteFieldRenderArgs& field_args, + const NoteColumnRenderArgs& column_args, + const vector& tap_set); /** * @brief Draw the TapNote onto the NoteField. * @param tn the TapNote in question. @@ -198,12 +198,12 @@ public: * @param fDrawDistanceAfterTargetsPixels how much to draw after the receptors. * @param fDrawDistanceBeforeTargetsPixels how much ot draw before the receptors. * @param fFadeInPercentOfDrawFar when to start fading in. */ - void DrawTap(const TapNote& tn, NoteFieldRenderArgs const& field_args, - NoteColumnRenderArgs const& column_args, float fBeat, + void DrawTap(const TapNote& tn, const NoteFieldRenderArgs& field_args, + const NoteColumnRenderArgs& column_args, float fBeat, bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollBeat, bool bIsAddition, float fPercentFadeToFail); - void DrawHold(const TapNote& tn, NoteFieldRenderArgs const& field_args, - NoteColumnRenderArgs const& column_args, int iRow, bool bIsBeingHeld, + void DrawHold(const TapNote& tn, const NoteFieldRenderArgs& field_args, + const NoteColumnRenderArgs& column_args, int iRow, bool bIsBeingHeld, const HoldNoteResult &Result, bool bIsAddition, float fPercentFadeToFail); @@ -217,18 +217,18 @@ private: Sprite *GetHoldSprite( NoteColorSprite ncs[NUM_HoldType][NUM_ActiveType], NotePart part, float fNoteBeat, bool bIsRoll, bool bIsBeingHeld ); void DrawActor(const TapNote& tn, Actor* pActor, NotePart part, - NoteFieldRenderArgs const& field_args, - NoteColumnRenderArgs const& column_args, float fYOffset, float fBeat, + const NoteFieldRenderArgs& field_args, + const NoteColumnRenderArgs& column_args, float fYOffset, float fBeat, bool bIsAddition, float fPercentFadeToFail, float fColorScale, bool is_being_held); - void DrawHoldBody(const TapNote& tn, NoteFieldRenderArgs const& field_args, - NoteColumnRenderArgs const& column_args, float fBeat, bool bIsBeingHeld, + void DrawHoldBody(const TapNote& tn, const NoteFieldRenderArgs& field_args, + const NoteColumnRenderArgs& column_args, float fBeat, bool bIsBeingHeld, float fYHead, float fYTail, bool bIsAddition, float fPercentFadeToFail, float fColorScale, bool bGlow, float top_beat, float bottom_beat); void DrawHoldPart(vector &vpSpr, - NoteFieldRenderArgs const& field_args, - NoteColumnRenderArgs const& column_args, int fYStep, + const NoteFieldRenderArgs& field_args, + const NoteColumnRenderArgs& column_args, int fYStep, float fPercentFadeToFail, float fColorScale, bool bGlow, float fOverlappedTime, float fYTop, float fYBottom, float fYStartPos, float fYEndPos, bool bWrapping, bool bAnchorToTop, @@ -275,13 +275,14 @@ struct NoteColumnRenderer : public Actor struct NCR_TweenState { + NCR_TweenState(); NCSplineHandler m_pos_handler; NCSplineHandler m_rot_handler; NCSplineHandler m_zoom_handler; static void MakeWeightedAverage(NCR_TweenState& out, - NCR_TweenState const& from, NCR_TweenState const& to, float between); - bool operator==(NCR_TweenState const& other) const; - bool operator!=(NCR_TweenState const& other) const { return !operator==(other); } + const NCR_TweenState& from, const NCR_TweenState& to, float between); + bool operator==(const NCR_TweenState& other) const; + bool operator!=(const NCR_TweenState& other) const { return !operator==(other); } }; NCR_TweenState& NCR_DestTweenState() @@ -291,7 +292,7 @@ struct NoteColumnRenderer : public Actor else { return NCR_Tweens.back(); } } - NCR_TweenState const& NCR_DestTweenState() const { return const_cast(this)->NCR_DestTweenState(); } + const NCR_TweenState& NCR_DestTweenState() const { return const_cast(this)->NCR_DestTweenState(); } virtual void SetCurrentTweenStart(); virtual void EraseHeadTween(); diff --git a/src/NoteField.cpp b/src/NoteField.cpp index 97dd4e976b..59d60fe54b 100644 --- a/src/NoteField.cpp +++ b/src/NoteField.cpp @@ -1397,10 +1397,10 @@ public: } \ return 0; \ } - SET_CALLBACK_GENERIC(SetStepCallback, m_StepCallback); - SET_CALLBACK_GENERIC(SetSetPressedCallback, m_SetPressedCallback); - SET_CALLBACK_GENERIC(SetDidTapNoteCallback, m_DidTapNoteCallback); - SET_CALLBACK_GENERIC(SetDidHoldNoteCallback, m_DidHoldNoteCallback); + SET_CALLBACK_GENERIC(set_step_callback, m_StepCallback); + SET_CALLBACK_GENERIC(set_set_pressed_callback, m_SetPressedCallback); + SET_CALLBACK_GENERIC(set_did_tap_note_callback, m_DidTapNoteCallback); + SET_CALLBACK_GENERIC(set_did_hold_note_callback, m_DidHoldNoteCallback); #undef SET_CALLBACK_GENERIC static int check_column(lua_State* L, int index) @@ -1416,7 +1416,7 @@ public: return col; } - static int Step(T* p, lua_State* L) + static int step(T* p, lua_State* L) { int col= check_column(L, 1); TapNoteScore tns= Enum::Check(L, 2); @@ -1424,14 +1424,14 @@ public: return 0; } - static int SetPressed(T* p, lua_State* L) + static int set_pressed(T* p, lua_State* L) { int col= check_column(L, 1); p->SetPressed(col, true); return 0; } - static int DidTapNote(T* p, lua_State* L) + static int did_tap_note(T* p, lua_State* L) { int col= check_column(L, 1); TapNoteScore tns= Enum::Check(L, 2); @@ -1440,7 +1440,7 @@ public: return 0; } - static int DidHoldNote(T* p, lua_State* L) + static int did_hold_note(T* p, lua_State* L) { int col= check_column(L, 1); HoldNoteScore hns= Enum::Check(L, 2); @@ -1462,14 +1462,14 @@ public: LunaNoteField() { - ADD_METHOD(SetStepCallback); - ADD_METHOD(SetSetPressedCallback); - ADD_METHOD(SetDidTapNoteCallback); - ADD_METHOD(SetDidHoldNoteCallback); - ADD_METHOD(Step); - ADD_METHOD(SetPressed); - ADD_METHOD(DidTapNote); - ADD_METHOD(DidHoldNote); + ADD_METHOD(set_step_callback); + ADD_METHOD(set_set_pressed_callback); + ADD_METHOD(set_did_tap_note_callback); + ADD_METHOD(set_did_hold_note_callback); + ADD_METHOD(step); + ADD_METHOD(set_pressed); + ADD_METHOD(did_tap_note); + ADD_METHOD(did_hold_note); ADD_METHOD(get_column_actors); } }; From 1ea48efb800c4a5d40f4b232f6c8dceb1df677c7 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Tue, 6 Jan 2015 22:00:06 -0700 Subject: [PATCH 13/14] Changed tper to vector of float. --- Themes/_fallback/Scripts/01 alias.lua | 10 ++++++---- src/ActorMultiVertex.cpp | 2 +- src/NoteDisplay.h | 3 +++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Themes/_fallback/Scripts/01 alias.lua b/Themes/_fallback/Scripts/01 alias.lua index 3ab5d577f9..65a527fcb6 100644 --- a/Themes/_fallback/Scripts/01 alias.lua +++ b/Themes/_fallback/Scripts/01 alias.lua @@ -76,10 +76,12 @@ function make_camel_aliases(class) end end -make_camel_aliases(CubicSplineN) -make_camel_aliases(NCSplineHandler) -make_camel_aliases(NoteColumnRenderer) -make_camel_aliases(NoteField) +local to_camel_list= { + CubicSplineN, NCSplineHandler, NoteColumnRenderer, NoteField} + +for i, class in ipairs(to_camel_list) do + make_camel_aliases(class) +end --[[ ActorScroller: all of these got renamed, so alias the lowercase ones if themes are going to look for them. ]] diff --git a/src/ActorMultiVertex.cpp b/src/ActorMultiVertex.cpp index 7794d069c4..c4f24890ce 100644 --- a/src/ActorMultiVertex.cpp +++ b/src/ActorMultiVertex.cpp @@ -322,7 +322,7 @@ void ActorMultiVertex::SetVertsFromSplinesInternal(size_t num_splines, size_t of vector& verts= AMV_DestTweenState().vertices; size_t first= AMV_DestTweenState().FirstToDraw + offset; size_t num_verts= AMV_DestTweenState().GetSafeNumToDraw(AMV_DestTweenState()._DrawMode, AMV_DestTweenState().NumToDraw); - float tper[num_splines]= {0.0f}; + vector tper(num_splines, 0.0f); float num_parts= static_cast(num_verts - offset) / static_cast(num_splines); for(size_t i= 0; i < num_splines; ++i) diff --git a/src/NoteDisplay.h b/src/NoteDisplay.h index df34bb82db..ce5afe18d8 100644 --- a/src/NoteDisplay.h +++ b/src/NoteDisplay.h @@ -96,6 +96,9 @@ enum NoteColumnSplineMode NoteColumnSplineMode_Invalid }; +const RString& NoteColumnSplineModeToString(NoteColumnSplineMode ncsm); +LuaDeclareType(NoteColumnSplineMode); + // A little pod struct to carry the data the NoteField needs to pass to the // NoteDisplay during rendering. struct NoteFieldRenderArgs From 5f3d07807c06131e74853ddc99a1b144fbec8e4e Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Thu, 8 Jan 2015 04:26:39 -0700 Subject: [PATCH 14/14] Fixed vert index math in SetVertsFromSplinesInternal --- src/ActorMultiVertex.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ActorMultiVertex.cpp b/src/ActorMultiVertex.cpp index c4f24890ce..8d0ffdf2b9 100644 --- a/src/ActorMultiVertex.cpp +++ b/src/ActorMultiVertex.cpp @@ -321,9 +321,9 @@ void ActorMultiVertex::SetVertsFromSplinesInternal(size_t num_splines, size_t of { vector& verts= AMV_DestTweenState().vertices; size_t first= AMV_DestTweenState().FirstToDraw + offset; - size_t num_verts= AMV_DestTweenState().GetSafeNumToDraw(AMV_DestTweenState()._DrawMode, AMV_DestTweenState().NumToDraw); + size_t num_verts= AMV_DestTweenState().GetSafeNumToDraw(AMV_DestTweenState()._DrawMode, AMV_DestTweenState().NumToDraw) - offset; vector tper(num_splines, 0.0f); - float num_parts= static_cast(num_verts - offset) / + float num_parts= static_cast(num_verts) / static_cast(num_splines); for(size_t i= 0; i < num_splines; ++i) {