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]); } }