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