diff --git a/Docs/Changelog_sm5.txt b/Docs/Changelog_sm5.txt
index c4b025070e..12dad16fa2 100644
--- a/Docs/Changelog_sm5.txt
+++ b/Docs/Changelog_sm5.txt
@@ -8,6 +8,23 @@ ________________________________________________________________________________
----------
* [Sprite] SetStateProperties function added. [kyzentun]
+2015/01/06
+----------
+* [ActorMultiVertex] GetSpline and SetVertsFromSplines functions added.
+ [kyzentun]
+* [NoteColumnRenderer] Functions for fetching the spline handlers for the
+ column added. [kyzentun]
+* [NCSplineHandler] New class for setting various parameters of a spline used
+ by a NoteColumnRenderer and containing the spline. [kyzentun]
+* [CubicSplineN] New class that provides spline functionality. [kyzentun]
+
+2014/12/26
+----------
+* [NoteField] Columns turned into actors that can be fetched with
+ get_column_actors. [kyzentun]
+ Tiny boost in fps when there is a large number of notes on screen.
+* [NoteColumnRenderer] New class for controlling one column. [kyzentun]
+
2014/12/20
----------
* [Preferences] AllowMultipleHighScoreWithSameName, ComboContinuesBetweenSongs
diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml
index fb367a0849..460157091c 100644
--- a/Docs/Luadoc/Lua.xml
+++ b/Docs/Luadoc/Lua.xml
@@ -272,6 +272,7 @@
+
@@ -301,6 +302,7 @@
+
@@ -494,6 +496,7 @@
+
@@ -504,6 +507,7 @@
+
@@ -676,6 +680,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -965,15 +994,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -2213,6 +2259,11 @@
+
+
+
+
+
diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml
index 37069fa2a1..9627c6f155 100644
--- a/Docs/Luadoc/LuaDocumentation.xml
+++ b/Docs/Luadoc/LuaDocumentation.xml
@@ -112,6 +112,9 @@ save yourself some time, copy this for undocumented things:
Tries to connect to the server at sAddress.
+
+ Creates a CubicSplineN for you to use. Make sure you destroy the CubicSplineN when you're done with it, or you will have a memory leak.
+
[02 Colors.lua]
@@ -831,6 +834,12 @@ save yourself some time, copy this for undocumented things:
Returns the Actor's parent, or nil if it doesn't have one.
+
+ Returns the Actor's fake parent, or nil if it doesn't have one.
+
+
+ Sets the Actor's fake parent to p, or clears it if p is nil.
+
Returns the Actor's visibility.
@@ -1515,6 +1524,22 @@ save yourself some time, copy this for undocumented things:
Sets multiple vertices at once. The elements of vertices should themselves be tables, of the form provided to SetVertex. If vertices is the first argument it will start from vertex 1. If an integer is provided before vertices it will start from that vertex. It will add vertices as necessary.
Example: self:SetVertices( { { { x1, y1, z1 } , { r1,g1,b1,a1 } , { tcx1,tcy1 } }; { { x2, y2, z2 } , { r2,g2,b2,a2 } , { tcx2,tcy2 } } } )
+
+ Sets all the drawn verts of the ActorMultiVertex by evaluating the splines.
+ ("all the drawn verts" means all the verts between FirstToDraw and NumToDraw, the verts that are set to draw in the current tween state.)
+ The parts of the ActorMultiVertex are evenly spaced along the spline in terms of t value.
+ The exact behavior depends on the draw mode.
+ DrawMode_Quads uses all 4 splines, one for each corner.
+ DrawMode_QuadStrip and DrawMode_Strip use 2 splines, one for each edge of the strip.
+ DrawMode_Fan uses one spline, for the edge verts of the fan. The first vert is not touched because it is the center.
+ DrawMode_Triangles uses 3 splines, one for each corner.
+ DrawMode_SymmetricQuadStrip uses 3 splines, one on each edge and one in the center.
+ DrawMode_LineStrip uses 1 spline.
+
+
+ Returns the requested spline. Spline indices range from 1 to 4.
+ ActorMultiVertex splines are not inside the tween state, and will not change the verts until you call SetVertsFromSplines.
+
Sets the number of vertices.
@@ -2056,6 +2081,92 @@ save yourself some time, copy this for undocumented things:
Returns the SHA-1 hash for s.
+
+
+ All functions in this class have camel case equivalents, use whichever naming style you prefer.
+ This spline implementation is a cubic spline.
+ A spline is a line calculated from a small set of points with mathematical smooting applied.
+ Splines can have any number of dimensions, but splines owned by actors (the ones inside NCSplineHandler and ActorMultiVertex) always have 3 dimensions.
+
+
+ Solves the spline, setting the coefficients.
+
+
+ Evaluates the spline at the given t value, returning a table of the results for each dimension of the spline.
+ t can range from 0 to the value returned by get_max_t().
+ A normal spline will return its starting point for any t value less than 0 and its end point for any t value greater than the max.
+ A looped spline adjust the t value to be within the its range by adding or subtracting the max t as needed. (so if the max t is 4 and you evaluate at 5, it will return the same as if you evaluated at 1.)
+
+
+ Evaluates the derivative at t.
+
+
+ Evaluates the second derivative at t.
+
+
+ Evaluates the third derivative at t.
+ Second and third derivative functions exist because they're possible, not because they're expected to be useful. The fourth derivative would be 0 because the equation for evaluating the spline is "a + (b*t) + (c*t^2) + (d*t^3)".
+
+
+ Sets point i of the spline to the position specified by the table p.
+
+
+ Sets the coefficients of the spline at point i.
+ Each table must contain a value for each dimension of the spline.
+ Solving the spline normally should cover all normal usage, this is for people that want a spline with an abnormal behavior, so if you set the coefficients directly, expect to end up with an unsmooth shape.
+
+
+ Returns a table containing the tables of coefficients for the point i.
+
+
+ Sets the spatial extent of dimension d of the spline to e.
+ The spatial extent exists to handle numbers that exist in a finite looped space, instead of the flat infinite space.
+ To put it more concretely, spatial extent exists to allow a spline to control rotation with wrapping behavior at 0.0 and 2pi, instead of suddenly jerking from 2pi to 0.0.
+
+
+ Returns the spatial extent of dimension d of the spline.
+
+
+ Returns the max t value the spline extends to. For a normal spline, this will be size()-1. For a looped spline, this will be size().
+
+
+ Sets the number of points in the spline. You must set the number of points before trying to set the position of any point.
+
+
+ Returns the number of points in the spline.
+
+
+ Sets the number of dimensions the spline has.
+ Splines that are owned by actors (the ones inside ActorMultiVertex and NCSplineHandler) cannot have their number of dimensions changed because the actors require them to have 3 dimensions.
+
+
+ Returns the number of dimensions the spline has.
+
+
+ Returns true of the spline has zero points, or false if it has more than zero points.
+
+
+ Sets whether the spline is looped. A looped spline is one where the end point is connected to the start point.
+
+
+ Returns whether the spline is looped.
+
+
+ Sets whether the spline is polygonal. If the spline is polygonal, then it will have straight lines between the points instead of curves.
+
+
+ Returns whether the spline is polygonal.
+
+
+ Sets whether the spline is dirty. A dirty spline is one that has been changed in some way that affects its shape. When solve() is called, the spline will only be solved if it is dirty. The dirty flag is automatically set by everything, so you should never have to call this function.
+
+
+ Returns whether the spline is currently dirty.
+
+
+ Destroys the spline, freeing the memory allocated for it. This can only be called on splines created with create_spline().
+
+
Sets the DifficultyIcon's state from the difficulty passed in.
@@ -2944,41 +3055,102 @@ save yourself some time, copy this for undocumented things:
Returns a table of noteskin names for the current gametype.
+
+
+ All functions in this class have camel case equivalents, use whichever naming style you prefer.
+ The spline handler holds info on how the spline is used by the engine.
+ Each get/set pair of functions in this class is for a different aspect of the spline's behavior.
+
+
+ Returns the spline for this handler.
+
+
+ Returns the beats per t value of the spline. If the beats_per_t is 4, then a note must be on screen for 4 beats to traverse from one point on the spline to the next.
+
+
+ Sets the beats per t value for the spline.
+
+
+ Returns the t value that receptors are evaluated at.
+
+
+ the t value that receptors are evaluated at.
+
+
+ Returns the mode the spline is set to.
+ "NoteColumnSplineMode_Disabled" means the spline will not affect the notes or receptors at all.
+ "NoteColumnSplineMode_Offset" means the spline will added to the effects from the mods.
+ "NoteColumnSplineMode_Position" means only the spline affect the notes and mods will be ignored. (but only mods that affect the same aspect of the note as the spline will be disabled. So a rotation spline won't disable Mini or Tiny, but a zoom spline will, and a zoom spline won't disable Dizzy, Twirl, or Roll, but a rotation spline will.)
+
+
+ Sets the current spline mode for this handler.
+
+
+ Returns whether the current song beat is subtracted from a note's beat when calculating the t value to use on the spline.
+
+
+ Sets whether the current song beat is subtracted from a note's beat when calculating the t value to use on the spline.
+
+
+
+
+ All functions in this class have camel case equivalents, use whichever naming style you prefer.
+ Position, rotation, and zoom each have separate spline handlers to allow them to have separate independent behavior.
+ It is important to note that the spline handlers are inside the tween state, so whenever you start a new tween on the actor, you need to refetch the spline handlers.
+
+
+ Returns the handler for the position spline.
+
+
+ Returns the handler for the rotation spline.
+ The rotation applied by the rotation spline is in radians.
+ For convenience, the spatial extent of the rotation spline defaults to 2pi.
+
+
+ Returns the handler for the zoom spline.
+
+
-
+
+ All functions in this class have camel case equivalents, use whichever naming style you prefer.
+
+
Makes the NoteField act as if a hold note was hit in the column, with the given score and bright setting.
- The callback for DidHoldNote will not be called.
+ The callback for did_hold_note will not be called.
-
+
Makes the NoteField act as if a tap note was hit in the column, with the given score and bright setting.
- The callback for DidTapNote will not be called.
+ The callback for did_tap_note will not be called.
-
+
+ Returns a table of the actors for the columns. This means that each column is an actor, so you can move it around or animate it like an actor. See the NoteColumnRenderer class for a list of special functions for the column's actor.
+
+
Same as SetDidTapNoteCallback, but for hold notes. Uses HoldNoteScore instead of TapNoteScore.
-
+
Sets the function that the NoteField will call whenever a tap note is hit.
The callback function is passed the column, the TapNoteScore, and whether the explosion will be bright.
The callback function can return changed values for the NoteField to use instead of the ones that were passed.
Pass nil instead of a function to clear the callback.
-
+
Makes the NoteField act as if a press occurred in the column.
- The callback for SetPressed will not be called.
+ The callback for set_pressed will not be called.
-
+
Sets the function that the NoteField will call whenever a press occurs.
The callback function is passed the column for the press.
The callback function can return changed values for the NoteField to use instead of the ones that were passed.
Pass nil instead of a function to clear the callback.
-
+
Sets the function that the NoteField will call whenever a step occurs.
The callback function is passed the column and the TapNoteScore for the step.
The callback function can return changed values for the NoteField to use instead of the ones that were passed.
Pass nil instead of a function to clear the callback.
-
+
Makes the NoteField act as if a step occurred in the column with the given score.
The callback for Step will not be called.
diff --git a/Themes/_fallback/Scripts/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..65a527fcb6 100644
--- a/Themes/_fallback/Scripts/01 alias.lua
+++ b/Themes/_fallback/Scripts/01 alias.lua
@@ -24,6 +24,65 @@ _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
+ if class[alt_name] then 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
+
+local to_camel_list= {
+ CubicSplineN, NCSplineHandler, NoteColumnRenderer, NoteField}
+
+for i, class in ipairs(to_camel_list) do
+ make_camel_aliases(class)
+end
+
--[[ ActorScroller: all of these got renamed, so alias the lowercase ones if
themes are going to look for them. ]]
ActorScroller.getsecondtodestination = ActorScroller.GetSecondsToDestination
diff --git a/Themes/_fallback/Scripts/02 Utilities.lua b/Themes/_fallback/Scripts/02 Utilities.lua
index 8b53d55055..4cc21ba963 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/Actor.cpp b/src/Actor.cpp
index 402899c411..d2ab5487a9 100644
--- a/src/Actor.cpp
+++ b/src/Actor.cpp
@@ -164,6 +164,7 @@ Actor::Actor()
m_size = RageVector2( 1, 1 );
InitState();
m_pParent = NULL;
+ m_FakeParent= NULL;
m_bFirstUpdate = true;
}
@@ -183,6 +184,7 @@ Actor::Actor( const Actor &cpy ):
#define CPY(x) x = cpy.x
CPY( m_sName );
CPY( m_pParent );
+ CPY( m_FakeParent );
CPY( m_pLuaInstance );
CPY( m_baseRotation );
@@ -279,24 +281,56 @@ void Actor::LoadFromNode( const XNode* pNode )
PlayCommandNoRecurse( Message("Init") );
}
+bool Actor::PartiallyOpaque()
+{
+ return m_pTempState->diffuse[0].a > 0 || m_pTempState->diffuse[1].a > 0 ||
+ m_pTempState->diffuse[2].a > 0 || m_pTempState->diffuse[3].a > 0 ||
+ m_pTempState->glow.a > 0;
+}
+
void Actor::Draw()
{
if( !m_bVisible ||
m_fHibernateSecondsLeft > 0 ||
this->EarlyAbortDraw() )
+ {
return; // early abort
+ }
+ bool fake_parent_partially_opaque= true;
+ if(m_FakeParent)
+ {
+ if(!m_FakeParent->m_bVisible || m_FakeParent->m_fHibernateSecondsLeft > 0
+ || m_FakeParent->EarlyAbortDraw())
+ {
+ return;
+ }
+ m_FakeParent->PreDraw();
+ fake_parent_partially_opaque= m_FakeParent->PartiallyOpaque();
+ }
this->PreDraw();
ASSERT( m_pTempState != NULL );
- if( m_pTempState->diffuse[0].a > 0 || m_pTempState->diffuse[1].a > 0 || m_pTempState->diffuse[2].a > 0 || m_pTempState->diffuse[3].a > 0 || m_pTempState->glow.a > 0 ) // This Actor is not fully transparent
- {
+ if(PartiallyOpaque() && fake_parent_partially_opaque)
+ {
+ if(m_FakeParent)
+ {
+ m_FakeParent->BeginDraw();
+ }
// call the most-derived versions
this->BeginDraw();
this->DrawPrimitives(); // call the most-derived version of DrawPrimitives();
this->EndDraw();
+ if(m_FakeParent)
+ {
+ m_FakeParent->EndDraw();
+ }
}
this->PostDraw();
+ if(m_FakeParent)
+ {
+ m_FakeParent->PostDraw();
+ }
m_pTempState = NULL;
}
@@ -1705,6 +1739,32 @@ public:
pParent->PushSelf(L);
return 1;
}
+ static int GetFakeParent(T* p, lua_State *L)
+ {
+ Actor* fake= p->GetFakeParent();
+ if(fake == NULL)
+ {
+ lua_pushnil(L);
+ }
+ else
+ {
+ fake->PushSelf(L);
+ }
+ return 1;
+ }
+ static int SetFakeParent(T* p, lua_State* L)
+ {
+ if(lua_isnoneornil(L, 1))
+ {
+ p->SetFakeParent(NULL);
+ }
+ else
+ {
+ Actor* fake= Luna::check(L, 1);
+ p->SetFakeParent(fake);
+ }
+ COMMON_RETURN_SELF;
+ }
static int Draw( T* p, lua_State *L )
{
LUA->YieldLua();
@@ -1876,6 +1936,8 @@ public:
ADD_METHOD( GetName );
ADD_METHOD( GetParent );
+ ADD_METHOD( GetFakeParent );
+ ADD_METHOD( SetFakeParent );
ADD_METHOD( Draw );
}
diff --git a/src/Actor.h b/src/Actor.h
index 2be5f711bd..781533987d 100644
--- a/src/Actor.h
+++ b/src/Actor.h
@@ -232,6 +232,8 @@ public:
float aux;
};
+ // PartiallyOpaque broken out of Draw for reuse and clarity.
+ bool PartiallyOpaque();
/**
* @brief Calls multiple functions for drawing the Actors.
*
@@ -305,6 +307,9 @@ public:
* @return the Actor's lineage. */
RString GetLineage() const;
+ void SetFakeParent(Actor* mailman) { m_FakeParent= mailman; }
+ Actor* GetFakeParent() { return m_FakeParent; }
+
/**
* @brief Retrieve the Actor's x position.
* @return the Actor's x position. */
@@ -607,6 +612,10 @@ protected:
RString m_sName;
/** @brief the current parent of this Actor if it exists. */
Actor *m_pParent;
+ // m_FakeParent exists to provide a way to render the actor inside another's
+ // state without making that actor the parent. It's like having multiple
+ // parents. -Kyz
+ Actor* m_FakeParent;
/** @brief Some general information about the Tween. */
struct TweenInfo
diff --git a/src/ActorMultiVertex.cpp b/src/ActorMultiVertex.cpp
index c569eb3d7a..8d0ffdf2b9 100644
--- a/src/ActorMultiVertex.cpp
+++ b/src/ActorMultiVertex.cpp
@@ -7,6 +7,7 @@
#include "RageLog.h"
#include "RageDisplay.h"
#include "RageTexture.h"
+#include "RageTimer.h"
#include "RageUtil.h"
#include "ActorUtil.h"
#include "Foreach.h"
@@ -62,6 +63,12 @@ ActorMultiVertex::ActorMultiVertex()
_EffectMode = EffectMode_Normal;
_TextureMode = TextureMode_Modulate;
+ _splines.resize(num_vert_splines);
+ for(size_t i= 0; i < num_vert_splines; ++i)
+ {
+ _splines[i].redimension(3);
+ _splines[i].m_owned_by_actor= true;
+ }
}
ActorMultiVertex::~ActorMultiVertex()
@@ -78,6 +85,7 @@ ActorMultiVertex::ActorMultiVertex( const ActorMultiVertex &cpy ):
CPY( AMV_start );
CPY( _EffectMode );
CPY( _TextureMode );
+ CPY( _splines );
#undef CPY
if( cpy._Texture != NULL )
@@ -309,6 +317,61 @@ bool ActorMultiVertex::EarlyAbortDraw() const
return false;
}
+void ActorMultiVertex::SetVertsFromSplinesInternal(size_t num_splines, size_t offset)
+{
+ vector& verts= AMV_DestTweenState().vertices;
+ size_t first= AMV_DestTweenState().FirstToDraw + offset;
+ size_t num_verts= AMV_DestTweenState().GetSafeNumToDraw(AMV_DestTweenState()._DrawMode, AMV_DestTweenState().NumToDraw) - offset;
+ vector tper(num_splines, 0.0f);
+ float num_parts= static_cast(num_verts) /
+ static_cast(num_splines);
+ for(size_t i= 0; i < num_splines; ++i)
+ {
+ tper[i]= _splines[i].get_max_t() / num_parts;
+ }
+ for(size_t v= 0; v < num_verts; ++v)
+ {
+ vector pos;
+ const int spi= v%num_splines;
+ _splines[spi].evaluate(static_cast(v) * tper[spi], pos);
+ verts[v+first].p.x= pos[0];
+ verts[v+first].p.y= pos[1];
+ verts[v+first].p.z= pos[2];
+ }
+}
+
+void ActorMultiVertex::SetVertsFromSplines()
+{
+ if(AMV_DestTweenState().vertices.empty()) { return; }
+ switch(AMV_DestTweenState()._DrawMode)
+ {
+ case DrawMode_Quads:
+ SetVertsFromSplinesInternal(4, 0);
+ break;
+ case DrawMode_QuadStrip:
+ case DrawMode_Strip:
+ SetVertsFromSplinesInternal(2, 0);
+ break;
+ case DrawMode_Fan:
+ // Skip the first vert because it is the center of the fan. -Kyz
+ SetVertsFromSplinesInternal(1, 1);
+ break;
+ case DrawMode_Triangles:
+ case DrawMode_SymmetricQuadStrip:
+ SetVertsFromSplinesInternal(3, 0);
+ break;
+ case DrawMode_LineStrip:
+ SetVertsFromSplinesInternal(1, 0);
+ break;
+ }
+}
+
+CubicSplineN* ActorMultiVertex::GetSpline(size_t i)
+{
+ ASSERT(i < num_vert_splines);
+ return &(_splines[i]);
+}
+
void ActorMultiVertex::SetCurrentTweenStart()
{
AMV_start= AMV_current;
@@ -638,6 +701,23 @@ public:
COMMON_RETURN_SELF;
}
+ static int GetSpline(T* p, lua_State* L)
+ {
+ size_t i= static_cast(IArg(1)-1);
+ if(i >= ActorMultiVertex::num_vert_splines)
+ {
+ luaL_error(L, "Spline index must be greater than 0 and less than or equal to %zu.", ActorMultiVertex::num_vert_splines);
+ }
+ p->GetSpline(i)->PushSelf(L);
+ return 1;
+ }
+
+ static int SetVertsFromSplines(T* p, lua_State* L)
+ {
+ p->SetVertsFromSplines();
+ COMMON_RETURN_SELF;
+ }
+
static int SetTexture( T* p, lua_State *L )
{
RageTexture *Texture = Luna::check(L, 1);
@@ -679,6 +759,9 @@ public:
ADD_METHOD( GetCurrFirstToDraw );
ADD_METHOD( GetCurrNumToDraw );
+ ADD_METHOD( GetSpline );
+ ADD_METHOD( SetVertsFromSplines );
+
// Copy from RageTexture
ADD_METHOD( SetTexture );
ADD_METHOD( GetTexture );
diff --git a/src/ActorMultiVertex.h b/src/ActorMultiVertex.h
index 176d2a7cad..5b598cb310 100644
--- a/src/ActorMultiVertex.h
+++ b/src/ActorMultiVertex.h
@@ -1,7 +1,9 @@
/** @brief ActorMultiVertex - A texture created from multiple textures. */
#include "Actor.h"
+#include "CubicSpline.h"
#include "RageDisplay.h"
+#include "RageMath.h"
#include "RageTextureID.h"
enum DrawMode
@@ -26,6 +28,7 @@ class RageTexture;
class ActorMultiVertex: public Actor
{
public:
+ static const size_t num_vert_splines= 4;
ActorMultiVertex();
ActorMultiVertex( const ActorMultiVertex &cpy );
virtual ~ActorMultiVertex();
@@ -35,7 +38,9 @@ public:
struct AMV_TweenState
{
- AMV_TweenState(): _DrawMode(DrawMode_Invalid), FirstToDraw(0), NumToDraw(-1), line_width(1.0f) {}
+ AMV_TweenState(): _DrawMode(DrawMode_Invalid), FirstToDraw(0),
+ NumToDraw(-1), line_width(1.0f)
+ {}
static void MakeWeightedAverage(AMV_TweenState& average_out, const AMV_TweenState& ts1, const AMV_TweenState& ts2, float percent_between);
bool operator==(const AMV_TweenState& other) const;
bool operator!=(const AMV_TweenState& other) const { return !operator==(other); }
@@ -102,6 +107,10 @@ public:
void SetVertexColor( int index , RageColor c );
void SetVertexCoords( int index , float TexCoordX , float TexCoordY );
+ inline void SetVertsFromSplinesInternal(size_t num_splines, size_t start_vert);
+ void SetVertsFromSplines();
+ CubicSplineN* GetSpline(size_t i);
+
virtual void PushSelf( lua_State *L );
private:
@@ -117,6 +126,10 @@ private:
EffectMode _EffectMode;
TextureMode _TextureMode;
+
+ // Four splines for controlling vert positions, because quads drawmode
+ // requires four. -Kyz
+ vector _splines;
};
/**
diff --git a/src/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
new file mode 100644
index 0000000000..142f305aa0
--- /dev/null
+++ b/src/CubicSpline.cpp
@@ -0,0 +1,1037 @@
+#include "global.h"
+#include "CubicSpline.h"
+#include "RageLog.h"
+#include "RageUtil.h"
+#include
+using std::list;
+
+// Spline solving optimization:
+// The tridiagonal part of the system of equations for a spline of size n is
+// the same for all splines of size n. It's not affected by the positions
+// of the points.
+// So spline solving can be split into two parts. Part 1 solves the
+// tridiagonal and stores the result. Part 2 takes the solved tridiagonal
+// and applies it to the positions to find the coefficients.
+// Part 1 only needs to be done when the number of points changes. So this
+// could cut solve time for the same number of points substantially.
+// Further optimization is to cache the part 1 results for the last 16 spline
+// sizes solved, to reduce the cost of using lots of splines with a small
+// number of sizes.
+
+struct SplineSolutionCache
+{
+ struct Entry
+ {
+ vector diagonals;
+ vector multiples;
+ };
+ void solve_diagonals_straight(vector& diagonals, vector& multiples);
+ void solve_diagonals_looped(vector& diagonals, vector& multiples);
+private:
+ void prep_inner(size_t last, vector& out);
+ bool find_in_cache(list& cache, vector& outd, vector& outm);
+ void add_to_cache(list& cache, vector& outd, vector& outm);
+ list straight_diagonals;
+ list looped_diagonals;
+};
+
+const size_t solution_cache_limit= 16;
+
+bool SplineSolutionCache::find_in_cache(list& cache, vector& outd, vector& outm)
+{
+ size_t out_size= outd.size();
+ for(list::iterator entry= cache.begin();
+ entry != cache.end(); ++entry)
+ {
+ if(out_size == entry->diagonals.size())
+ {
+ for(size_t i= 0; i < out_size; ++i)
+ {
+ outd[i]= entry->diagonals[i];
+ }
+ outm.resize(entry->multiples.size());
+ for(size_t i= 0; i < entry->multiples.size(); ++i)
+ {
+ outm[i]= entry->multiples[i];
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+void SplineSolutionCache::add_to_cache(list& cache, vector& outd, vector& outm)
+{
+ if(cache.size() >= solution_cache_limit)
+ {
+ cache.pop_back();
+ }
+ cache.push_front(Entry());
+ cache.front().diagonals= outd;
+ cache.front().multiples= outm;
+}
+
+void SplineSolutionCache::prep_inner(size_t last, vector& out)
+{
+ for(size_t i= 1; i < last; ++i)
+ {
+ out[i]= 4.0f;
+ }
+}
+
+void SplineSolutionCache::solve_diagonals_straight(vector& diagonals, vector& multiples)
+{
+ if(find_in_cache(straight_diagonals, diagonals, multiples))
+ {
+ return;
+ }
+
+ // Solution steps:
+ // Two stages: First, work downwards, zeroing the 1s below each diagonal.
+ // | 2 1 0 0 | -> | 2 1 0 0 | -> | 2 1 0 0 | -> | 2 1 0 0 |
+ // | 1 4 1 0 | -> | 0 a 1 0 | -> | 0 d 1 0 | -> | 0 a 1 0 |
+ // | 0 1 4 1 | -> | 0 1 4 1 | -> | 0 0 b 1 | -> | 0 0 b 1 |
+ // | 0 0 1 2 | -> | 0 0 1 2 | -> | 0 0 1 2 | -> | 0 0 0 c |
+ // Second stage: Work upwards, zeroing the 1s above each diagonal.
+ // V
+ // | 2 1 0 0 | -> | 2 1 0 0 | -> | 2 0 0 0 |
+ // | 0 a 1 0 | -> | 0 a 0 0 | -> | 0 a 0 0 |
+ // | 0 0 b 0 | -> | 0 0 b 0 | -> | 0 0 b 0 |
+ // | 0 0 0 c | -> | 0 0 0 c | -> | 0 0 0 c |
+
+ size_t last= diagonals.size();
+ diagonals[0]= 2.0f;
+ prep_inner(last-1, diagonals);
+ diagonals[last-1]= 2.0f;
+
+ // Stage one.
+ // Operation: Add row[0] * -.5 to row[1] to zero [r1][c0].
+ diagonals[1]-= .5f;
+ multiples.push_back(.5f);
+ for(size_t i= 1; i < last-1; ++i)
+ {
+ // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci].
+ const float diag_recip= 1.0f / diagonals[i];
+ diagonals[i+1]-= diag_recip;
+ multiples.push_back(diag_recip);
+ }
+ // Stage two.
+ for(size_t i= last-1; i > 0; --i)
+ {
+ // Operation: Add row [i] / -[ri][ci] to row[i-1] to zero [ri-1][ci].
+ multiples.push_back(1.0f / diagonals[i]);
+ }
+ // Solving finished.
+ add_to_cache(straight_diagonals, diagonals, multiples);
+}
+
+void SplineSolutionCache::solve_diagonals_looped(vector& diagonals, vector& multiples)
+{
+ if(find_in_cache(looped_diagonals, diagonals, multiples))
+ {
+ return;
+ }
+
+ // The steps to solve the system of equations look like this:
+ // Stage one: Zero the 1s below the diagonals.
+ // | 4 1 0 0 1 | -> | 4 1 0 0 1 | -> | 4 1 0 0 1 | -> | 4 1 0 0 1 |
+ // | 1 4 1 0 0 | -> | 0 a 1 0 u | -> | 0 a 1 0 u | -> | 0 a 1 0 u |
+ // | 0 1 4 1 0 | -> | 0 1 4 1 0 | -> | 0 0 b 1 v | -> | 0 0 b 1 v |
+ // | 0 0 1 4 1 | -> | 0 0 1 4 1 | -> | 0 0 1 4 1 | -> | 0 0 0 c w |
+ // | 1 0 0 1 4 | -> | 1 0 0 1 4 | -> | 1 0 0 1 4 | -> | 1 0 0 1 4 |
+ // V
+ // | 4 1 0 0 1 |
+ // | 0 a 1 0 u |
+ // | 0 0 b 1 v |
+ // | 0 0 0 c w |
+ // | 1 0 0 0 d |
+ // The top of the right column is left unzeroed because it will be changed
+ // by stage two, nullifying the effect of zeroing it.
+ // V Stage two: Zero the 1s above the diagonals, starting with the second
+ // to last row to avoid carrying effects across the left column.
+ // | 4 1 0 0 1 | -> | 4 1 0 0 1 | -> | 4 0 0 0 z | -> | 4 0 0 0 z |
+ // | 0 a 1 0 u | -> | 0 a 0 0 y | -> | 0 a 0 0 y | -> | 0 a 0 0 y |
+ // | 0 0 b 0 x | -> | 0 0 b 0 x | -> | 0 0 b 0 x | -> | 0 0 b 0 x |
+ // | 0 0 0 c w | -> | 0 0 0 c w | -> | 0 0 0 c w | -> | 0 0 0 c w |
+ // | 1 0 0 0 d | -> | 1 0 0 0 d | -> | 1 0 0 0 d | -> | 0 0 0 0 f |
+ // V Stage three: Zero the right column.
+ // | 4 0 0 0 0 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 | -> | 4 0 0 0 0 |
+ // | 0 a 0 0 y | -> | 0 a 0 0 0 | -> | 0 a 0 0 0 | -> | 0 a 0 0 0 |
+ // | 0 0 b 0 x | -> | 0 0 b 0 x | -> | 0 0 b 0 0 | -> | 0 0 b 0 0 |
+ // | 0 0 0 c w | -> | 0 0 0 c w | -> | 0 0 0 c w | -> | 0 0 0 c 0 |
+ // | 0 0 0 0 f | -> | 0 0 0 0 f | -> | 0 0 0 0 f | -> | 0 0 0 0 f |
+
+ size_t last= diagonals.size();
+ diagonals[0]= 4.0f;
+ prep_inner(last, diagonals);
+ // right_column is sized to not store the diagonal .
+ vector right_column(diagonals.size()-1, 0.0f);
+ right_column[0]= 1.0f;
+ right_column[last-2]= 1.0f;
+
+ // Stage one.
+ for(size_t i= 0; i < last-2; ++i)
+ {
+ // Operation: Add row[i] / -[ri][ci] to row[i+1] to zero [ri+1][ci].
+ const float diag_recip= 1.0f / diagonals[i];
+ diagonals[i+1]-= diag_recip;
+ right_column[i+1]-= right_column[i] * diag_recip;
+ multiples.push_back(diag_recip);
+ }
+ // Last step of stage one needs special handling for right_column.
+ // Operation: Add row[l-2] / [rl-2][cl-2] to row[l-1] to zero [rl-1][cl-2].
+ {
+ const float diag_recip= 1.0f / diagonals[last-2];
+ diagonals[last-1]-= right_column[last-2] * diag_recip;
+ multiples.push_back(diag_recip);
+ }
+ // Stage two.
+ for(size_t i= last-2; i > 0; --i)
+ {
+ // Operation: Add row[i] / -[ri][ci] to row[i-1] to zero [ri-1][ci].
+ const float diag_recip= 1.0f / diagonals[i];
+ right_column[i-1]-= right_column[i] * diag_recip;
+ multiples.push_back(diag_recip);
+ }
+ // Last step of stage two.
+ {
+ // Operation: Add row[0] / [r0][c0] to row[l-1] to zero [rl-1][c0].
+ const float diag_recip= 1.0f / diagonals[0];
+ right_column[0]-= right_column[1] * diag_recip;
+ multiples.push_back(diag_recip);
+ }
+ // Stage three.
+ const size_t end= last-1;
+ for(size_t i= 0; i < end; ++i)
+ {
+ // Operation: Add row[e] * (right_column[i] / [re][ce]) to row[i] to
+ // zero right_column[i].
+ multiples.push_back(right_column[i] / diagonals[end]);
+ }
+
+ // Solving finished.
+ add_to_cache(looped_diagonals, diagonals, multiples);
+}
+
+SplineSolutionCache solution_cache;
+
+// loop_space_difference exists to handle numbers that exist in a finite
+// looped space, instead of the flat infinite space.
+// To put it more concretely, loop_space_difference exists to allow a spline
+// to control rotation with wrapping behavior at 0.0 and 2pi, instead of
+// suddenly jerking from 2pi to 0.0. -Kyz
+float loop_space_difference(float a, float b, float spatial_extent);
+float loop_space_difference(float a, float b, float spatial_extent)
+{
+ const float norm_diff= a - b;
+ if(spatial_extent == 0.0f) { return norm_diff; }
+ const float plus_diff= a - (b + spatial_extent);
+ const float minus_diff= a - (b - spatial_extent);
+ const float abs_norm_diff= abs(norm_diff);
+ const float abs_plus_diff= abs(plus_diff);
+ const float abs_minus_diff= abs(minus_diff);
+ if(abs_norm_diff < abs_plus_diff)
+ {
+ if(abs_norm_diff < abs_minus_diff)
+ {
+ return norm_diff;
+ }
+ if(abs_plus_diff < abs_minus_diff)
+ {
+ return plus_diff;
+ }
+ return minus_diff;
+ }
+ if(abs_plus_diff < abs_minus_diff)
+ {
+ return plus_diff;
+ }
+ return minus_diff;
+}
+
+void CubicSpline::solve_looped()
+{
+ if(check_minimum_size()) { return; }
+ size_t last= m_points.size();
+ vector results(m_points.size());
+ vector diagonals(m_points.size());
+ vector multiples;
+ solution_cache.solve_diagonals_looped(diagonals, multiples);
+ results[0]= 3 * loop_space_difference(
+ m_points[1].a, m_points[last-1].a, m_spatial_extent);
+ prep_inner(last, results);
+ results[last-1]= 3 * loop_space_difference(
+ m_points[0].a, m_points[last-2].a, m_spatial_extent);
+
+ // Steps explained in detail in SplineSolutionCache.
+ // Only the operations on the results column are performed here.
+ // Stage one.
+ // SplineSolutionCache's Stage one loop ends at last-2 because it has to
+ // handle right_column. This does not handle right_column, so the loop
+ // goes to last-1.
+ for(size_t i= 0; i < last-1; ++i)
+ {
+ // Operation: Add row[i] * -multiples[i] to row[i+1].
+ results[i+1]-= results[i] * multiples[i];
+ }
+ size_t next_mult= last-1;
+ // Stage two.
+ for(size_t i= last-2; i > 0; --i)
+ {
+ // Operation: Add row[i] * -multiples[nm] to row[i-1].
+ results[i-1]-= results[i] * multiples[next_mult];
+ ++next_mult;
+ }
+ // Last step of stage two.
+ // Operation: Add row[0] * -multiples[nm] to row[l-1].
+ results[last-1]-= results[0] * multiples[next_mult];
+ ++next_mult;
+ // Stage three.
+ const size_t end= last-1;
+ for(size_t i= 0; i < end; ++i)
+ {
+ // Operation: Add row[e] * -multiples[nm] to row[i].
+ results[i]-= results[end] * multiples[next_mult];
+ ++next_mult;
+ }
+ // Solving finished.
+ set_results(last, diagonals, results);
+}
+
+void CubicSpline::solve_straight()
+{
+ if(check_minimum_size()) { return; }
+ size_t last= m_points.size();
+ vector results(m_points.size());
+ vector diagonals(m_points.size());
+ vector multiples;
+ solution_cache.solve_diagonals_straight(diagonals, multiples);
+ results[0]= 3 * (m_points[1].a - m_points[0].a);
+ prep_inner(last, results);
+ results[last-1]= 3 * loop_space_difference(
+ m_points[last-1].a, m_points[last-2].a, m_spatial_extent);
+
+ // Steps explained in detail in SplineSolutionCache.
+ // Only the operations on the results column are performed here.
+ // Stage one.
+ for(size_t i= 0; i < last-1; ++i)
+ {
+ // Operation: Add row[i] * -multiples[i] to row[i+1].
+ results[i+1]-= results[i] * multiples[i];
+ }
+ size_t next_mult= last-1;
+ // Stage two.
+ for(size_t i= last-1; i > 0; --i)
+ {
+ // Operation: Add row[i] * -multiples[nm] to row [i-1].
+ results[i-1]-= results[i] * multiples[next_mult];
+ ++next_mult;
+ }
+ // Solving finished.
+ set_results(last, diagonals, results);
+}
+
+void CubicSpline::solve_polygonal()
+{
+ if(check_minimum_size()) { return; }
+ size_t last= m_points.size() - 1;
+ for(size_t i= 0; i < last; ++i)
+ {
+ m_points[i].b= loop_space_difference(
+ m_points[i+1].a, m_points[i].a, m_spatial_extent);
+ }
+ m_points[last].b= loop_space_difference(
+ m_points[0].a, m_points[last].a, m_spatial_extent);
+}
+
+bool CubicSpline::check_minimum_size()
+{
+ size_t last= m_points.size();
+ if(last < 2)
+ {
+ m_points[0].b= m_points[0].c= m_points[0].d= 0.0f;
+ return true;
+ }
+ if(last == 2)
+ {
+ m_points[0].b= loop_space_difference(
+ m_points[1].a, m_points[0].a, m_spatial_extent);
+ m_points[0].c= m_points[0].d= 0.0f;
+ // These will be used in the looping case.
+ m_points[1].b= loop_space_difference(
+ m_points[0].a, m_points[1].a, m_spatial_extent);
+ m_points[1].c= m_points[1].d= 0.0f;
+ return true;
+ }
+ float a= m_points[0].a;
+ bool all_points_identical= true;
+ for(size_t i= 0; i < m_points.size(); ++i)
+ {
+ m_points[i].b= m_points[i].c= m_points[i].d= 0.0f;
+ if(m_points[i].a != a) { all_points_identical= false; }
+ }
+ return all_points_identical;
+}
+
+void CubicSpline::prep_inner(size_t last, vector& results)
+{
+ for(size_t i= 1; i < last - 1; ++i)
+ {
+ results[i]= 3 * loop_space_difference(
+ m_points[i+1].a, m_points[i-1].a, m_spatial_extent);
+ }
+}
+
+void CubicSpline::set_results(size_t last, vector& diagonals, vector& results)
+{
+ // No more operations left, everything not a diagonal should be zero now.
+ for(size_t i= 0; i < last; ++i)
+ {
+ results[i]/= diagonals[i];
+ }
+ // Now we can go through and set the b, c, d values of each point.
+ // b, c, d values of the last point are not set because they are unused.
+ for(size_t i= 0; i < last; ++i)
+ {
+ size_t next= (i+1) % last;
+ float diff= loop_space_difference(
+ m_points[next].a, m_points[i].a, m_spatial_extent);
+ m_points[i].b= results[i];
+ m_points[i].c= (3 * diff) - (2 * results[i]) - results[next];
+ m_points[i].d= (2 * -diff) + results[i] + results[next];
+#define UNNAN(n) if(n != n) { n = 0.0f; }
+ UNNAN(m_points[i].b);
+ UNNAN(m_points[i].c);
+ UNNAN(m_points[i].d);
+#undef UNNAN
+ }
+ // Solving is now complete.
+}
+
+void CubicSpline::p_and_tfrac_from_t(float t, bool loop, size_t& p, float& tfrac) const
+{
+ if(loop)
+ {
+ float max_t= static_cast(m_points.size());
+ while(t >= max_t) { t-= max_t; }
+ while(t < 0.0f) { t+= max_t; }
+ p= static_cast(t);
+ tfrac= t - static_cast(p);
+ }
+ else
+ {
+ int flort= static_cast(t);
+ if(flort < 0)
+ {
+ p= 0;
+ tfrac= 0;
+ }
+ else if(static_cast(flort) >= m_points.size() - 1)
+ {
+ p= m_points.size() - 1;
+ tfrac= 0;
+ }
+ else
+ {
+ p= static_cast(flort);
+ tfrac= t - static_cast(p);
+ }
+ }
+}
+
+#define RETURN_IF_EMPTY if(m_points.empty()) { return 0.0f; }
+#define DECLARE_P_AND_TFRAC \
+size_t p= 0; float tfrac= 0.0f; \
+p_and_tfrac_from_t(t, loop, p, tfrac);
+
+float CubicSpline::evaluate(float t, bool loop) const
+{
+ RETURN_IF_EMPTY;
+ DECLARE_P_AND_TFRAC;
+ float tsq= tfrac * tfrac;
+ float tcub= tsq * tfrac;
+ return m_points[p].a + (m_points[p].b * tfrac) +
+ (m_points[p].c * tsq) + (m_points[p].d * tcub);
+}
+
+float CubicSpline::evaluate_derivative(float t, bool loop) const
+{
+ RETURN_IF_EMPTY;
+ DECLARE_P_AND_TFRAC;
+ float tsq= tfrac * tfrac;
+ return m_points[p].b + (2.0f * m_points[p].c * tfrac) +
+ (3.0f * m_points[p].d * tsq);
+}
+
+float CubicSpline::evaluate_second_derivative(float t, bool loop) const
+{
+ RETURN_IF_EMPTY;
+ DECLARE_P_AND_TFRAC;
+ return (2.0f * m_points[p].c) + (6.0f * m_points[p].d * tfrac);
+}
+
+float CubicSpline::evaluate_third_derivative(float t, bool loop) const
+{
+ RETURN_IF_EMPTY;
+ DECLARE_P_AND_TFRAC;
+ return 6.0f * m_points[p].d;
+}
+
+#undef RETURN_IF_EMPTY
+#undef DECLARE_P_AND_TFRAC
+
+void CubicSpline::set_point(size_t i, float v)
+{
+ ASSERT_M(i < m_points.size(), "CubicSpline::set_point requires the index to be less than the number of points.");
+ 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) const
+{
+ 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::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);
+}
+
+size_t CubicSpline::size() const
+{
+ return m_points.size();
+}
+
+bool CubicSpline::empty() const
+{
+ return m_points.empty();
+}
+
+void CubicSplineN::weighted_average(CubicSplineN& out,
+ const CubicSplineN& 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.
+ const size_t from_size= from.size();
+ const size_t to_size= to.size();
+ size_t out_size= to_size;
+ size_t limit= to_size;
+ if(from_size < to_size)
+ {
+ 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; }
+#define SOLVE_LOOP(solvent) \
+ for(spline_cont_t::iterator spline= m_splines.begin(); \
+ spline != m_splines.end(); ++spline) \
+ { \
+ spline->solvent(); \
+ }
+ if(m_polygonal)
+ {
+ SOLVE_LOOP(solve_polygonal);
+ }
+ else
+ {
+ if(m_loop)
+ {
+ SOLVE_LOOP(solve_looped);
+ }
+ else
+ {
+ SOLVE_LOOP(solve_straight);
+ }
+ }
+#undef SOLVE_LOOP
+ m_dirty= false;
+}
+
+#define CSN_EVAL_SOMETHING(something) \
+void CubicSplineN::something(float t, vector& v) const \
+{ \
+ for(spline_cont_t::const_iterator spline= m_splines.begin(); \
+ spline != m_splines.end(); ++spline) \
+ { \
+ v.push_back(spline->something(t, m_loop)); \
+ } \
+}
+
+CSN_EVAL_SOMETHING(evaluate);
+CSN_EVAL_SOMETHING(evaluate_derivative);
+CSN_EVAL_SOMETHING(evaluate_second_derivative);
+CSN_EVAL_SOMETHING(evaluate_third_derivative);
+
+#undef CSN_EVAL_SOMETHING
+
+void CubicSplineN::set_point(size_t i, const vector& v)
+{
+ ASSERT_M(v.size() == m_splines.size(), "CubicSplineN::set_point requires the passed point to be the same dimension as the spline.");
+ for(size_t n= 0; n < m_splines.size(); ++n)
+ {
+ m_splines[n].set_point(i, v[n]);
+ }
+ m_dirty= true;
+}
+
+void CubicSplineN::set_coefficients(size_t i, const vector& b,
+ const vector& c, const vector& d)
+{
+ ASSERT_M(b.size() == c.size() && c.size() == d.size() &&
+ d.size() == m_splines.size(), "CubicSplineN: coefficient vectors must be "
+ "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]);
+ }
+ m_dirty= true;
+}
+
+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::set_spatial_extent(size_t i, float extent)
+{
+ ASSERT_M(i < m_splines.size(), "CubicSplineN: index of spline to set extent"
+ " of is out of range.");
+ m_splines[i].m_spatial_extent= extent;
+ m_dirty= true;
+}
+
+float CubicSplineN::get_spatial_extent(size_t i)
+{
+ ASSERT_M(i < m_splines.size(), "CubicSplineN: index of spline to get extent"
+ " of is out of range.");
+ return m_splines[i].m_spatial_extent;
+}
+
+void CubicSplineN::resize(size_t s)
+{
+ for(spline_cont_t::iterator spline= m_splines.begin();
+ spline != m_splines.end(); ++spline)
+ {
+ spline->resize(s);
+ }
+ m_dirty= true;
+}
+
+size_t CubicSplineN::size() const
+{
+ if(!m_splines.empty())
+ {
+ return m_splines[0].size();
+ }
+ return 0;
+}
+
+bool CubicSplineN::empty() const
+{
+ return m_splines.empty() || m_splines[0].empty();
+}
+
+void CubicSplineN::redimension(size_t d)
+{
+ m_splines.resize(d);
+ m_dirty= true;
+}
+
+size_t CubicSplineN::dimension() const
+{
+ return m_splines.size();
+}
+
+// m_dirty is set before the member so that the set_dirty that is created
+// can actually be used to set the dirty flag. -Kyz
+#define SET_GET_MEM(member, name) \
+void CubicSplineN::set_##name(bool b) \
+{ \
+ m_dirty= true; \
+ member= b; \
+} \
+bool CubicSplineN::get_##name() const \
+{ \
+ return member; \
+}
+
+SET_GET_MEM(m_loop, loop);
+SET_GET_MEM(m_polygonal, polygonal);
+SET_GET_MEM(m_dirty, dirty);
+
+#undef SET_GET_MEM
+
+#include "LuaBinding.h"
+
+struct LunaCubicSplineN : Luna
+{
+ static size_t dimension_index(T* p, lua_State* L, int s)
+ {
+ size_t i= static_cast(IArg(s)-1);
+ if(i >= p->dimension())
+ {
+ luaL_error(L, "Spline dimension index out of range.");
+ }
+ return i;
+ }
+ static size_t point_index(T* p, lua_State* L, int s)
+ {
+ size_t i= static_cast(IArg(s)-1);
+ if(i >= p->size())
+ {
+ luaL_error(L, "Spline point index out of range.");
+ }
+ return i;
+ }
+ static int solve(T* p, lua_State* L)
+ {
+ p->solve();
+ COMMON_RETURN_SELF;
+ }
+#define LCSN_EVAL_SOMETHING(something) \
+ static int something(T* p, lua_State* L) \
+ { \
+ vector pos; \
+ p->something(FArg(1), pos); \
+ lua_createtable(L, pos.size(), 0); \
+ for(size_t i= 0; i < pos.size(); ++i) \
+ { \
+ lua_pushnumber(L, pos[i]); \
+ lua_rawseti(L, -2, i+1); \
+ } \
+ return 1; \
+ }
+ LCSN_EVAL_SOMETHING(evaluate);
+ LCSN_EVAL_SOMETHING(evaluate_derivative);
+ LCSN_EVAL_SOMETHING(evaluate_second_derivative);
+ LCSN_EVAL_SOMETHING(evaluate_third_derivative);
+#undef LCSN_EVAL_SOMETHING
+
+ static void get_element_table_from_stack(T* p, lua_State* L, int s,
+ size_t limit, vector& ret)
+ {
+ 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);
+ }
+ ret.resize(limit);
+ }
+ 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.");
+ }
+ vector pos;
+ 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)
+ {
+ size_t i= point_index(p, L, 1);
+ set_point_from_stack(p, L, i, 2);
+ COMMON_RETURN_SELF;
+ }
+ static void set_coefficients_from_stack(T* p, lua_State* L, size_t i, int s)
+ {
+ 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)
+ {
+ size_t i= point_index(p, L, 1);
+ set_coefficients_from_stack(p, L, i, 2);
+ COMMON_RETURN_SELF;
+ }
+ static int get_coefficients(T* p, lua_State* L)
+ {
+ size_t i= point_index(p, L, 1);
+ 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 set_spatial_extent(T* p, lua_State* L)
+ {
+ size_t i= dimension_index(p, L, 1);
+ p->set_spatial_extent(i, FArg(2));
+ COMMON_RETURN_SELF;
+ }
+ static int get_spatial_extent(T* p, lua_State* L)
+ {
+ size_t i= dimension_index(p, L, 1);
+ lua_pushnumber(L, p->get_spatial_extent(i));
+ return 1;
+ }
+ static int get_max_t(T* p, lua_State* L)
+ {
+ lua_pushnumber(L, p->get_max_t());
+ return 1;
+ }
+ static int set_size(T* p, lua_State* L)
+ {
+ int siz= IArg(1);
+ if(siz < 0)
+ {
+ luaL_error(L, "A spline cannot have less than 0 points.");
+ }
+ p->resize(static_cast(siz));
+ COMMON_RETURN_SELF;
+ }
+ static int get_size(T* p, lua_State* L)
+ {
+ lua_pushnumber(L, p->size());
+ return 1;
+ }
+ static int set_dimension(T* p, lua_State* L)
+ {
+ if(p->m_owned_by_actor)
+ {
+ luaL_error(L, "This spline cannot be redimensioned because it is "
+ "owned by an actor that relies on it having fixed dimensions.");
+ }
+ int dim= IArg(1);
+ if(dim < 0)
+ {
+ luaL_error(L, "A spline cannot have less than 0 dimensions.");
+ }
+ p->redimension(static_cast(dim));
+ COMMON_RETURN_SELF;
+ }
+ static int get_dimension(T* p, lua_State* L)
+ {
+ lua_pushnumber(L, p->dimension());
+ return 1;
+ }
+ static int empty(T* p, lua_State* L)
+ {
+ lua_pushboolean(L, p->empty());
+ return 1;
+ }
+#define SET_GET_LUA(name) \
+ static int set_##name(T* p, lua_State* L) \
+ { \
+ p->set_##name(lua_toboolean(L, 1)); \
+ COMMON_RETURN_SELF; \
+ } \
+ static int get_##name(T* p, lua_State* L) \
+ { \
+ lua_pushboolean(L, p->get_##name()); \
+ return 1; \
+ }
+ SET_GET_LUA(loop);
+ SET_GET_LUA(polygonal);
+ SET_GET_LUA(dirty);
+#undef SET_GET_LUA
+ static int destroy(T* p, lua_State* L)
+ {
+ if(p->m_owned_by_actor)
+ {
+ luaL_error(L, "This spline cannot be destroyed because it is "
+ "owned by an actor that relies on it existing.");
+ }
+ SAFE_DELETE(p);
+ return 0;
+ }
+ LunaCubicSplineN()
+ {
+ ADD_METHOD(solve);
+ ADD_METHOD(evaluate);
+ ADD_METHOD(evaluate_derivative);
+ ADD_METHOD(evaluate_second_derivative);
+ ADD_METHOD(evaluate_third_derivative);
+ ADD_METHOD(set_point);
+ ADD_METHOD(set_coefficients);
+ ADD_METHOD(get_coefficients);
+ ADD_METHOD(set_spatial_extent);
+ ADD_METHOD(get_spatial_extent);
+ ADD_METHOD(get_max_t);
+ ADD_METHOD(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);
+ ADD_METHOD(set_polygonal);
+ ADD_METHOD(get_polygonal);
+ ADD_METHOD(set_dirty);
+ ADD_METHOD(get_dirty);
+ ADD_METHOD(destroy);
+ }
+};
+LUA_REGISTER_CLASS(CubicSplineN);
+
+int LuaFunc_create_spline(lua_State* L);
+int LuaFunc_create_spline(lua_State* L)
+{
+ CubicSplineN* spline= new CubicSplineN;
+ spline->PushSelf(L);
+ return 1;
+}
+LUAFUNC_REGISTER_COMMON(create_spline);
+
+// Side note: Actually written between 2014/12/26 and 2014/12/28
+/*
+ * Copyright (c) 2014-2015 Eric Reese
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, and/or sell copies of the Software, and to permit persons to
+ * whom the Software is furnished to do so, provided that the above
+ * copyright notice(s) and this permission notice appear in all copies of
+ * the Software and that both the above copyright notice(s) and this
+ * permission notice appear in supporting documentation.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
+ * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
+ * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
+ * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
diff --git a/src/CubicSpline.h b/src/CubicSpline.h
new file mode 100644
index 0000000000..aae6e702e9
--- /dev/null
+++ b/src/CubicSpline.h
@@ -0,0 +1,111 @@
+#ifndef CUBIC_SPLINE_H
+#define CUBIC_SPLINE_H
+
+#include
+using std::vector;
+struct lua_State;
+
+struct CubicSpline
+{
+CubicSpline() :m_spatial_extent(0.0f) {}
+ void solve_looped();
+ void solve_straight();
+ void solve_polygonal();
+ void p_and_tfrac_from_t(float t, bool loop, size_t& p, float& tfrac) const;
+ float evaluate(float t, bool loop) const;
+ float evaluate_derivative(float t, bool loop) const;
+ float evaluate_second_derivative(float t, bool loop) const;
+ float evaluate_third_derivative(float t, bool loop) const;
+ void set_point(size_t i, float v);
+ void set_coefficients(size_t i, float b, float c, float d);
+ void get_coefficients(size_t i, float& b, float& c, float& d) 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;
+ float m_spatial_extent;
+private:
+ bool check_minimum_size();
+ void prep_inner(size_t last, vector& results);
+ void set_results(size_t last, vector& diagonals, vector& results);
+
+ struct SplinePoint
+ {
+ float a, b, c, d;
+ };
+ vector m_points;
+};
+
+struct CubicSplineN
+{
+ CubicSplineN()
+ :m_owned_by_actor(false), m_loop(false), m_polygonal(false), m_dirty(true)
+ {}
+ static void weighted_average(CubicSplineN& out, const CubicSplineN& from,
+ const CubicSplineN& to, float between);
+ void solve();
+ void evaluate(float t, vector& v) const;
+ void evaluate_derivative(float t, vector& v) const;
+ void evaluate_second_derivative(float t, vector& v) const;
+ void evaluate_third_derivative(float t, vector& v) const;
+ void set_point(size_t i, const vector& v);
+ void set_coefficients(size_t i, const vector& b,
+ const vector& c, const vector& d);
+ void get_coefficients(size_t i, vector& b,
+ vector& c, vector& d);
+ void set_spatial_extent(size_t i, float extent);
+ float get_spatial_extent(size_t i);
+ void resize(size_t s);
+ size_t size() const;
+ void redimension(size_t d);
+ size_t dimension() const;
+ bool empty() const;
+ float get_max_t() const {
+ if(m_loop) { return static_cast(size()); }
+ else { return static_cast(size()-1); }
+ }
+ typedef vector spline_cont_t;
+ void set_loop(bool l);
+ bool get_loop() const;
+ void set_polygonal(bool p);
+ bool get_polygonal() const;
+ void set_dirty(bool d);
+ bool get_dirty() const;
+ bool m_owned_by_actor;
+
+ void PushSelf(lua_State* L);
+private:
+ bool m_loop;
+ bool m_polygonal;
+ bool m_dirty;
+ spline_cont_t m_splines;
+};
+
+#endif
+
+// Side note: Actually written between 2014/12/26 and 2014/12/28
+/*
+ * Copyright (c) 2014-2015 Eric Reese
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, and/or sell copies of the Software, and to permit persons to
+ * whom the Software is furnished to do so, provided that the above
+ * copyright notice(s) and this permission notice appear in all copies of
+ * the Software and that both the above copyright notice(s) and this
+ * permission notice appear in supporting documentation.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
+ * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
+ * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
+ * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
diff --git a/src/GhostArrowRow.cpp b/src/GhostArrowRow.cpp
index fef07474e7..65535363d9 100644
--- a/src/GhostArrowRow.cpp
+++ b/src/GhostArrowRow.cpp
@@ -35,6 +35,16 @@ void GhostArrowRow::Load( const PlayerState* pPlayerState, float fYReverseOffset
}
}
+void GhostArrowRow::SetColumnRenderers(vector& renderers)
+{
+ ASSERT_M(renderers.size() == m_Ghost.size(), "Notefield has different number of columns than ghost row.");
+ for(size_t c= 0; c < m_Ghost.size(); ++c)
+ {
+ m_Ghost[c]->SetFakeParent(&(renderers[c]));
+ }
+ m_renderers= &renderers;
+}
+
GhostArrowRow::~GhostArrowRow()
{
for( unsigned i = 0; i < m_Ghost.size(); ++i )
@@ -47,20 +57,7 @@ void GhostArrowRow::Update( float fDeltaTime )
for( unsigned c=0; cUpdate( fDeltaTime );
-
- float fX = ArrowEffects::GetXPos( m_pPlayerState, c, 0 );
- float fY = ArrowEffects::GetYPos( m_pPlayerState, c, 0, m_fYReverseOffsetPixels );
- float fZ = ArrowEffects::GetZPos( m_pPlayerState, c, 0 );
-
- m_Ghost[c]->SetX( fX );
- m_Ghost[c]->SetY( fY );
- m_Ghost[c]->SetZ( fZ );
-
- 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/GhostArrowRow.h b/src/GhostArrowRow.h
index b1647d3aa9..d2dc96e1b3 100644
--- a/src/GhostArrowRow.h
+++ b/src/GhostArrowRow.h
@@ -4,6 +4,7 @@
#include "ActorFrame.h"
#include "GameConstantsAndTypes.h"
#include "NoteTypes.h"
+#include "NoteDisplay.h"
class PlayerState;
/** @brief Row of GhostArrow Actors. */
@@ -15,6 +16,7 @@ public:
virtual void DrawPrimitives();
void Load( const PlayerState* pPlayerState, float fYReverseOffset );
+ void SetColumnRenderers(vector& renderers);
void DidTapNote( int iCol, TapNoteScore tns, bool bBright );
void DidHoldNote( int iCol, HoldNoteScore hns, bool bBright );
@@ -24,6 +26,7 @@ protected:
float m_fYReverseOffsetPixels;
const PlayerState* m_pPlayerState;
+ vector const* m_renderers;
vector m_Ghost;
vector m_bHoldShowing;
vector m_bLastHoldShowing;
diff --git a/src/Makefile.am b/src/Makefile.am
index 19ac2d4d8a..332de6ce02 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -516,6 +516,7 @@ RageFileDriverSlice.cpp RageFileDriverSlice.h \
RageFileDriverTimeout.cpp RageFileDriverTimeout.h
Rage = $(PCRE) $(Lua) $(jsoncpp) $(RageFile) $(RageSoundFileReaders) \
+CubicSpline.cpp CubicSpline.h \
RageBitmapTexture.cpp RageBitmapTexture.h \
RageDisplay.cpp RageDisplay.h \
RageDisplay_OGL.cpp RageDisplay_OGL.h \
diff --git a/src/NoteDisplay.cpp b/src/NoteDisplay.cpp
index d7606f5ed9..22718ad273 100644
--- a/src/NoteDisplay.cpp
+++ b/src/NoteDisplay.cpp
@@ -1,10 +1,13 @@
#include "global.h"
#include "NoteDisplay.h"
#include "GameState.h"
+#include "GhostArrowRow.h"
+#include "NoteData.h"
#include "NoteSkinManager.h"
#include "ArrowEffects.h"
#include "RageLog.h"
#include "RageDisplay.h"
+#include "ReceptorArrowRow.h"
#include "ActorUtil.h"
#include "Style.h"
#include "PlayerState.h"
@@ -14,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 )
@@ -40,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;
@@ -274,6 +289,120 @@ 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 NCSplineHandler::MakeWeightedAverage(NCSplineHandler& out,
+ const NCSplineHandler& from, const NCSplineHandler& 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(const PlayerState* 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(const PlayerState* 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,
+ const vector& sp_pos, const vector& ae_pos,
+ const vector& sp_rot, const vector& ae_rot,
+ const vector& sp_zoom, const vector& ae_zoom) const
+{
+ actor->SetX(sp_pos[0] + ae_pos[0]);
+ actor->SetY(sp_pos[1] + ae_pos[1]);
+ 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;
@@ -317,6 +446,187 @@ void NoteDisplay::Load( int iColNum, const PlayerState* pPlayerState, float fYRe
}
}
+inline float NoteRowToVisibleBeat( const PlayerState *pPlayerState, int iRow )
+{
+ return NoteRowToBeat(iRow);
+}
+
+bool NoteDisplay::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const
+{
+ // IMPORTANT: Do not modify this function without also modifying the
+ // version that is in NoteField.cpp or coming up with a good way to
+ // merge them. -Kyz
+ // TRICKY: If boomerang is on, then ones in the range
+ // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible.
+ // Test to see if this beat is visible before drawing.
+ float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fBeat );
+ if( fYOffset > iDrawDistanceBeforeTargetsPixels ) // off screen
+ return false;
+ if( fYOffset < iDrawDistanceAfterTargetsPixels ) // off screen
+ return false;
+
+ return true;
+}
+
+bool NoteDisplay::DrawHoldsInRange(const NoteFieldRenderArgs& field_args,
+ const NoteColumnRenderArgs& column_args,
+ const vector& tap_set)
+{
+ bool any_upcoming = false;
+ for(vector::const_iterator tapit=
+ tap_set.begin(); tapit != tap_set.end(); ++tapit)
+ {
+ const TapNote& tn= (*tapit)->second;
+ const HoldNoteResult& result= tn.HoldResult;
+ int start_row= (*tapit)->first;
+ int end_row = start_row + tn.iDuration;
+
+ // TRICKY: If boomerang is on, then all notes in the range
+ // [first_row,last_row] aren't necessarily visible.
+ // Test every note to make sure it's on screen before drawing
+ float throw_away;
+ bool start_past_peak = false;
+ bool end_past_peak = false;
+ float start_y = ArrowEffects::GetYOffset(m_pPlayerState, column_args.column,
+ NoteRowToVisibleBeat(m_pPlayerState, start_row), throw_away,
+ start_past_peak);
+ float end_y = ArrowEffects::GetYOffset(m_pPlayerState, column_args.column,
+ NoteRowToVisibleBeat(m_pPlayerState, end_row), throw_away,
+ end_past_peak);
+ 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))
+ {
+ //LOG->Trace( "skip drawing this hold." );
+ continue; // skip
+ }
+
+ bool is_addition = (tn.source == TapNoteSource_Addition);
+ bool hopo_possible = (tn.bHopoPossible);
+ bool use_addition_coloring = is_addition || hopo_possible;
+ const bool hold_ghost_showing = tn.HoldResult.bActive && tn.HoldResult.fLife > 0;
+ const bool is_holding = tn.HoldResult.bHeld;
+ if(hold_ghost_showing)
+ {
+ field_args.ghost_row->SetHoldShowing(column_args.column, tn);
+ }
+
+ ASSERT_M(NoteRowToBeat(start_row) > -2000, ssprintf("%i %i %i", start_row, end_row, column_args.column));
+
+ bool in_selection_range = false;
+ if(*field_args.selection_begin_marker != -1 && *field_args.selection_end_marker != -1)
+ {
+ in_selection_range = (*field_args.selection_begin_marker <= start_row &&
+ end_row < *field_args.selection_end_marker);
+ }
+
+ DrawHold(tn, field_args, column_args, start_row, is_holding, result,
+ use_addition_coloring,
+ in_selection_range ? field_args.selection_glow : field_args.fail_fade);
+
+ bool note_upcoming = NoteRowToBeat(start_row) >
+ m_pPlayerState->GetDisplayedPosition().m_fSongBeat;
+ any_upcoming |= note_upcoming;
+ }
+ return any_upcoming;
+}
+
+bool NoteDisplay::DrawTapsInRange(const NoteFieldRenderArgs& field_args,
+ const NoteColumnRenderArgs& column_args,
+ const vector& tap_set)
+{
+ bool any_upcoming= false;
+ // draw notes from furthest to closest
+ for(vector::const_iterator tapit=
+ tap_set.begin(); tapit != tap_set.end(); ++tapit)
+ {
+ int tap_row= (*tapit)->first;
+ const TapNote& tn= (*tapit)->second;
+
+ // TRICKY: If boomerang is on, then all notes in the range
+ // [first_row,last_row] aren't necessarily visible.
+ // Test every note to make sure it's on screen before drawing.
+ if(!IsOnScreen(NoteRowToBeat(tap_row), column_args.column,
+ field_args.draw_pixels_after_targets, field_args.draw_pixels_before_targets))
+ {
+ continue; // skip
+ }
+
+ // Hm, this assert used to pass the first and last rows to draw, when it
+ // was in NoteField, but those aren't available here.
+ // Well, anyone who has to investigate hitting it can use a debugger to
+ // discover the values, hopefully. -Kyz
+ ASSERT_M(NoteRowToBeat(tap_row) > -2000,
+ ssprintf("Invalid tap_row: %i, %f %f",
+ tap_row,
+ m_pPlayerState->GetDisplayedPosition().m_fSongBeat,
+ m_pPlayerState->GetDisplayedPosition().m_fMusicSeconds));
+
+ // See if there is a hold step that begins on this index.
+ // Only do this if the noteskin cares.
+ bool hold_begins_on_this_beat = false;
+ if(DrawHoldHeadForTapsOnSameRow())
+ {
+ for(int c2= 0; c2 < field_args.note_data->GetNumTracks(); ++c2)
+ {
+ const TapNote &tmp = field_args.note_data->GetTapNote(c2, tap_row);
+ if(tmp.type == TapNoteType_HoldHead &&
+ tmp.subType == TapNoteSubType_Hold)
+ {
+ hold_begins_on_this_beat = true;
+ break;
+ }
+ }
+ }
+
+ // do the same for a roll.
+ bool roll_begins_on_this_beat = false;
+ if(DrawRollHeadForTapsOnSameRow())
+ {
+ for(int c2= 0; c2 < field_args.note_data->GetNumTracks(); ++c2)
+ {
+ const TapNote &tmp = field_args.note_data->GetTapNote(c2, tap_row);
+ if(tmp.type == TapNoteType_HoldHead &&
+ tmp.subType == TapNoteSubType_Roll)
+ {
+ roll_begins_on_this_beat = true;
+ break;
+ }
+ }
+ }
+
+ bool in_selection_range = false;
+ if(*field_args.selection_begin_marker != -1 && *field_args.selection_end_marker != -1)
+ {
+ 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, 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 ? field_args.selection_glow : field_args.fail_fade);
+
+ any_upcoming |= NoteRowToBeat(tap_row) >
+ m_pPlayerState->GetDisplayedPosition().m_fSongBeat;
+
+ if(!PREFSMAN->m_FastNoteRendering)
+ {
+ DISPLAY->ClearZBuffer();
+ }
+ }
+ return any_upcoming;
+}
+
bool NoteDisplay::DrawHoldHeadForTapsOnSameRow() const
{
return cache->m_bDrawHoldHeadForTapsOnSameRow;
@@ -425,29 +735,25 @@ struct StripBuffer
int Free() const { return size - Used(); }
};
-void NoteDisplay::DrawHoldPart( vector &vpSpr, int iCol, int fYStep, float fPercentFadeToFail, float fColorScale, bool bGlow,
- float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar,
- float fOverlappedTime,
- float fYTop, float fYBottom,
- float fYStartPos, float fYEndPos,
- bool bWrapping, bool bAnchorToTop, bool bFlipTextureVertically )
+void NoteDisplay::DrawHoldPart(vector &vpSpr,
+ const NoteFieldRenderArgs& field_args,
+ const NoteColumnRenderArgs& column_args, int fYStep,
+ float fPercentFadeToFail, float fColorScale, bool bGlow,
+ float fOverlappedTime, float fYTop, float fYBottom, float fYStartPos,
+ float fYEndPos, bool bWrapping, bool bAnchorToTop,
+ 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
@@ -486,6 +792,10 @@ void NoteDisplay::DrawHoldPart( vector &vpSpr, int iCol, int fYStep, fl
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 const RageVector3 pos_z_vec(0.0f, 0.0f, 1.0f);
+ static const RageVector3 pos_y_vec(0.0f, 1.0f, 0.0f);
+
StripBuffer queue;
for( float fY = fYStartPos; !bLast; fY += fYStep )
{
@@ -495,42 +805,165 @@ void NoteDisplay::DrawHoldPart( vector &vpSpr, int iCol, int fYStep, fl
bLast = true;
}
- const float fYOffset = ArrowEffects::GetYOffsetFromYPos( m_pPlayerState, iCol, fY, m_fYReverseOffsetPixels );
- const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset );
+ const float fYOffset = ArrowEffects::GetYOffsetFromYPos( m_pPlayerState, column_args.column, fY, m_fYReverseOffsetPixels );
+
+ float cur_beat= top_beat;
+ if(top_beat != bottom_beat)
+ {
+ 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;
+ }
+
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 );
+ // 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;
+
+ const RageVector3 left_vert(center_vert.x + render_left.x,
+ center_vert.y + render_left.y, center_vert.z + render_left.z);
+ const RageVector3 right_vert(center_vert.x - render_left.x,
+ center_vert.y - render_left.y, center_vert.z - render_left.z);
const float fDistFromTop = fY - fYTop;
float fTexCoordTop = SCALE( fDistFromTop, 0, fFrameHeight, rect.top, rect.bottom );
fTexCoordTop += fAddToTexCoord;
- const float fAlpha = ArrowGetAlphaOrGlow( bGlow, m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar );
+ const float fAlpha = ArrowGetAlphaOrGlow( bGlow, m_pPlayerState, 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, fZLeft); queue.v[0].c = color; queue.v[0].t = RageVector2(fTexCoordLeft, fTexCoordTop);
- queue.v[1].p = RageVector3(fXCenter, fY, fZCenter); queue.v[1].c = color; queue.v[1].t = RageVector2(fTexCoordCenter, fTexCoordTop);
- queue.v[2].p = RageVector3(fXRight, fY, fZRight); queue.v[2].c = color; queue.v[2].t = RageVector2(fTexCoordRight, fTexCoordTop);
+ queue.v[0].p = 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 )
@@ -556,8 +989,12 @@ void NoteDisplay::DrawHoldPart( vector &vpSpr, int iCol, int fYStep, fl
}
}
-void NoteDisplay::DrawHoldBody( const TapNote& tn, int iCol, float fBeat, bool bIsBeingHeld, float fYHead, float fYTail, bool /* bIsAddition */, float fPercentFadeToFail, float fColorScale, bool bGlow,
- float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar )
+void NoteDisplay::DrawHoldBody(const TapNote& tn,
+ const NoteFieldRenderArgs& field_args,
+ const NoteColumnRenderArgs& column_args, float fBeat,
+ bool bIsBeingHeld, float fYHead, float fYTail, bool bIsAddition,
+ float fPercentFadeToFail, float fColorScale, bool bGlow,
+ float top_beat, float bottom_beat)
{
vector vpSprTop;
Sprite *pSpriteTop = GetHoldSprite( m_HoldTopCap, NotePart_HoldTopCap, fBeat, tn.subType == TapNoteSubType_Roll, bIsBeingHeld && !cache->m_bHoldActiveIsAddLayer );
@@ -581,7 +1018,7 @@ void NoteDisplay::DrawHoldBody( const TapNote& tn, int iCol, float fBeat, bool b
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 )
{
@@ -615,52 +1052,56 @@ void NoteDisplay::DrawHoldBody( const TapNote& tn, int iCol, float fBeat, bool b
const float fFrameHeightTop = pSpriteTop->GetUnzoomedHeight();
const float fFrameHeightBottom = pSpriteBottom->GetUnzoomedHeight();
- float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fDrawDistanceAfterTargetsPixels, m_fYReverseOffsetPixels );
- float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, iCol, fDrawDistanceBeforeTargetsPixels, m_fYReverseOffsetPixels );
+ float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, 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,
- iCol, fYStep, fPercentFadeToFail, fColorScale, bGlow,
- fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar,
+ DrawHoldPart(vpSprTop, field_args, column_args, fYStep, fPercentFadeToFail,
+ fColorScale, bGlow,
tn.HoldResult.fOverlappedTime,
fYHead-fFrameHeightTop, fYHead,
fYStartPos, fYEndPos,
- false, bTopAnchor, bFlipHoldBody );
+ false, bTopAnchor, bFlipHoldBody, top_beat, top_beat);
// Draw the body
- DrawHoldPart(
- vpSprBody,
- iCol, fYStep, fPercentFadeToFail, fColorScale, bGlow,
- fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar,
+ DrawHoldPart(vpSprBody, field_args, column_args, fYStep, fPercentFadeToFail,
+ fColorScale, bGlow,
tn.HoldResult.fOverlappedTime,
fYHead, fYTail,
fYStartPos, fYEndPos,
- true, bTopAnchor, bFlipHoldBody );
+ true, bTopAnchor, bFlipHoldBody, top_beat, bottom_beat);
// Draw the bottom cap
- DrawHoldPart(
- vpSprBottom,
- iCol, fYStep, fPercentFadeToFail, fColorScale, bGlow,
- fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar,
+ DrawHoldPart(vpSprBottom, field_args, column_args, fYStep, fPercentFadeToFail,
+ fColorScale, bGlow,
tn.HoldResult.fOverlappedTime,
fYTail, fYTail+fFrameHeightBottom,
max(fYStartPos, fYHead), fYEndPos,
- false, bTopAnchor, bFlipHoldBody );
+ false, bTopAnchor, bFlipHoldBody, bottom_beat, bottom_beat);
}
-void NoteDisplay::DrawHold( const TapNote &tn, int iCol, int iRow, bool bIsBeingHeld, const HoldNoteResult &Result, bool bIsAddition, float fPercentFadeToFail,
- float fReverseOffsetPixels, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fDrawDistanceBeforeTargetsPixels2, float fFadeInPercentOfDrawFar )
+void NoteDisplay::DrawHold(const TapNote& tn,
+ const NoteFieldRenderArgs& field_args,
+ const NoteColumnRenderArgs& column_args, int iRow, bool bIsBeingHeld,
+ const HoldNoteResult &Result, bool bIsAddition, float fPercentFadeToFail)
{
int iEndRow = iRow + tn.iDuration;
+ float top_beat= NoteRowToVisibleBeat(m_pPlayerState, iRow);
+ float bottom_beat= NoteRowToVisibleBeat(m_pPlayerState, iEndRow);
+ if(bIsBeingHeld)
+ {
+ 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;
@@ -670,11 +1111,11 @@ void NoteDisplay::DrawHold( const TapNote &tn, int iCol, int iRow, bool bIsBeing
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
@@ -687,8 +1128,8 @@ void NoteDisplay::DrawHold( const TapNote &tn, int iCol, int iRow, bool bIsBeing
if( bReverse )
swap( fStartYOffset, fEndYOffset );
- const float fYHead = ArrowEffects::GetYPos( m_pPlayerState, iCol, fStartYOffset, fReverseOffsetPixels );
- const float fYTail = ArrowEffects::GetYPos( m_pPlayerState, iCol, fEndYOffset, fReverseOffsetPixels );
+ const float fYHead = ArrowEffects::GetYPos( m_pPlayerState, 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 );
@@ -697,6 +1138,9 @@ void NoteDisplay::DrawHold( const TapNote &tn, int iCol, int iRow, bool bIsBeing
/* 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 )
{
@@ -710,8 +1154,8 @@ void NoteDisplay::DrawHold( const TapNote &tn, int iCol, int iRow, bool bIsBeing
}
*/
- DrawHoldBody( tn, iCol, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, false, fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar );
- DrawHoldBody( tn, iCol, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, true, fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar );
+ DrawHoldBody(tn, 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
@@ -719,48 +1163,76 @@ void NoteDisplay::DrawHold( const TapNote &tn, int iCol, int iRow, bool bIsBeing
if( cache->m_bHoldTailIsAboveWavyParts )
{
Actor *pActor = GetHoldActor( m_HoldTail, NotePart_HoldTail, NoteRowToBeat(iRow), tn.subType == TapNoteSubType_Roll, bIsBeingHeld );
- DrawActor( tn, pActor, NotePart_HoldTail, iCol, bFlipHeadAndTail ? fStartYOffset : fEndYOffset, fBeat, bIsAddition, fPercentFadeToFail, fReverseOffsetPixels, fColorScale, fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar );
+ DrawActor(tn, pActor, NotePart_HoldTail, 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, iCol, bFlipHeadAndTail ? fEndYOffset : fStartYOffset, fBeat, bIsAddition, fPercentFadeToFail, fReverseOffsetPixels, fColorScale, fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar );
+ 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, int iCol, float fYOffset, float fBeat, bool bIsAddition, float fPercentFadeToFail, float fReverseOffsetPixels, float fColorScale, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar )
+void NoteDisplay::DrawActor(const TapNote& tn, Actor* pActor, NotePart part,
+ const NoteFieldRenderArgs& field_args, const NoteColumnRenderArgs& 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 < fDrawDistanceAfterTargetsPixels || fYOffset > fDrawDistanceBeforeTargetsPixels )
+ if(fYOffset < field_args.draw_pixels_after_targets ||
+ fYOffset > field_args.draw_pixels_before_targets)
return;
- const float fY = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYOffset, fReverseOffsetPixels );
- const float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset );
- const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset );
- const float fAlpha = ArrowEffects::GetAlpha( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar );
- const float fGlow = ArrowEffects::GetGlow( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar );
+ float spline_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 );
@@ -793,13 +1265,11 @@ void NoteDisplay::DrawActor( const TapNote& tn, Actor* pActor, NotePart part, in
}
}
-void NoteDisplay::DrawTap(const TapNote& tn, int iCol, float fBeat,
- bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollStart,
- bool bIsAddition, float fPercentFadeToFail,
- float fReverseOffsetPixels,
- float fDrawDistanceAfterTargetsPixels,
- float fDrawDistanceBeforeTargetsPixels,
- float fFadeInPercentOfDrawFar)
+void NoteDisplay::DrawTap(const TapNote& tn,
+ const NoteFieldRenderArgs& field_args,
+ const NoteColumnRenderArgs& column_args, float fBeat,
+ bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollStart,
+ bool bIsAddition, float fPercentFadeToFail)
{
Actor* pActor = NULL;
NotePart part = NotePart_Tap;
@@ -871,15 +1341,287 @@ void NoteDisplay::DrawTap(const TapNote& tn, int iCol, float fBeat,
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, iCol, fYOffset, fBeat, bIsAddition, fPercentFadeToFail, fReverseOffsetPixels, 1.0f, fDrawDistanceAfterTargetsPixels, fDrawDistanceBeforeTargetsPixels, fFadeInPercentOfDrawFar );
+ 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
+{
+ const PlayerState* player_state= m_field_render_args->player_state;
+ float song_beat= player_state->GetDisplayedPosition().m_fSongBeatVisible;
+ // sp_* will be filled with the settings from the splines.
+ // ae_* will be filled with the settings from ArrowEffects.
+ // 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(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);
+ 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);
+ NCR_current.m_pos_handler.EvalForReceptor(song_beat, sp_pos);
+ break;
+ case NCSM_Position:
+ NCR_current.m_pos_handler.EvalForReceptor(song_beat, sp_pos);
+ break;
+ }
+ switch(NCR_current.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);
+ NCR_current.m_rot_handler.EvalForReceptor(song_beat, sp_rot);
+ break;
+ case NCSM_Position:
+ NCR_current.m_rot_handler.EvalForReceptor(song_beat, sp_rot);
+ break;
+ }
+ 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);
+ 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);
+ NCR_current.m_zoom_handler.EvalForReceptor(song_beat, sp_zoom);
+ break;
+ case NCSM_Position:
+ 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);
+}
+
+void NoteColumnRenderer::DrawPrimitives()
+{
+ m_column_render_args.song_beat= m_field_render_args->player_state->GetDisplayedPosition().m_fSongBeatVisible;
+ 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;
+ // Build lists of holds and taps for each player number, then pass those
+ // lists to the displays to draw.
+ // The vector in the NUM_PlayerNumber slot should stay empty, not worth
+ // optimizing it out. -Kyz
+ vector > holds(PLAYER_INVALID+1);
+ vector > taps(PLAYER_INVALID+1);
+ NoteData::TrackMap::const_iterator begin, end;
+ m_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)
+ {
+ const TapNote& tn= begin->second;
+ switch(tn.type)
+ {
+ case TapNoteType_Empty:
+ continue;
+ case TapNoteType_Tap:
+ case TapNoteType_HoldTail:
+ case TapNoteType_Mine:
+ case TapNoteType_Lift:
+ case TapNoteType_Attack:
+ case TapNoteType_AutoKeysound:
+ case TapNoteType_Fake:
+ if(!tn.result.bHidden)
+ {
+ taps[tn.pn].push_back(begin);
+ }
+ break;
+ case TapNoteType_HoldHead:
+ if(tn.HoldResult.hns != HNS_Held)
+ {
+ holds[tn.pn].push_back(begin);
+ }
+ break;
+ }
+ }
+#define DTS_INNER(pn, tap_set, draw_func, disp) \
+ if(!tap_set[pn].empty()) \
+ { \
+ any_upcoming|= disp->draw_func(*m_field_render_args, m_column_render_args, tap_set[pn]); \
+ }
+#define DRAW_TAP_SET(tap_set, draw_func) \
+ FOREACH_PlayerNumber(pn) \
+ { \
+ DTS_INNER(pn, tap_set, draw_func, m_displays[pn]); \
+ }
+ DRAW_TAP_SET(holds, DrawHoldsInRange);
+ DTS_INNER(PLAYER_INVALID, holds, DrawHoldsInRange, m_displays[PLAYER_INVALID]);
+ DRAW_TAP_SET(taps, DrawTapsInRange);
+ DTS_INNER(PLAYER_INVALID, taps, DrawTapsInRange, m_displays[PLAYER_INVALID]);
+#undef DTS_INNER
+#undef DRAW_TAP_SET
+ m_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();
+}
+
+NoteColumnRenderer::NCR_TweenState::NCR_TweenState()
+{
+ m_rot_handler.m_spline.set_spatial_extent(0, PI*2.0f);
+ m_rot_handler.m_spline.set_spatial_extent(1, PI*2.0f);
+ m_rot_handler.m_spline.set_spatial_extent(2, PI*2.0f);
+}
+
+void NoteColumnRenderer::NCR_TweenState::MakeWeightedAverage(
+ NCR_TweenState& out, const NCR_TweenState& from, const NCR_TweenState& 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
+{
+ static int get_spline(T* p, lua_State* L)
+ {
+ p->m_spline.PushSelf(L);
+ return 1;
+ }
+ DEFINE_METHOD(get_receptor_t, m_receptor_t);
+ DEFINE_METHOD(get_beats_per_t, m_beats_per_t);
+#define SET_T(member, name) \
+ static int name(T* p, lua_State* L) \
+ { \
+ p->member= FArg(1); \
+ COMMON_RETURN_SELF; \
+ }
+#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(GetPosHandler(), get_pos_handler);
+ GET_HANDLER(GetRotHandler(), get_rot_handler);
+ GET_HANDLER(GetZoomHandler(), get_zoom_handler);
+#undef GET_HANDLER
+
+ LunaNoteColumnRenderer()
+ {
+ 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 c0c88d8733..ce5afe18d8 100644
--- a/src/NoteDisplay.h
+++ b/src/NoteDisplay.h
@@ -1,13 +1,17 @@
#ifndef NOTE_DISPLAY_H
#define NOTE_DISPLAY_H
+#include "ActorFrame.h"
+#include "CubicSpline.h"
+#include "NoteData.h"
#include "PlayerNumber.h"
#include "GameInput.h"
-class Actor;
class Sprite;
class Model;
class PlayerState;
+class GhostArrowRow;
+class ReceptorArrowRow;
struct TapNote;
struct HoldNoteResult;
struct NoteMetricCache_t;
@@ -83,6 +87,88 @@ 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
+};
+
+const RString& NoteColumnSplineModeToString(NoteColumnSplineMode ncsm);
+LuaDeclareType(NoteColumnSplineMode);
+
+// A little pod struct to carry the data the NoteField needs to pass to the
+// NoteDisplay during rendering.
+struct NoteFieldRenderArgs
+{
+ const PlayerState* player_state; // to look up PlayerOptions
+ float reverse_offset_pixels;
+ ReceptorArrowRow* receptor_row;
+ GhostArrowRow* ghost_row;
+ const NoteData* note_data;
+ float first_beat;
+ float last_beat;
+ int first_row;
+ int last_row;
+ float draw_pixels_before_targets;
+ float draw_pixels_after_targets;
+ int* selection_begin_marker;
+ int* selection_end_marker;
+ float selection_glow;
+ float fail_fade;
+ 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;
+ static void MakeWeightedAverage(NCSplineHandler& out,
+ const NCSplineHandler& from, const NCSplineHandler& to, float between);
+
+ 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(const PlayerState* state,
+ float beat, float y_offset, float y_reverse_offset,
+ vector& sp_pos, vector& ae_pos) const;
+ void spae_zoom_for_beat(const PlayerState* state, float beat,
+ vector& sp_zoom, vector& ae_zoom) const;
+ void SetPRZForActor(Actor* actor,
+ const vector& sp_pos, const vector& ae_pos,
+ const vector& sp_rot, const vector& ae_rot,
+ const vector& sp_zoom, const vector& ae_zoom) const;
+ const NCSplineHandler* pos_handler;
+ const NCSplineHandler* rot_handler;
+ const NCSplineHandler* zoom_handler;
+ float song_beat;
+ int column;
+};
+
/** @brief Draws TapNotes and HoldNotes. */
class NoteDisplay
{
@@ -94,6 +180,14 @@ public:
static void Update( float fDeltaTime );
+ bool IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const;
+
+ bool DrawHoldsInRange(const NoteFieldRenderArgs& field_args,
+ const NoteColumnRenderArgs& column_args,
+ const vector& tap_set);
+ bool DrawTapsInRange(const NoteFieldRenderArgs& field_args,
+ const NoteColumnRenderArgs& column_args,
+ const vector& tap_set);
/**
* @brief Draw the TapNote onto the NoteField.
* @param tn the TapNote in question.
@@ -107,19 +201,16 @@ public:
* @param fDrawDistanceAfterTargetsPixels how much to draw after the receptors.
* @param fDrawDistanceBeforeTargetsPixels how much ot draw before the receptors.
* @param fFadeInPercentOfDrawFar when to start fading in. */
- void DrawTap(const TapNote& tn, int iCol, float fBeat,
- bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollBeat,
- bool bIsAddition, float fPercentFadeToFail,
- float fReverseOffsetPixels,
- float fDrawDistanceAfterTargetsPixels,
- float fDrawDistanceBeforeTargetsPixels,
- float fFadeInPercentOfDrawFar );
- void DrawHold( const TapNote& tn, int iCol, int iRow, bool bIsBeingHeld, const HoldNoteResult &Result,
- bool bIsAddition, float fPercentFadeToFail, float fReverseOffsetPixels, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels,
- float fDrawDistanceBeforeTargetsPixels2, float fFadeInPercentOfDrawFar );
-
+ void DrawTap(const TapNote& tn, const NoteFieldRenderArgs& field_args,
+ const NoteColumnRenderArgs& column_args, float fBeat,
+ bool bOnSameRowAsHoldStart,
+ bool bOnSameRowAsRollBeat, bool bIsAddition, float fPercentFadeToFail);
+ void DrawHold(const TapNote& tn, const NoteFieldRenderArgs& field_args,
+ const NoteColumnRenderArgs& column_args, int iRow, bool bIsBeingHeld,
+ const HoldNoteResult &Result,
+ bool bIsAddition, float fPercentFadeToFail);
+
bool DrawHoldHeadForTapsOnSameRow() const;
-
bool DrawRollHeadForTapsOnSameRow() const;
private:
@@ -128,14 +219,23 @@ private:
Actor *GetHoldActor( NoteColorActor nca[NUM_HoldType][NUM_ActiveType], NotePart part, float fNoteBeat, bool bIsRoll, bool bIsBeingHeld );
Sprite *GetHoldSprite( NoteColorSprite ncs[NUM_HoldType][NUM_ActiveType], NotePart part, float fNoteBeat, bool bIsRoll, bool bIsBeingHeld );
- void DrawActor( const TapNote& tn, Actor* pActor, NotePart part, int iCol, float fYOffset, float fBeat, bool bIsAddition, float fPercentFadeToFail,
- float fReverseOffsetPixels, float fColorScale, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar );
- void DrawHoldBody( const TapNote& tn, int iCol, float fBeat, bool bIsBeingHeld, float fYHead, float fYTail, bool bIsAddition, float fPercentFadeToFail,
- float fColorScale,
- bool bGlow, float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar );
- void DrawHoldPart( vector &vpSpr, int iCol, int fYStep, float fPercentFadeToFail, float fColorScale, bool bGlow,
- float fDrawDistanceAfterTargetsPixels, float fDrawDistanceBeforeTargetsPixels, float fFadeInPercentOfDrawFar, float fOverlappedTime,
- float fYTop, float fYBottom, float fYStartPos, float fYEndPos, bool bWrapping, bool bAnchorToTop, bool bFlipTextureVertically );
+ void DrawActor(const TapNote& tn, Actor* pActor, NotePart part,
+ const NoteFieldRenderArgs& field_args,
+ const NoteColumnRenderArgs& column_args, float fYOffset, float fBeat,
+ bool bIsAddition, float fPercentFadeToFail, float fColorScale,
+ bool is_being_held);
+ void DrawHoldBody(const TapNote& tn, const NoteFieldRenderArgs& field_args,
+ const NoteColumnRenderArgs& column_args, float fBeat, bool bIsBeingHeld,
+ float fYHead, float fYTail,
+ bool bIsAddition, float fPercentFadeToFail, float fColorScale,
+ bool bGlow, float top_beat, float bottom_beat);
+ void DrawHoldPart(vector &vpSpr,
+ const NoteFieldRenderArgs& field_args,
+ const NoteColumnRenderArgs& column_args, int fYStep,
+ float fPercentFadeToFail, float fColorScale, bool bGlow,
+ float fOverlappedTime, float fYTop, float fYBottom, float fYStartPos,
+ float fYEndPos, bool bWrapping, bool bAnchorToTop,
+ bool bFlipTextureVertically, float top_beat, float bottom_beat);
const PlayerState *m_pPlayerState; // to look up PlayerOptions
NoteMetricCache_t *cache;
@@ -152,9 +252,72 @@ private:
float m_fYReverseOffsetPixels;
};
+// So, this is a bit screwy, and it's partly because routine forces rendering
+// notes from different noteskins in the same column.
+// NoteColumnRenderer exists to hold all the data needed for rendering a
+// column and apply any transforms from that column's actor to the
+// NoteDisplays that render the notes.
+// NoteColumnRenderer is also used as a fake parent for the receptor and ghost
+// actors so they can move with the rest of the column. I didn't use
+// ActorProxy because the receptor/ghost actors need to pull in the parent
+// state of their rows and the parent state of the column. -Kyz
+
+struct NoteColumnRenderer : public Actor
+{
+ NoteDisplay* m_displays[PLAYER_INVALID+1];
+ NoteFieldRenderArgs* m_field_render_args;
+ NoteColumnRenderArgs m_column_render_args;
+ int m_column;
+
+ // 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);
+
+ struct NCR_TweenState
+ {
+ NCR_TweenState();
+ NCSplineHandler m_pos_handler;
+ NCSplineHandler m_rot_handler;
+ NCSplineHandler m_zoom_handler;
+ static void MakeWeightedAverage(NCR_TweenState& out,
+ const NCR_TweenState& from, const NCR_TweenState& to, float between);
+ bool operator==(const NCR_TweenState& other) const;
+ bool operator!=(const NCR_TweenState& other) const { return !operator==(other); }
+ };
+
+ NCR_TweenState& NCR_DestTweenState()
+ {
+ if(NCR_Tweens.empty())
+ { return NCR_current; }
+ else
+ { return NCR_Tweens.back(); }
+ }
+ const NCR_TweenState& 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
/**
+ * 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 e3ed26873c..59d60fe54b 100644
--- a/src/NoteField.cpp
+++ b/src/NoteField.cpp
@@ -36,11 +36,8 @@ static ThemeMetric FADE_FAIL_TIME( "NoteField", "FadeFailTime" );
static RString RoutineNoteSkinName( size_t i ) { return ssprintf("RoutineNoteSkinP%i",int(i+1)); }
static ThemeMetric1D ROUTINE_NOTESKIN( "NoteField", RoutineNoteSkinName, NUM_PLAYERS );
-static bool FAST_NOTE_RENDERING_PREF_CACHED= false;
-
NoteField::NoteField()
{
- FAST_NOTE_RENDERING_PREF_CACHED= PREFSMAN->m_FastNoteRendering;
m_pNoteData = NULL;
m_pCurDisplay = NULL;
@@ -64,9 +61,13 @@ NoteField::NoteField()
m_sprBeatBars.Load( THEME->GetPathG("NoteField","bars") );
m_sprBeatBars.StopAnimating();
+ // I decided to do it this way because I don't want to dig through
+ // ScreenEdit to change all the places it touches the markers. -Kyz
+ m_FieldRenderArgs.selection_begin_marker= &m_iBeginMarker;
+ m_FieldRenderArgs.selection_end_marker= &m_iEndMarker;
m_iBeginMarker = m_iEndMarker = -1;
- m_fPercentFadeToFail = -1;
+ m_FieldRenderArgs.fail_fade = -1;
m_StepCallback.SetFromNil();
m_SetPressedCallback.SetFromNil();
@@ -169,6 +170,8 @@ void NoteField::CacheAllUsedNoteSkins()
ASSERT_M( it != m_NoteDisplays.end(), sNoteSkinLower );
m_pDisplays[pn] = it->second;
}
+
+ InitColumnRenderers();
}
void NoteField::Init( const PlayerState* pPlayerState, float fYReverseOffsetPixels )
@@ -189,7 +192,7 @@ void NoteField::Load(
m_iDrawDistanceBeforeTargetsPixels = iDrawDistanceBeforeTargetsPixels;
ASSERT( m_iDrawDistanceBeforeTargetsPixels >= m_iDrawDistanceAfterTargetsPixels );
- m_fPercentFadeToFail = -1;
+ m_FieldRenderArgs.fail_fade = -1;
//int i1 = m_pNoteData->GetNumTracks();
//int i2 = GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer;
@@ -240,6 +243,30 @@ void NoteField::Load(
ASSERT_M( it != m_NoteDisplays.end(), sNoteSkinLower );
m_pDisplays[pn] = it->second;
}
+ InitColumnRenderers();
+}
+
+void NoteField::InitColumnRenderers()
+{
+ 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)
+ {
+ FOREACH_EnabledPlayer(pn)
+ {
+ m_ColumnRenderers[ncr].m_displays[pn]= &(m_pDisplays[pn]->display[ncr]);
+ }
+ m_ColumnRenderers[ncr].m_displays[PLAYER_INVALID]= &(m_pCurDisplay->display[ncr]);
+ m_ColumnRenderers[ncr].m_column= ncr;
+ m_ColumnRenderers[ncr].m_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);
}
void NoteField::Update( float fDeltaTime )
@@ -251,6 +278,11 @@ void NoteField::Update( float fDeltaTime )
ActorFrame::Update( fDeltaTime );
+ for(size_t c= 0; c < m_ColumnRenderers.size(); ++c)
+ {
+ m_ColumnRenderers[c].Update(fDeltaTime);
+ }
+
// update m_fBoardOffsetPixels, m_fCurrentBeatLastUpdate, m_fYPosCurrentBeatLastUpdate
const float fCurrentBeat = m_pPlayerState->GetDisplayedPosition().m_fSongBeat;
bool bTweeningOn = m_sprBoard->GetCurrentDiffuseAlpha() >= 0.98 && m_sprBoard->GetCurrentDiffuseAlpha() < 1.00; // HACK
@@ -276,11 +308,11 @@ void NoteField::Update( float fDeltaTime )
cur->m_ReceptorArrowRow.Update( fDeltaTime );
cur->m_GhostArrowRow.Update( fDeltaTime );
- if( m_fPercentFadeToFail >= 0 )
- m_fPercentFadeToFail = min( m_fPercentFadeToFail + fDeltaTime/FADE_FAIL_TIME, 1 );
+ if( m_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_fPercentFadeToFail );
+ 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
@@ -801,13 +833,11 @@ float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceB
return fLastBeatToDraw;
}
-inline float NoteRowToVisibleBeat( const PlayerState *pPlayerState, int iRow )
-{
- return NoteRowToBeat(iRow);
-}
-
bool NoteField::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const
{
+ // IMPORTANT: Do not modify this function without also modifying the
+ // version that is in NoteDisplay.cpp or coming up with a good way to
+ // merge them. -Kyz
// TRICKY: If boomerang is on, then ones in the range
// [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible.
// Test to see if this beat is visible before drawing.
@@ -834,41 +864,41 @@ void NoteField::DrawPrimitives()
const PlayerOptions ¤t_po = m_pPlayerState->m_PlayerOptions.GetCurrent();
// Adjust draw range depending on some effects
- int iDrawDistanceAfterTargetsPixels = m_iDrawDistanceAfterTargetsPixels;
+ m_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];
- iDrawDistanceAfterTargetsPixels += int(SCALE( fCenteredTimesBoomerang, 0.f, 1.f, 0.f, -SCREEN_HEIGHT/2 ));
- int iDrawDistanceBeforeTargetsPixels = 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] );
- iDrawDistanceAfterTargetsPixels = (int)(iDrawDistanceAfterTargetsPixels * fDrawScale);
- iDrawDistanceBeforeTargetsPixels = (int)(iDrawDistanceBeforeTargetsPixels * 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, iDrawDistanceAfterTargetsPixels );
- float fLastBeatToDraw = FindLastDisplayedBeat( m_pPlayerState, iDrawDistanceBeforeTargetsPixels );
+ 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;
- const int iFirstRowToDraw = BeatToNoteRow(fFirstBeatToDraw);
- const int iLastRowToDraw = 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", iFirstRowToDraw, iLastRowToDraw );
+ //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, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) )
+#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( iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels );
+ DrawBoard( m_FieldRenderArgs.draw_pixels_after_targets, m_FieldRenderArgs.draw_pixels_before_targets );
}
// Draw Receptors
@@ -891,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()) ? iLastRowToDraw : 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;
@@ -934,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() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
+ if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row )
{
float fBeat = seg->GetBeat();
if( IS_ON_SCREEN(fBeat) )
@@ -946,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() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
+ if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row )
{
float fBeat = seg->GetBeat();
if( IS_ON_SCREEN(fBeat) )
@@ -958,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() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
+ if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row )
{
float fBeat = seg->GetBeat();
if( IS_ON_SCREEN(fBeat) )
@@ -970,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() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
+ if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row )
{
float fBeat = seg->GetBeat();
if( IS_ON_SCREEN(fBeat) )
@@ -982,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() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
+ if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row )
{
float fBeat = seg->GetBeat();
if( IS_ON_SCREEN(fBeat) )
@@ -994,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() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
+ if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row )
{
float fBeat = seg->GetBeat();
if( IS_ON_SCREEN(fBeat) )
@@ -1006,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() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
+ if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row )
{
float fBeat = seg->GetBeat();
if( IS_ON_SCREEN(fBeat) )
@@ -1018,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() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
+ if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row )
{
float fBeat = seg->GetBeat();
if( IS_ON_SCREEN(fBeat) )
@@ -1030,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() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
+ if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row )
{
float fBeat = seg->GetBeat();
if( IS_ON_SCREEN(fBeat) )
@@ -1042,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() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
+ if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row )
{
float fBeat = seg->GetBeat();
if( IS_ON_SCREEN(fBeat) )
@@ -1055,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() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw )
+ if( seg->GetRow() >= m_FieldRenderArgs.first_row && seg->GetRow() <= m_FieldRenderArgs.last_row )
{
float fBeat = seg->GetBeat();
if( IS_ON_SCREEN(fBeat) )
@@ -1075,8 +1105,8 @@ void NoteField::DrawPrimitives()
float fSecond = a->fStartSecond;
float fBeat = timing.GetBeatFromElapsedTime( fSecond );
- if( BeatToNoteRow(fBeat) >= iFirstRowToDraw &&
- BeatToNoteRow(fBeat) <= iLastRowToDraw)
+ if( BeatToNoteRow(fBeat) >= m_FieldRenderArgs.first_row &&
+ BeatToNoteRow(fBeat) <= m_FieldRenderArgs.last_row)
{
if( IS_ON_SCREEN(fBeat) )
DrawAttackText( fBeat, *a );
@@ -1094,8 +1124,8 @@ void NoteField::DrawPrimitives()
FOREACH_CONST(Attack, attacks, a)
{
float fBeat = timing.GetBeatFromElapsedTime(a->fStartSecond);
- if (BeatToNoteRow(fBeat) >= iFirstRowToDraw &&
- BeatToNoteRow(fBeat) <= iLastRowToDraw &&
+ if (BeatToNoteRow(fBeat) >= m_FieldRenderArgs.first_row &&
+ BeatToNoteRow(fBeat) <= m_FieldRenderArgs.last_row &&
IS_ON_SCREEN(fBeat))
{
this->DrawAttackText(fBeat, *a);
@@ -1179,20 +1209,20 @@ void NoteField::DrawPrimitives()
{
int iBegin = m_iBeginMarker;
int iEnd = m_iEndMarker;
- CLAMP( iBegin, iFirstRowToDraw, iLastRowToDraw );
- CLAMP( iEnd, iFirstRowToDraw, iLastRowToDraw );
+ 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 >= iFirstRowToDraw &&
- m_iBeginMarker <= iLastRowToDraw )
+ if( m_iBeginMarker >= m_FieldRenderArgs.first_row &&
+ m_iBeginMarker <= m_FieldRenderArgs.last_row )
DrawMarkerBar( m_iBeginMarker );
}
else if( m_iEndMarker != -1 )
{
- if( m_iEndMarker >= iFirstRowToDraw &&
- m_iEndMarker <= iLastRowToDraw )
+ if( m_iEndMarker >= m_FieldRenderArgs.first_row &&
+ m_iEndMarker <= m_FieldRenderArgs.last_row )
DrawMarkerBar( m_iEndMarker );
}
}
@@ -1201,175 +1231,19 @@ void NoteField::DrawPrimitives()
// Draw the arrows in order of column. This minimizes texture switches and
// lets us draw in big batches.
- float fSelectedRangeGlow = SCALE( RageFastCos(RageTimer::GetTimeSinceStartFast()*2), -1, 1, 0.1f, 0.3f );
-
const Style* pStyle = GAMESTATE->GetCurrentStyle();
ASSERT_M(m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer,
ssprintf("NumTracks %d != ColsPerPlayer %d",m_pNoteData->GetNumTracks(),
GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer));
+ m_FieldRenderArgs.selection_glow= SCALE(
+ RageFastCos(RageTimer::GetTimeSinceStartFast()*2), -1, 1, 0.1f, 0.3f);
+ m_FieldRenderArgs.fade_before_targets= FADE_BEFORE_TARGETS_PERCENT;
+
for( int j=0; jGetNumTracks(); j++ ) // for each arrow column
{
const int c = pStyle->m_iColumnDrawOrder[j];
-
- bool bAnyUpcomingInThisCol = false;
-
- // Draw all HoldNotes in this column (so that they appear under the tap notes)
- {
- NoteData::TrackMap::const_iterator begin, end;
- m_pNoteData->GetTapNoteRangeInclusive( c, iFirstRowToDraw, iLastRowToDraw+1, begin, end );
-
- for( ; begin != end; ++begin )
- {
- const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, j);
- if( tn.type != TapNoteType_HoldHead )
- continue; // skip
-
- const HoldNoteResult &Result = tn.HoldResult;
- if( Result.hns == HNS_Held ) // if this HoldNote was completed
- continue; // don't draw anything
-
- int iStartRow = begin->first;
- int iEndRow = iStartRow + tn.iDuration;
-
- // TRICKY: If boomerang is on, then all notes in the range
- // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible.
- // Test every note to make sure it's on screen before drawing
- float fThrowAway;
- bool bStartIsPastPeak = false;
- bool bEndIsPastPeak = false;
- float fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iStartRow), fThrowAway, bStartIsPastPeak );
- float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iEndRow), fThrowAway, bEndIsPastPeak );
-
- bool bTailIsOnVisible = iDrawDistanceAfterTargetsPixels <= fEndYOffset && fEndYOffset <= iDrawDistanceBeforeTargetsPixels;
- bool bHeadIsVisible = iDrawDistanceAfterTargetsPixels <= fStartYOffset && fStartYOffset <= iDrawDistanceBeforeTargetsPixels;
- bool bStraddlingVisible = fStartYOffset <= iDrawDistanceAfterTargetsPixels && iDrawDistanceBeforeTargetsPixels <= fEndYOffset;
- bool bStaddlingPeak = bStartIsPastPeak && !bEndIsPastPeak;
- if( !(bTailIsOnVisible || bHeadIsVisible || bStraddlingVisible || bStaddlingPeak) )
- {
- //LOG->Trace( "skip drawing this hold." );
- continue; // skip
- }
-
- bool bIsAddition = (tn.source == TapNoteSource_Addition);
- bool bIsHopoPossible = (tn.bHopoPossible);
- bool bUseAdditionColoring = bIsAddition || bIsHopoPossible;
- const bool bHoldGhostShowing = tn.HoldResult.bActive && tn.HoldResult.fLife > 0;
- const bool bIsHoldingNote = tn.HoldResult.bHeld;
- if( bHoldGhostShowing )
- m_pCurDisplay->m_GhostArrowRow.SetHoldShowing( c, tn );
-
- ASSERT_M( NoteRowToBeat(iStartRow) > -2000, ssprintf("%i %i %i", iStartRow, iEndRow, c) );
-
- bool bIsInSelectionRange = false;
- if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 )
- bIsInSelectionRange = (m_iBeginMarker <= iStartRow && iEndRow < m_iEndMarker);
-
- NoteDisplayCols *displayCols = tn.pn == PLAYER_INVALID ? m_pCurDisplay : m_pDisplays[tn.pn];
- displayCols->display[c].DrawHold( tn, c, iStartRow, bIsHoldingNote, Result, bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail,
- m_fYReverseOffsetPixels, (float) iDrawDistanceAfterTargetsPixels, (float) iDrawDistanceBeforeTargetsPixels, iDrawDistanceBeforeTargetsPixels, FADE_BEFORE_TARGETS_PERCENT );
-
- bool bNoteIsUpcoming = NoteRowToBeat(iStartRow) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat;
- bAnyUpcomingInThisCol |= bNoteIsUpcoming;
- }
- }
-
- // Draw all TapNotes in this column
-
- // draw notes from furthest to closest
- NoteData::TrackMap::const_iterator begin, end;
- m_pNoteData->GetTapNoteRange( c, iFirstRowToDraw, iLastRowToDraw+1, begin, end );
- for( ; begin != end; ++begin )
- {
- int q = begin->first;
- const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, q);
-
- // Switch modified by Wolfman2000, tested by Saturn2888
- // Fixes hold head overlapping issue, but not the rolls.
- switch( tn.type )
- {
- case TapNoteType_Empty: // no note here
- {
- continue;
- }
- case TapNoteType_HoldHead:
- {
- //if (tn.subType == TapNoteSubType_Roll)
- continue; // skip
- }
- default: break;
- }
-
- // Don't draw hidden (fully judged) steps.
- if( tn.result.bHidden )
- continue;
-
- // TRICKY: If boomerang is on, then all notes in the range
- // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible.
- // Test every note to make sure it's on screen before drawing.
- if( !IsOnScreen( NoteRowToBeat(q), c, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) )
- continue; // skip
-
- ASSERT_M( NoteRowToBeat(q) > -2000, ssprintf("%i %i %i, %f %f", q, iLastRowToDraw,
- iFirstRowToDraw, m_pPlayerState->GetDisplayedPosition().m_fSongBeat, m_pPlayerState->GetDisplayedPosition().m_fMusicSeconds) );
-
- // See if there is a hold step that begins on this index.
- // Only do this if the noteskin cares.
- bool bHoldNoteBeginsOnThisBeat = false;
- if( m_pCurDisplay->display[c].DrawHoldHeadForTapsOnSameRow() )
- {
- for( int c2=0; c2GetNumTracks(); c2++ )
- {
- const TapNote &tmp = m_pNoteData->GetTapNote(c2, q);
- if(tmp.type == TapNoteType_HoldHead &&
- tmp.subType == TapNoteSubType_Hold)
- {
- bHoldNoteBeginsOnThisBeat = true;
- break;
- }
- }
- }
-
- // do the same for a roll.
- bool bRollNoteBeginsOnThisBeat = false;
- if (m_pCurDisplay->display[c].DrawRollHeadForTapsOnSameRow() )
- {
- for( int c2=0; c2GetNumTracks(); c2++ )
- {
- const TapNote &tmp = m_pNoteData->GetTapNote(c2, q);
- if(tmp.type == TapNoteType_HoldHead &&
- tmp.subType == TapNoteSubType_Roll)
- {
- bRollNoteBeginsOnThisBeat = true;
- break;
- }
- }
- }
-
- bool bIsInSelectionRange = false;
- if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 )
- bIsInSelectionRange = m_iBeginMarker<=q && qdisplay[c].DrawTap(tn, c, NoteRowToVisibleBeat(m_pPlayerState, q),
- bHoldNoteBeginsOnThisBeat, bRollNoteBeginsOnThisBeat,
- bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail,
- m_fYReverseOffsetPixels, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels,
- FADE_BEFORE_TARGETS_PERCENT );
-
- bool bNoteIsUpcoming = NoteRowToBeat(q) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat;
- bAnyUpcomingInThisCol |= bNoteIsUpcoming;
-
- if(!FAST_NOTE_RENDERING_PREF_CACHED)
- {
- DISPLAY->ClearZBuffer();
- }
- }
-
- cur->m_ReceptorArrowRow.SetNoteUpcoming( c, bAnyUpcomingInThisCol );
+ m_ColumnRenderers[c].Draw();
}
cur->m_GhostArrowRow.Draw();
@@ -1377,7 +1251,7 @@ void NoteField::DrawPrimitives()
void NoteField::FadeToFail()
{
- m_fPercentFadeToFail = max( 0.0f, m_fPercentFadeToFail ); // 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
}
@@ -1523,10 +1397,10 @@ public:
} \
return 0; \
}
- SET_CALLBACK_GENERIC(SetStepCallback, m_StepCallback);
- SET_CALLBACK_GENERIC(SetSetPressedCallback, m_SetPressedCallback);
- SET_CALLBACK_GENERIC(SetDidTapNoteCallback, m_DidTapNoteCallback);
- SET_CALLBACK_GENERIC(SetDidHoldNoteCallback, m_DidHoldNoteCallback);
+ SET_CALLBACK_GENERIC(set_step_callback, m_StepCallback);
+ SET_CALLBACK_GENERIC(set_set_pressed_callback, m_SetPressedCallback);
+ SET_CALLBACK_GENERIC(set_did_tap_note_callback, m_DidTapNoteCallback);
+ SET_CALLBACK_GENERIC(set_did_hold_note_callback, m_DidHoldNoteCallback);
#undef SET_CALLBACK_GENERIC
static int check_column(lua_State* L, int index)
@@ -1542,7 +1416,7 @@ public:
return col;
}
- static int Step(T* p, lua_State* L)
+ static int step(T* p, lua_State* L)
{
int col= check_column(L, 1);
TapNoteScore tns= Enum::Check(L, 2);
@@ -1550,14 +1424,14 @@ public:
return 0;
}
- static int SetPressed(T* p, lua_State* L)
+ static int set_pressed(T* p, lua_State* L)
{
int col= check_column(L, 1);
p->SetPressed(col, true);
return 0;
}
- static int DidTapNote(T* p, lua_State* L)
+ static int did_tap_note(T* p, lua_State* L)
{
int col= check_column(L, 1);
TapNoteScore tns= Enum::Check(L, 2);
@@ -1566,7 +1440,7 @@ public:
return 0;
}
- static int DidHoldNote(T* p, lua_State* L)
+ static int did_hold_note(T* p, lua_State* L)
{
int col= check_column(L, 1);
HoldNoteScore hns= Enum::Check(L, 2);
@@ -1575,16 +1449,28 @@ public:
return 0;
}
+ 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)
+ {
+ p->m_ColumnRenderers[i].PushSelf(L);
+ lua_rawseti(L, -2, i+1);
+ }
+ return 1;
+ }
+
LunaNoteField()
{
- ADD_METHOD(SetStepCallback);
- ADD_METHOD(SetSetPressedCallback);
- ADD_METHOD(SetDidTapNoteCallback);
- ADD_METHOD(SetDidHoldNoteCallback);
- ADD_METHOD(Step);
- ADD_METHOD(SetPressed);
- ADD_METHOD(DidTapNote);
- ADD_METHOD(DidHoldNote);
+ ADD_METHOD(set_step_callback);
+ ADD_METHOD(set_set_pressed_callback);
+ ADD_METHOD(set_did_tap_note_callback);
+ ADD_METHOD(set_did_hold_note_callback);
+ ADD_METHOD(step);
+ ADD_METHOD(set_pressed);
+ ADD_METHOD(did_tap_note);
+ ADD_METHOD(did_hold_note);
+ ADD_METHOD(get_column_actors);
}
};
diff --git a/src/NoteField.h b/src/NoteField.h
index cfdf24be04..1d6a0f1861 100644
--- a/src/NoteField.h
+++ b/src/NoteField.h
@@ -29,6 +29,8 @@ public:
int iDrawDistanceBeforeTargetsPixels );
virtual void Unload();
+ void InitColumnRenderers();
+
virtual void HandleMessage( const Message &msg );
// This is done automatically by Init(), but can be re-called explicitly if the
@@ -55,6 +57,10 @@ public:
int m_iBeginMarker, m_iEndMarker; // only used with MODE_EDIT
+ // m_ColumnRenderers belongs in the protected section, but it's here in
+ // public so that the Lua API can access it. -Kyz
+ vector m_ColumnRenderers;
+
protected:
void CacheNoteSkin( const RString &sNoteSkin );
void UncacheNoteSkin( const RString &sNoteSkin );
@@ -84,8 +90,6 @@ protected:
const NoteData *m_pNoteData;
- float m_fPercentFadeToFail; // -1 if not fading to fail
-
const PlayerState* m_pPlayerState;
int m_iDrawDistanceAfterTargetsPixels; // this should be a negative number
int m_iDrawDistanceBeforeTargetsPixels; // this should be a positive number
@@ -101,6 +105,8 @@ protected:
~NoteDisplayCols() { delete [] display; }
};
+ NoteFieldRenderArgs m_FieldRenderArgs;
+
/* All loaded note displays, mapped by their name. */
map m_NoteDisplays;
NoteDisplayCols *m_pCurDisplay;
diff --git a/src/RageMath.cpp b/src/RageMath.cpp
index dd209974cc..05bc7dab0c 100644
--- a/src/RageMath.cpp
+++ b/src/RageMath.cpp
@@ -5,6 +5,7 @@
*/
#include "global.h"
+#include "RageLog.h"
#include "RageMath.h"
#include "RageTypes.h"
#include
@@ -40,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
@@ -313,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/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; }
diff --git a/src/ReceptorArrowRow.cpp b/src/ReceptorArrowRow.cpp
index fd6d2deb31..c4565fd2f1 100644
--- a/src/ReceptorArrowRow.cpp
+++ b/src/ReceptorArrowRow.cpp
@@ -30,6 +30,16 @@ void ReceptorArrowRow::Load( const PlayerState* pPlayerState, float fYReverseOff
}
}
+void ReceptorArrowRow::SetColumnRenderers(vector& renderers)
+{
+ ASSERT_M(renderers.size() == m_ReceptorArrow.size(), "Notefield has different number of columns than receptor row.");
+ for(size_t c= 0; c < m_ReceptorArrow.size(); ++c)
+ {
+ m_ReceptorArrow[c]->SetFakeParent(&(renderers[c]));
+ }
+ m_renderers= &renderers;
+}
+
ReceptorArrowRow::~ReceptorArrowRow()
{
for( unsigned i = 0; i < m_ReceptorArrow.size(); ++i )
@@ -53,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 );
- m_ReceptorArrow[c]->SetY( fY );
- m_ReceptorArrow[c]->SetZ( fZ );
-
- 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]);
}
}
diff --git a/src/ReceptorArrowRow.h b/src/ReceptorArrowRow.h
index 1b93047386..a65cd407bb 100644
--- a/src/ReceptorArrowRow.h
+++ b/src/ReceptorArrowRow.h
@@ -4,6 +4,7 @@
#include "ReceptorArrow.h"
#include "ActorFrame.h"
#include "GameConstantsAndTypes.h"
+#include "NoteDisplay.h"
class PlayerState;
/** @brief A row of ReceptorArrow objects. */
@@ -16,6 +17,7 @@ public:
virtual void DrawPrimitives();
void Load( const PlayerState* pPlayerState, float fYReverseOffset );
+ void SetColumnRenderers(vector& renderers);
void Step( int iCol, TapNoteScore score );
void SetPressed( int iCol );
@@ -28,6 +30,7 @@ protected:
float m_fYReverseOffsetPixels;
float m_fFadeToFailPercent;
+ vector const* m_renderers;
vector m_ReceptorArrow;
};