Added rotation and zoom splines to note columns, with the help of a couple new classes. Reorganized args to NoteDisplay functions into helper structures. Made splines have 3 modes controlling their behavior. Hold twisting is now handled by an AA rotation and hold drawing in general is now more complex.

This commit is contained in:
Kyzentun
2015-01-01 04:36:20 -07:00
parent 0cb08dc8bd
commit 84e758c6c7
13 changed files with 886 additions and 276 deletions
+499 -143
View File
@@ -17,6 +17,9 @@
#include "Foreach.h"
#include "RageMath.h"
static const double PI_180= PI / 180.0;
static const double PI_180R= 180.0 / PI;
const RString& NoteNotePartToString( NotePart i );
/** @brief A foreach loop going through the different NoteParts. */
#define FOREACH_NotePart( i ) FOREACH_ENUM( NotePart, i )
@@ -43,6 +46,15 @@ XToString( NoteColorType );
StringToX( NoteColorType );
LuaXType( NoteColorType );
static const char* NoteColumnSplineModeNames[] = {
"Disabled",
"Offset",
"Position",
};
XToString(NoteColumnSplineMode);
StringToX(NoteColumnSplineMode);
LuaXType(NoteColumnSplineMode);
static bool IsVectorZero( const RageVector2 &v )
{
return v.x == 0 && v.y == 0;
@@ -277,6 +289,101 @@ XToString( ActiveType );
float NCSplineHandler::BeatToTValue(float song_beat, float note_beat) const
{
float relative_beat= note_beat;
// This allows someone to do something really fancy like having a spline
// that extends the length of the song. Think of arrows tracing a path
// as the song progresses. -Kyz
if(m_subtract_song_beat_from_curr)
{
relative_beat-= song_beat;
return (relative_beat / m_beats_per_t) - m_receptor_t;
}
return relative_beat / m_beats_per_t;
}
void NCSplineHandler::EvalForBeat(float song_beat, float note_beat, vector<float>& 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<float>& ret) const
{
float t_value= BeatToTValue(song_beat, note_beat);
m_spline.evaluate_derivative(t_value, ret);
}
void NCSplineHandler::EvalForReceptor(float song_beat, vector<float>& ret) const
{
float t_value= m_receptor_t;
if(!m_subtract_song_beat_from_curr)
{
t_value= song_beat / m_beats_per_t;
}
m_spline.evaluate(t_value, ret);
}
void NoteColumnRenderArgs::spae_pos_for_beat(PlayerState const* state,
float beat, float y_offset, float y_reverse_offset,
vector<float>& sp_pos, vector<float>& ae_pos) const
{
switch(pos_handler->m_spline_mode)
{
case NCSM_Disabled:
ArrowEffects::GetXYZPos(state, column, y_offset, y_reverse_offset, ae_pos);
sp_pos.resize(3);
// Sure, resize is supposed to call the default constructor, and for
// numbers the default constructor is supposed to set it to zero, but
// I got bit for relying on that once. -Kyz
sp_pos[0]= sp_pos[1]= sp_pos[2]= 0.0f;
break;
case NCSM_Offset:
ArrowEffects::GetXYZPos(state, column, y_offset, y_reverse_offset, ae_pos);
pos_handler->EvalForBeat(song_beat, beat, sp_pos);
break;
case NCSM_Position:
pos_handler->EvalForBeat(song_beat, beat, sp_pos);
break;
}
}
void NoteColumnRenderArgs::spae_zoom_for_beat(PlayerState const* state, float beat,
vector<float>& sp_zoom, vector<float>& ae_zoom) const
{
switch(zoom_handler->m_spline_mode)
{
case NCSM_Disabled:
ae_zoom[0]= ae_zoom[1]= ae_zoom[2]= ArrowEffects::GetZoom(state);
sp_zoom.resize(3);
sp_zoom[0]= sp_zoom[1]= sp_zoom[2]= 0.0f;
break;
case NCSM_Offset:
ae_zoom[0]= ae_zoom[1]= ae_zoom[2]= ArrowEffects::GetZoom(state);
zoom_handler->EvalForBeat(song_beat, beat, sp_zoom);
break;
case NCSM_Position:
zoom_handler->EvalForBeat(song_beat, beat, sp_zoom);
break;
}
}
void NoteColumnRenderArgs::SetPRZForActor(Actor* actor,
vector<float> const& sp_pos, vector<float> const& ae_pos,
vector<float> const& sp_rot, vector<float> const& ae_rot,
vector<float> const& sp_zoom, vector<float> const& ae_zoom) const
{
actor->SetX(sp_pos[0] + ae_pos[0]);
actor->SetY(sp_pos[1] + ae_pos[1]);
actor->SetZ(sp_pos[2] + ae_pos[2]);
actor->SetRotationX(sp_rot[0] * PI_180R + ae_rot[0]);
actor->SetRotationY(sp_rot[1] * PI_180R + ae_rot[1]);
actor->SetRotationZ(sp_rot[2] * PI_180R + ae_rot[2]);
actor->SetZoomX(sp_zoom[0] + ae_zoom[0]);
actor->SetZoomY(sp_zoom[1] + ae_zoom[1]);
actor->SetZoomZ(sp_zoom[2] + ae_zoom[2]);
}
NoteDisplay::NoteDisplay()
{
cache = new NoteMetricCache_t;
@@ -342,15 +449,9 @@ bool NoteDisplay::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTarge
return true;
}
float NoteDisplay::BeatToTValue(CommonColumnRenderArgs const& args, float beat) const
{
float song_beat= m_pPlayerState->GetDisplayedPosition().m_fSongBeatVisible;
float relative_beat= beat - song_beat;
return (relative_beat / args.beats_per_t) - args.receptor_t;
}
bool NoteDisplay::DrawHoldsInRange(CommonColumnRenderArgs const& args,
int column, vector<NoteData::TrackMap::const_iterator> const& tap_set)
bool NoteDisplay::DrawHoldsInRange(NoteFieldRenderArgs const& field_args,
NoteColumnRenderArgs const& column_args,
vector<NoteData::TrackMap::const_iterator> const& tap_set)
{
bool any_upcoming = false;
for(vector<NoteData::TrackMap::const_iterator>::const_iterator tapit=
@@ -367,18 +468,18 @@ bool NoteDisplay::DrawHoldsInRange(CommonColumnRenderArgs const& args,
float throw_away;
bool start_past_peak = false;
bool end_past_peak = false;
float start_y = ArrowEffects::GetYOffset(m_pPlayerState, column,
float start_y = ArrowEffects::GetYOffset(m_pPlayerState, column_args.column,
NoteRowToVisibleBeat(m_pPlayerState, start_row), throw_away,
start_past_peak);
float end_y = ArrowEffects::GetYOffset(m_pPlayerState, column,
float end_y = ArrowEffects::GetYOffset(m_pPlayerState, column_args.column,
NoteRowToVisibleBeat(m_pPlayerState, end_row), throw_away,
end_past_peak);
bool tail_visible = args.draw_pixels_after_targets <= end_y &&
end_y <= args.draw_pixels_before_targets;
bool head_visible = args.draw_pixels_after_targets <= start_y &&
start_y <= args.draw_pixels_before_targets;
bool straddling_visible = start_y <= args.draw_pixels_after_targets &&
args.draw_pixels_before_targets <= end_y;
bool tail_visible = field_args.draw_pixels_after_targets <= end_y &&
end_y <= field_args.draw_pixels_before_targets;
bool head_visible = field_args.draw_pixels_after_targets <= start_y &&
start_y <= field_args.draw_pixels_before_targets;
bool straddling_visible = start_y <= field_args.draw_pixels_after_targets &&
field_args.draw_pixels_before_targets <= end_y;
bool straddling_peak = start_past_peak && !end_past_peak;
if(!(tail_visible || head_visible || straddling_visible || straddling_peak))
{
@@ -393,21 +494,21 @@ bool NoteDisplay::DrawHoldsInRange(CommonColumnRenderArgs const& args,
const bool is_holding = tn.HoldResult.bHeld;
if(hold_ghost_showing)
{
args.ghost_row->SetHoldShowing(column, tn);
field_args.ghost_row->SetHoldShowing(column_args.column, tn);
}
ASSERT_M(NoteRowToBeat(start_row) > -2000, ssprintf("%i %i %i", start_row, end_row, column));
ASSERT_M(NoteRowToBeat(start_row) > -2000, ssprintf("%i %i %i", start_row, end_row, column_args.column));
bool in_selection_range = false;
if(*args.selection_begin_marker != -1 && *args.selection_end_marker != -1)
if(*field_args.selection_begin_marker != -1 && *field_args.selection_end_marker != -1)
{
in_selection_range = (*args.selection_begin_marker <= start_row &&
end_row < *args.selection_end_marker);
in_selection_range = (*field_args.selection_begin_marker <= start_row &&
end_row < *field_args.selection_end_marker);
}
DrawHold(tn, args, column, start_row, is_holding, result,
DrawHold(tn, field_args, column_args, start_row, is_holding, result,
use_addition_coloring,
in_selection_range ? args.selection_glow : args.fail_fade);
in_selection_range ? field_args.selection_glow : field_args.fail_fade);
bool note_upcoming = NoteRowToBeat(start_row) >
m_pPlayerState->GetDisplayedPosition().m_fSongBeat;
@@ -416,8 +517,9 @@ bool NoteDisplay::DrawHoldsInRange(CommonColumnRenderArgs const& args,
return any_upcoming;
}
bool NoteDisplay::DrawTapsInRange(CommonColumnRenderArgs const& args,
int column, vector<NoteData::TrackMap::const_iterator> const& tap_set)
bool NoteDisplay::DrawTapsInRange(NoteFieldRenderArgs const& field_args,
NoteColumnRenderArgs const& column_args,
vector<NoteData::TrackMap::const_iterator> const& tap_set)
{
bool any_upcoming= false;
// draw notes from furthest to closest
@@ -430,8 +532,8 @@ bool NoteDisplay::DrawTapsInRange(CommonColumnRenderArgs const& args,
// TRICKY: If boomerang is on, then all notes in the range
// [first_row,last_row] aren't necessarily visible.
// Test every note to make sure it's on screen before drawing.
if(!IsOnScreen(NoteRowToBeat(tap_row), column,
args.draw_pixels_after_targets, args.draw_pixels_before_targets))
if(!IsOnScreen(NoteRowToBeat(tap_row), column_args.column,
field_args.draw_pixels_after_targets, field_args.draw_pixels_before_targets))
{
continue; // skip
}
@@ -451,9 +553,9 @@ bool NoteDisplay::DrawTapsInRange(CommonColumnRenderArgs const& args,
bool hold_begins_on_this_beat = false;
if(DrawHoldHeadForTapsOnSameRow())
{
for(int c2= 0; c2 < args.note_data->GetNumTracks(); ++c2)
for(int c2= 0; c2 < field_args.note_data->GetNumTracks(); ++c2)
{
const TapNote &tmp = args.note_data->GetTapNote(c2, tap_row);
const TapNote &tmp = field_args.note_data->GetTapNote(c2, tap_row);
if(tmp.type == TapNoteType_HoldHead &&
tmp.subType == TapNoteSubType_Hold)
{
@@ -467,9 +569,9 @@ bool NoteDisplay::DrawTapsInRange(CommonColumnRenderArgs const& args,
bool roll_begins_on_this_beat = false;
if(DrawRollHeadForTapsOnSameRow())
{
for(int c2= 0; c2 < args.note_data->GetNumTracks(); ++c2)
for(int c2= 0; c2 < field_args.note_data->GetNumTracks(); ++c2)
{
const TapNote &tmp = args.note_data->GetTapNote(c2, tap_row);
const TapNote &tmp = field_args.note_data->GetTapNote(c2, tap_row);
if(tmp.type == TapNoteType_HoldHead &&
tmp.subType == TapNoteSubType_Roll)
{
@@ -480,19 +582,20 @@ bool NoteDisplay::DrawTapsInRange(CommonColumnRenderArgs const& args,
}
bool in_selection_range = false;
if(*args.selection_begin_marker != -1 && *args.selection_end_marker != -1)
if(*field_args.selection_begin_marker != -1 && *field_args.selection_end_marker != -1)
{
in_selection_range = *args.selection_begin_marker <= tap_row &&
tap_row < *args.selection_end_marker;
in_selection_range = *field_args.selection_begin_marker <= tap_row &&
tap_row < *field_args.selection_end_marker;
}
bool is_addition = (tn.source == TapNoteSource_Addition);
bool hopo_possible = (tn.bHopoPossible);
bool use_addition_coloring = is_addition || hopo_possible;
DrawTap(tn, args, column, NoteRowToVisibleBeat(m_pPlayerState, tap_row),
DrawTap(tn, field_args, column_args,
NoteRowToVisibleBeat(m_pPlayerState, tap_row),
hold_begins_on_this_beat, roll_begins_on_this_beat,
use_addition_coloring,
in_selection_range ? args.selection_glow : args.fail_fade);
in_selection_range ? field_args.selection_glow : field_args.fail_fade);
any_upcoming |= NoteRowToBeat(tap_row) >
m_pPlayerState->GetDisplayedPosition().m_fSongBeat;
@@ -614,28 +717,24 @@ struct StripBuffer
};
void NoteDisplay::DrawHoldPart(vector<Sprite*> &vpSpr,
CommonColumnRenderArgs const& args, int iCol, int fYStep,
NoteFieldRenderArgs const& field_args,
NoteColumnRenderArgs const& column_args, int fYStep,
float fPercentFadeToFail, float fColorScale, bool bGlow,
float fOverlappedTime, float fYTop, float fYBottom, float fYStartPos,
float fYEndPos, bool bWrapping, bool bAnchorToTop,
bool bFlipTextureVertically, float top_t, float bottom_t)
bool bFlipTextureVertically, float top_beat, float bottom_beat)
{
ASSERT( !vpSpr.empty() );
float ae_zoom= ArrowEffects::GetZoom(m_pPlayerState);
Sprite *pSprite = vpSpr.front();
FOREACH( Sprite *, vpSpr, s )
{
(*s)->SetZoom( ArrowEffects::GetZoom(m_pPlayerState) );
ASSERT( (*s)->GetUnzoomedWidth() == pSprite->GetUnzoomedWidth() );
ASSERT( (*s)->GetUnzoomedHeight() == pSprite->GetUnzoomedHeight() );
}
// draw manually in small segments
RectF rect = *pSprite->GetCurrentTextureCoordRect();
if( bFlipTextureVertically )
swap( rect.top, rect.bottom );
const float fFrameWidth = pSprite->GetZoomedWidth();
const float fFrameHeight = pSprite->GetZoomedHeight();
const float fFrameWidth = pSprite->GetUnzoomedWidth();
const float fFrameHeight = pSprite->GetUnzoomedHeight() * ae_zoom;
/* Only draw the section that's within the range specified. If a hold note is
* very long, don't process or draw the part outside of the range. Don't change
@@ -674,6 +773,10 @@ void NoteDisplay::DrawHoldPart(vector<Sprite*> &vpSpr,
const float fTexCoordRight = rect.right;
const float fTexCoordCenter = (fTexCoordLeft+fTexCoordRight)/2;
// pos_z_vec will be used later to orient the hold. Read below. -Kyz
static RageVector3 const pos_z_vec(0.0f, 0.0f, 1.0f);
static RageVector3 const pos_y_vec(0.0f, 1.0f, 0.0f);
StripBuffer queue;
for( float fY = fYStartPos; !bLast; fY += fYStep )
{
@@ -683,50 +786,165 @@ void NoteDisplay::DrawHoldPart(vector<Sprite*> &vpSpr,
bLast = true;
}
float curtsy= top_t;
if(top_t != bottom_t)
const float fYOffset = ArrowEffects::GetYOffsetFromYPos( m_pPlayerState, column_args.column, fY, m_fYReverseOffsetPixels );
float cur_beat= top_beat;
if(top_beat != bottom_beat)
{
curtsy= SCALE(fY, fYTop, fYBottom, top_t, bottom_t);
cur_beat= SCALE(fY, fYTop, fYBottom, top_beat, bottom_beat);
}
// Fun times ahead with vector math. If the notes are being moved by the
// position spline, the vectors used to position the edges of the strip
// need to be adjusted or the hold will vanish when the notes move
// horizontally.
// To accomplish this, we use the derivative at the current point from
// AE and the position spline. That gives us the forward vector for the
// strip, pointing to where the next center vert will be. (step 1)
// The vectors pointing left and right to the edges of the strip are
// obtained from the cross product of the forward vector and pos_z_vec.
// (unless the forward vec is too close to pos_z_vec or -pos_z_vec, in
// which case pos_y_vec is used) The result of a cross product is a
// vector perpendicular to both, so forward crossed with pos_z_vec gives
// us the left vector. Right is of course -left. (step 2)
// After that step, the left and right vectors need to be rotated around
// the forward vector axis by the y rotation value, to allow the hold to
// twist. (step 3)
// Steps will be labeled where they occur below. -Kyz
// TODO: Figure out whether it's worth the time investment to figure out
// a way to skip the complex vector handling if the spline is disabled.
vector<float> sp_pos;
vector<float> sp_pos_forward;
vector<float> sp_rot;
vector<float> sp_zoom;
vector<float> ae_pos(3, 0.0f);
vector<float> ae_rot(3, 0.0f);
// (step 1 of vector handling, part 1)
// ArrowEffects only contributes to the Y component of the vector to
// maintain the old behavior of how holds are drawn when they wave back
// and forth. -Kyz
RageVector3 render_forward(0.0f, 1.0f, 0.0f);
column_args.spae_pos_for_beat(m_pPlayerState, cur_beat,
fYOffset, m_fYReverseOffsetPixels, sp_pos, ae_pos);
// fX and fZ are sp_pos[0] + ae_pos[0] and sp_pos[2] + ae_pos[2]. -Kyz
// fY is the actual y position that should be used, not whatever spae
// fetched from ArrowEffects. -Kyz
switch(column_args.pos_handler->m_spline_mode)
{
case NCSM_Disabled:
ae_pos[1]= fY;
sp_pos_forward.resize(3);
sp_pos_forward[0]= sp_pos_forward[1]= sp_pos_forward[2]= 0.0f;
break;
case NCSM_Offset:
ae_pos[1]= fY;
column_args.pos_handler->EvalDerivForBeat(column_args.song_beat, cur_beat, sp_pos_forward);
VectorFloatNormalize(sp_pos_forward);
break;
case NCSM_Position:
ae_pos[1]= 0.0f;
render_forward.y= 0.0f;
column_args.pos_handler->EvalDerivForBeat(column_args.song_beat, cur_beat, sp_pos_forward);
VectorFloatNormalize(sp_pos_forward);
break;
}
render_forward.x+= sp_pos_forward[0];
render_forward.y+= sp_pos_forward[1];
render_forward.z+= sp_pos_forward[2];
// Normalize the vector so it'll be easy to test when determining whether
// to use pos_z_vec or pos_y_vec for the cross product in step 2.
RageVec3Normalize(&render_forward, &render_forward);
// Holds are only affected by the x axis of the zoom spline because they
// are flat sprites. -Kyz
float render_width= fFrameWidth;
switch(column_args.zoom_handler->m_spline_mode)
{
case NCSM_Disabled:
render_width= fFrameWidth * ae_zoom;
break;
case NCSM_Offset:
column_args.zoom_handler->EvalForBeat(column_args.song_beat, cur_beat, sp_zoom);
render_width= fFrameWidth * (ae_zoom + sp_zoom[0]);
break;
case NCSM_Position:
column_args.zoom_handler->EvalForBeat(column_args.song_beat, cur_beat, sp_zoom);
render_width= fFrameWidth * sp_zoom[0];
break;
}
vector<float> spline_pos;
args.spline->evaluate(curtsy, spline_pos);
const float fYOffset = ArrowEffects::GetYOffsetFromYPos( m_pPlayerState, iCol, fY, m_fYReverseOffsetPixels );
const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ) + spline_pos[2];
const float fFrameWidthScale = ArrowEffects::GetFrameWidthScale( m_pPlayerState, fYOffset, fOverlappedTime );
const float fScaledFrameWidth = fFrameWidth * fFrameWidthScale;
const float fScaledFrameWidth = render_width * fFrameWidthScale;
float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ) + spline_pos[0];
// Can't use the same code as for taps because hold bodies can only rotate
// around the y axis. -Kyz
switch(column_args.rot_handler->m_spline_mode)
{
case NCSM_Disabled:
// XXX: Actor rotations use degrees, Math uses radians. Convert here.
ae_rot[1]= ArrowEffects::GetRotationY(m_pPlayerState, fYOffset) * PI_180;
sp_rot.resize(3);
sp_rot[0]= sp_rot[1]= sp_rot[2]= 0.0f;
break;
case NCSM_Offset:
ae_rot[1]= ArrowEffects::GetRotationY(m_pPlayerState, fYOffset) * PI_180;
column_args.rot_handler->EvalForBeat(column_args.song_beat, cur_beat, sp_rot);
break;
case NCSM_Position:
column_args.rot_handler->EvalForBeat(column_args.song_beat, cur_beat, sp_rot);
break;
}
// XXX: Actor rotations use degrees, RageFastCos/Sin use radians. Convert here.
const float fRotationY = ArrowEffects::GetRotationY( m_pPlayerState, fYOffset ) * PI/180;
RageVector3 center_vert(sp_pos[0] + ae_pos[0],
sp_pos[1] + ae_pos[1], sp_pos[2] + ae_pos[2]);
// if we're rotating, we need to modify the X and Z coords for the outer edges.
const float fRotOffsetX = (fScaledFrameWidth/2) * RageFastCos(fRotationY);
const float fRotOffsetZ = (fScaledFrameWidth/2) * RageFastSin(fRotationY);
// Special case for hold caps, which have the same top and bottom beat.
if(top_beat == bottom_beat && fY != fYStartPos)
{
center_vert.x+= render_forward.x;
center_vert.y+= render_forward.y;
center_vert.z+= render_forward.z;
}
//const float fXLeft = fX - (fScaledFrameWidth/2);
const float fXLeft = fX - fRotOffsetX;
const float fXCenter = fX;
//const float fXRight = fX + (fScaledFrameWidth/2);
const float fXRight = fX + fRotOffsetX;
const float fZLeft = fZ - fRotOffsetZ;
const float fZCenter = fZ;
const float fZRight = fZ + fRotOffsetZ;
const float render_roty= (sp_rot[1] + ae_rot[1]);
// (step 2 of vector handling)
RageVector3 render_left;
if(abs(render_forward.z) > 0.9f) // 0.9 arbitrariliy picked.
{
RageVec3Cross(&render_left, &render_forward, &pos_y_vec);
}
else
{
RageVec3Cross(&render_left, &render_forward, &pos_z_vec);
}
RageAARotate(&render_left, &render_forward, render_roty);
const float half_width= fScaledFrameWidth * .5f;
render_left.x*= half_width;
render_left.y*= half_width;
render_left.z*= half_width;
RageVector3 const left_vert(center_vert.x + render_left.x,
center_vert.y + render_left.y, center_vert.z + render_left.z);
RageVector3 const right_vert(center_vert.x - render_left.x,
center_vert.y - render_left.y, center_vert.z - render_left.z);
const float fDistFromTop = fY - fYTop;
float fTexCoordTop = SCALE( fDistFromTop, 0, fFrameHeight, rect.top, rect.bottom );
fTexCoordTop += fAddToTexCoord;
const float fAlpha = ArrowGetAlphaOrGlow( bGlow, m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, args.draw_pixels_before_targets, args.fade_before_targets );
const float fAlpha = ArrowGetAlphaOrGlow( bGlow, m_pPlayerState, column_args.column, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, field_args.draw_pixels_before_targets, field_args.fade_before_targets );
const RageColor color = RageColor(fColorScale,fColorScale,fColorScale,fAlpha);
if( fAlpha > 0 )
bAllAreTransparent = false;
queue.v[0].p = RageVector3(fXLeft, fY + spline_pos[1], fZLeft); queue.v[0].c = color; queue.v[0].t = RageVector2(fTexCoordLeft, fTexCoordTop);
queue.v[1].p = RageVector3(fXCenter, fY + spline_pos[1], fZCenter); queue.v[1].c = color; queue.v[1].t = RageVector2(fTexCoordCenter, fTexCoordTop);
queue.v[2].p = RageVector3(fXRight, fY + spline_pos[1], fZRight); queue.v[2].c = color; queue.v[2].t = RageVector2(fTexCoordRight, fTexCoordTop);
queue.v[0].p = left_vert; queue.v[0].c = color; queue.v[0].t = RageVector2(fTexCoordLeft, fTexCoordTop);
queue.v[1].p = center_vert; queue.v[1].c = color; queue.v[1].t = RageVector2(fTexCoordCenter, fTexCoordTop);
queue.v[2].p = right_vert; queue.v[2].c = color; queue.v[2].t = RageVector2(fTexCoordRight, fTexCoordTop);
queue.v+=3;
if( queue.Free() < 3 || bLast )
@@ -753,10 +971,11 @@ void NoteDisplay::DrawHoldPart(vector<Sprite*> &vpSpr,
}
void NoteDisplay::DrawHoldBody(const TapNote& tn,
CommonColumnRenderArgs const& args, int iCol, float fBeat,
NoteFieldRenderArgs const& field_args,
NoteColumnRenderArgs const& column_args, float fBeat,
bool bIsBeingHeld, float fYHead, float fYTail, bool bIsAddition,
float fPercentFadeToFail, float fColorScale, bool bGlow,
float top_t, float bottom_t)
float top_beat, float bottom_beat)
{
vector<Sprite*> vpSprTop;
Sprite *pSpriteTop = GetHoldSprite( m_HoldTopCap, NotePart_HoldTopCap, fBeat, tn.subType == TapNoteSubType_Roll, bIsBeingHeld && !cache->m_bHoldActiveIsAddLayer );
@@ -780,7 +999,7 @@ void NoteDisplay::DrawHoldBody(const TapNote& tn,
vpSprBottom.push_back( pSprBottom );
}
const bool bReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(iCol) > 0.5f;
const bool bReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(column_args.column) > 0.5f;
bool bFlipHoldBody = bReverse && cache->m_bFlipHoldBodyWhenReverse;
if( bFlipHoldBody )
{
@@ -814,55 +1033,56 @@ void NoteDisplay::DrawHoldBody(const TapNote& tn,
const float fFrameHeightTop = pSpriteTop->GetUnzoomedHeight();
const float fFrameHeightBottom = pSpriteBottom->GetUnzoomedHeight();
float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, iCol,
args.draw_pixels_after_targets, m_fYReverseOffsetPixels );
float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, iCol,
args.draw_pixels_before_targets, m_fYReverseOffsetPixels );
float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, column_args.column,
field_args.draw_pixels_after_targets, m_fYReverseOffsetPixels );
float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, column_args.column,
field_args.draw_pixels_before_targets, m_fYReverseOffsetPixels );
if( bReverse )
swap( fYStartPos, fYEndPos );
bool bTopAnchor = bReverse && cache->m_bTopHoldAnchorWhenReverse;
// Draw the top cap
DrawHoldPart(vpSprTop, args, iCol, fYStep, fPercentFadeToFail,
DrawHoldPart(vpSprTop, field_args, column_args, fYStep, fPercentFadeToFail,
fColorScale, bGlow,
tn.HoldResult.fOverlappedTime,
fYHead-fFrameHeightTop, fYHead,
fYStartPos, fYEndPos,
false, bTopAnchor, bFlipHoldBody, top_t, top_t);
false, bTopAnchor, bFlipHoldBody, top_beat, top_beat);
// Draw the body
DrawHoldPart(vpSprBody, args, iCol, fYStep, fPercentFadeToFail,
DrawHoldPart(vpSprBody, field_args, column_args, fYStep, fPercentFadeToFail,
fColorScale, bGlow,
tn.HoldResult.fOverlappedTime,
fYHead, fYTail,
fYStartPos, fYEndPos,
true, bTopAnchor, bFlipHoldBody, top_t, bottom_t);
true, bTopAnchor, bFlipHoldBody, top_beat, bottom_beat);
// Draw the bottom cap
DrawHoldPart(vpSprBottom, args, iCol, fYStep, fPercentFadeToFail,
DrawHoldPart(vpSprBottom, field_args, column_args, fYStep, fPercentFadeToFail,
fColorScale, bGlow,
tn.HoldResult.fOverlappedTime,
fYTail, fYTail+fFrameHeightBottom,
max(fYStartPos, fYHead), fYEndPos,
false, bTopAnchor, bFlipHoldBody, bottom_t, bottom_t);
false, bTopAnchor, bFlipHoldBody, bottom_beat, bottom_beat);
}
void NoteDisplay::DrawHold(const TapNote& tn,
CommonColumnRenderArgs const& args, int iCol, int iRow, bool bIsBeingHeld,
NoteFieldRenderArgs const& field_args,
NoteColumnRenderArgs const& column_args, int iRow, bool bIsBeingHeld,
const HoldNoteResult &Result, bool bIsAddition, float fPercentFadeToFail)
{
int iEndRow = iRow + tn.iDuration;
float top_t= BeatToTValue(args, NoteRowToVisibleBeat(m_pPlayerState, iRow));
float bottom_t= BeatToTValue(args, NoteRowToVisibleBeat(m_pPlayerState, iEndRow));
float top_beat= NoteRowToVisibleBeat(m_pPlayerState, iRow);
float bottom_beat= NoteRowToVisibleBeat(m_pPlayerState, iEndRow);
if(bIsBeingHeld)
{
top_t= args.receptor_t;
top_beat= column_args.song_beat;
}
// bDrawGlowOnly is a little hacky. We need to draw the diffuse part and the glow part one pass at a time to minimize state changes
bool bReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(iCol) > 0.5f;
bool bReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(column_args.column) > 0.5f;
float fStartBeat = NoteRowToBeat( max(tn.HoldResult.iLastHeldRow, iRow) );
float fThrowAway = 0;
@@ -872,11 +1092,11 @@ void NoteDisplay::DrawHold(const TapNote& tn,
if( tn.HoldResult.bActive && tn.HoldResult.fLife > 0 )
; // use the default values filled in above
else
fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fStartBeat, fThrowAway, bStartIsPastPeak );
fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, column_args.column, fStartBeat, fThrowAway, bStartIsPastPeak );
float fEndPeakYOffset = 0;
bool bEndIsPastPeak = false;
float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, NoteRowToBeat(iEndRow), fEndPeakYOffset, bEndIsPastPeak );
float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, column_args.column, NoteRowToBeat(iEndRow), fEndPeakYOffset, bEndIsPastPeak );
// In boomerang, the arrows reverse direction at Y offset value fPeakAtYOffset.
// If fPeakAtYOffset lies inside of the hold we're drawing, then the we
@@ -889,8 +1109,8 @@ void NoteDisplay::DrawHold(const TapNote& tn,
if( bReverse )
swap( fStartYOffset, fEndYOffset );
const float fYHead = ArrowEffects::GetYPos( m_pPlayerState, iCol, fStartYOffset, m_fYReverseOffsetPixels );
const float fYTail = ArrowEffects::GetYPos( m_pPlayerState, iCol, fEndYOffset, m_fYReverseOffsetPixels );
const float fYHead = ArrowEffects::GetYPos( m_pPlayerState, column_args.column, fStartYOffset, m_fYReverseOffsetPixels );
const float fYTail = ArrowEffects::GetYPos( m_pPlayerState, column_args.column, fEndYOffset, m_fYReverseOffsetPixels );
const float fColorScale = SCALE( tn.HoldResult.fLife, 0.0f, 1.0f, cache->m_fHoldLetGoGrayPercent, 1.0f );
@@ -899,6 +1119,9 @@ void NoteDisplay::DrawHold(const TapNote& tn,
/* The body and caps should have no overlap, so their order doesn't matter.
* Draw the head last, so it appears on top. */
float fBeat = NoteRowToBeat(iRow);
// Side note: I don't know why these two checks were commented out and I
// didn't bother to update them when rewriting the arguments that are
// passed to the note drawing functions. -Kyz
/*
if( !cache->m_bHoldHeadIsAboveWavyParts )
{
@@ -912,8 +1135,8 @@ void NoteDisplay::DrawHold(const TapNote& tn,
}
*/
DrawHoldBody(tn, args, iCol, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, false, top_t, bottom_t);
DrawHoldBody(tn, args, iCol, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, true, top_t, bottom_t);
DrawHoldBody(tn, field_args, column_args, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, false, top_beat, bottom_beat);
DrawHoldBody(tn, field_args, column_args, fBeat, bIsBeingHeld, fYHead, fYTail, bIsAddition, fPercentFadeToFail, fColorScale, true, top_beat, bottom_beat);
/* These set the texture mode themselves. */
// this part was modified in pumpmania, where it flips the draw order
@@ -921,56 +1144,76 @@ void NoteDisplay::DrawHold(const TapNote& tn,
if( cache->m_bHoldTailIsAboveWavyParts )
{
Actor *pActor = GetHoldActor( m_HoldTail, NotePart_HoldTail, NoteRowToBeat(iRow), tn.subType == TapNoteSubType_Roll, bIsBeingHeld );
DrawActor(tn, pActor, NotePart_HoldTail, args, iCol, bFlipHeadAndTail ? fStartYOffset : fEndYOffset, fBeat, bIsAddition, fPercentFadeToFail, fColorScale, false);
DrawActor(tn, pActor, NotePart_HoldTail, field_args, column_args, bFlipHeadAndTail ? fStartYOffset : fEndYOffset, fBeat, bIsAddition, fPercentFadeToFail, fColorScale, false);
}
if( cache->m_bHoldHeadIsAboveWavyParts )
{
Actor *pActor = GetHoldActor( m_HoldHead, NotePart_HoldHead, NoteRowToBeat(iRow), tn.subType == TapNoteSubType_Roll, bIsBeingHeld );
DrawActor(tn, pActor, NotePart_HoldHead, args, iCol, bFlipHeadAndTail ? fEndYOffset : fStartYOffset, fBeat, bIsAddition, fPercentFadeToFail, fColorScale, bIsBeingHeld);
DrawActor(tn, pActor, NotePart_HoldHead, field_args, column_args, bFlipHeadAndTail ? fEndYOffset : fStartYOffset, fBeat, bIsAddition, fPercentFadeToFail, fColorScale, bIsBeingHeld);
}
}
void NoteDisplay::DrawActor(const TapNote& tn, Actor* pActor, NotePart part,
CommonColumnRenderArgs const& args, int iCol, float fYOffset, float fBeat,
NoteFieldRenderArgs const& field_args, NoteColumnRenderArgs const& column_args, float fYOffset, float fBeat,
bool bIsAddition, float fPercentFadeToFail, float fColorScale,
bool is_being_held)
{
if (tn.type == TapNoteType_AutoKeysound && !GAMESTATE->m_bInStepEditor) return;
if(fYOffset < args.draw_pixels_after_targets ||
fYOffset > args.draw_pixels_before_targets)
if(fYOffset < field_args.draw_pixels_after_targets ||
fYOffset > field_args.draw_pixels_before_targets)
return;
float spline_t= BeatToTValue(args, fBeat);
if(is_being_held) { spline_t= args.receptor_t; }
vector<float> spline_pos;
args.spline->evaluate(spline_t, spline_pos);
const float fY = ArrowEffects::GetYPos( m_pPlayerState, iCol, fYOffset, m_fYReverseOffsetPixels ) + spline_pos[1];
const float fX = ArrowEffects::GetXPos( m_pPlayerState, iCol, fYOffset ) + spline_pos[0];
const float fZ = ArrowEffects::GetZPos( m_pPlayerState, iCol, fYOffset ) + spline_pos[2];
const float fAlpha = ArrowEffects::GetAlpha( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, args.draw_pixels_before_targets, args.fade_before_targets );
const float fGlow = ArrowEffects::GetGlow( m_pPlayerState, iCol, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, args.draw_pixels_before_targets, args.fade_before_targets );
float spline_beat= fBeat;
if(is_being_held) { spline_beat= column_args.song_beat; }
const float fAlpha = ArrowEffects::GetAlpha( m_pPlayerState, column_args.column, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, field_args.draw_pixels_before_targets, field_args.fade_before_targets );
const float fGlow = ArrowEffects::GetGlow( m_pPlayerState, column_args.column, fYOffset, fPercentFadeToFail, m_fYReverseOffsetPixels, field_args.draw_pixels_before_targets, field_args.fade_before_targets );
const RageColor diffuse = RageColor(fColorScale,fColorScale,fColorScale,fAlpha);
const RageColor glow = RageColor(1,1,1,fGlow);
float fRotationX = 0, fRotationZ = 0;
const float fRotationY = ArrowEffects::GetRotationY( m_pPlayerState, fYOffset );
bool bIsHoldHead = tn.type == TapNoteType_HoldHead;
bool bIsHoldCap = bIsHoldHead || tn.type == TapNoteType_HoldTail;
fRotationZ = ArrowEffects::GetRotationZ( m_pPlayerState, fBeat, bIsHoldHead );
if( !bIsHoldCap )
{
fRotationX = ArrowEffects::GetRotationX( m_pPlayerState, fYOffset );
}
if( tn.type != TapNoteType_HoldHead )
fColorScale *= ArrowEffects::GetBrightness( m_pPlayerState, fBeat );
pActor->SetRotationX( fRotationX );
pActor->SetRotationY( fRotationY );
pActor->SetRotationZ( fRotationZ );
pActor->SetXY( fX, fY );
pActor->SetZ( fZ );
pActor->SetZoom( ArrowEffects::GetZoom(m_pPlayerState) );
// same logical structure as in UpdateReceptorGhostStuff, I just haven't
// figured out a good way to combine them. -Kyz
vector<float> sp_pos;
vector<float> sp_rot;
vector<float> sp_zoom;
vector<float> ae_pos(3, 0.0f);
vector<float> ae_rot(3, 0.0f);
vector<float> ae_zoom(3, 0.0f);
column_args.spae_pos_for_beat(m_pPlayerState, spline_beat,
fYOffset, m_fYReverseOffsetPixels, sp_pos, ae_pos);
switch(column_args.rot_handler->m_spline_mode)
{
case NCSM_Disabled:
if(!bIsHoldCap)
{
ae_rot[0]= ArrowEffects::GetRotationX(m_pPlayerState, fYOffset);
}
ae_rot[1]= ArrowEffects::GetRotationY(m_pPlayerState, fYOffset);
ae_rot[2]= ArrowEffects::GetRotationZ(m_pPlayerState, fBeat, bIsHoldHead);
sp_rot.resize(3);
sp_rot[0]= sp_rot[1]= sp_rot[2]= 0.0f;
break;
case NCSM_Offset:
if(!bIsHoldCap)
{
ae_rot[0]= ArrowEffects::GetRotationX(m_pPlayerState, fYOffset);
}
ae_rot[1]= ArrowEffects::GetRotationY(m_pPlayerState, fYOffset);
ae_rot[2]= ArrowEffects::GetRotationZ(m_pPlayerState, fBeat, bIsHoldHead);
column_args.rot_handler->EvalForBeat(column_args.song_beat, spline_beat, sp_rot);
break;
case NCSM_Position:
column_args.rot_handler->EvalForBeat(column_args.song_beat, spline_beat, sp_rot);
break;
}
column_args.spae_zoom_for_beat(m_pPlayerState, spline_beat, sp_zoom, ae_zoom);
column_args.SetPRZForActor(pActor, sp_pos, ae_pos, sp_rot, ae_rot, sp_zoom, ae_zoom);
// [AJ] this two lines (and how they're handled) piss off many people:
pActor->SetDiffuse( diffuse );
pActor->SetGlow( glow );
@@ -1004,7 +1247,8 @@ void NoteDisplay::DrawActor(const TapNote& tn, Actor* pActor, NotePart part,
}
void NoteDisplay::DrawTap(const TapNote& tn,
CommonColumnRenderArgs const& args, int iCol, float fBeat,
NoteFieldRenderArgs const& field_args,
NoteColumnRenderArgs const& column_args, float fBeat,
bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollStart,
bool bIsAddition, float fPercentFadeToFail)
{
@@ -1078,21 +1322,89 @@ void NoteDisplay::DrawTap(const TapNote& tn,
pActor->HandleMessage( msg );
}
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fBeat );
const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, column_args.column, fBeat );
// this is the line that forces the (1,1,1,x) part of the noteskin diffuse -aj
DrawActor(tn, pActor, part, args, iCol, fYOffset, fBeat, bIsAddition, fPercentFadeToFail, 1.0f, false);
DrawActor(tn, pActor, part, field_args, column_args, fYOffset, fBeat, bIsAddition, fPercentFadeToFail, 1.0f, false);
if( tn.type == TapNoteType_Attack )
pActor->PlayCommand( "UnsetAttack" );
}
void NoteColumnRenderer::UpdateReceptorGhostStuff(Actor* receptor) const
{
PlayerState const* player_state= m_field_render_args->player_state;
float song_beat= player_state->GetDisplayedPosition().m_fSongBeatVisible;
// sp_* will be filled with the settings from the splines.
// ae_* will be filled with the settings from ArrowEffects.
// The two together will be applied to the actor.
// sp_* will be zeroes in NCSM_Disabled, and ae_* will be zeroes in
// NCSM_Position, so the setting step won't have to check the mode. -Kyz
// sp_* are sized by the spline evaluate function.
vector<float> sp_pos;
vector<float> sp_rot;
vector<float> sp_zoom;
vector<float> ae_pos(3, 0.0f);
vector<float> ae_rot(3, 0.0f);
vector<float> ae_zoom(3, 0.0f);
switch(m_pos_handler.m_spline_mode)
{
case NCSM_Disabled:
ArrowEffects::GetXYZPos(player_state, m_column, 0, m_field_render_args->reverse_offset_pixels, ae_pos);
sp_pos.resize(3);
// Sure, resize is supposed to call the default constructor, and for
// numbers the default constructor is supposed to set it to zero, but
// I got bit for relying on that once. -Kyz
sp_pos[0]= sp_pos[1]= sp_pos[2]= 0.0f;
break;
case NCSM_Offset:
ArrowEffects::GetXYZPos(player_state, m_column, 0, m_field_render_args->reverse_offset_pixels, ae_pos);
m_pos_handler.EvalForReceptor(song_beat, sp_pos);
break;
case NCSM_Position:
m_pos_handler.EvalForReceptor(song_beat, sp_pos);
break;
}
switch(m_rot_handler.m_spline_mode)
{
case NCSM_Disabled:
ae_rot[2]= ArrowEffects::ReceptorGetRotationZ(player_state);
sp_rot.resize(3);
sp_rot[0]= sp_rot[1]= sp_rot[2]= 0.0f;
break;
case NCSM_Offset:
ae_rot[2]= ArrowEffects::ReceptorGetRotationZ(player_state);
m_rot_handler.EvalForReceptor(song_beat, sp_rot);
break;
case NCSM_Position:
m_rot_handler.EvalForReceptor(song_beat, sp_rot);
break;
}
switch(m_zoom_handler.m_spline_mode)
{
case NCSM_Disabled:
ae_zoom[0]= ae_zoom[1]= ae_zoom[2]= ArrowEffects::GetZoom(player_state);
sp_zoom.resize(3);
sp_zoom[0]= sp_zoom[1]= sp_zoom[2]= 0.0f;
break;
case NCSM_Offset:
ae_zoom[0]= ae_zoom[1]= ae_zoom[2]= ArrowEffects::GetZoom(player_state);
m_zoom_handler.EvalForReceptor(song_beat, sp_zoom);
break;
case NCSM_Position:
m_zoom_handler.EvalForReceptor(song_beat, sp_zoom);
break;
}
m_column_render_args.SetPRZForActor(receptor, sp_pos, ae_pos, sp_rot, ae_rot, sp_zoom, ae_zoom);
}
void NoteColumnRenderer::DrawPrimitives()
{
m_render_args->spline= &m_spline;
m_render_args->receptor_t= m_receptor_t;
m_render_args->beats_per_t= m_beats_per_t;
m_render_args->first_beat= NoteRowToBeat(m_render_args->first_row);
m_render_args->last_beat= NoteRowToBeat(m_render_args->last_row);
m_column_render_args.song_beat= m_field_render_args->player_state->GetDisplayedPosition().m_fSongBeatVisible;
m_column_render_args.pos_handler= &m_pos_handler;
m_column_render_args.rot_handler= &m_rot_handler;
m_column_render_args.zoom_handler= &m_zoom_handler;
m_field_render_args->first_beat= NoteRowToBeat(m_field_render_args->first_row);
m_field_render_args->last_beat= NoteRowToBeat(m_field_render_args->last_row);
bool any_upcoming= false;
// Build lists of holds and taps for each player number, then pass those
// lists to the displays to draw.
@@ -1101,8 +1413,8 @@ void NoteColumnRenderer::DrawPrimitives()
vector<vector<NoteData::TrackMap::const_iterator> > holds(PLAYER_INVALID+1);
vector<vector<NoteData::TrackMap::const_iterator> > taps(PLAYER_INVALID+1);
NoteData::TrackMap::const_iterator begin, end;
m_render_args->note_data->GetTapNoteRangeInclusive(m_column,
m_render_args->first_row, m_render_args->last_row+1, begin, end);
m_field_render_args->note_data->GetTapNoteRangeInclusive(m_column,
m_field_render_args->first_row, m_field_render_args->last_row+1, begin, end);
for(; begin != end; ++begin)
{
TapNote const& tn= begin->second;
@@ -1133,7 +1445,7 @@ void NoteColumnRenderer::DrawPrimitives()
#define DTS_INNER(pn, tap_set, draw_func, disp) \
if(!tap_set[pn].empty()) \
{ \
any_upcoming|= disp->draw_func(*m_render_args, m_column, tap_set[pn]); \
any_upcoming|= disp->draw_func(*m_field_render_args, m_column_render_args, tap_set[pn]); \
}
#define DRAW_TAP_SET(tap_set, draw_func) \
FOREACH_PlayerNumber(pn) \
@@ -1146,12 +1458,12 @@ void NoteColumnRenderer::DrawPrimitives()
DTS_INNER(PLAYER_INVALID, taps, DrawTapsInRange, m_displays[PLAYER_INVALID]);
#undef DTS_INNER
#undef DRAW_TAP_SET
m_render_args->receptor_row->SetNoteUpcoming(m_column, any_upcoming);
m_field_render_args->receptor_row->SetNoteUpcoming(m_column, any_upcoming);
}
#include "LuaBinding.h"
struct LunaNoteColumnRenderer : Luna<NoteColumnRenderer>
struct LunaNCSplineHandler : Luna<NCSplineHandler>
{
static int get_spline(T* p, lua_State* L)
{
@@ -1166,22 +1478,66 @@ struct LunaNoteColumnRenderer : Luna<NoteColumnRenderer>
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<NoteColumnSplineMode>(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<NoteColumnRenderer>
{
#define GET_HANDLER(member, name) \
static int name(T* p, lua_State* L) \
{ \
p->member.PushSelf(L); \
return 1; \
}
GET_HANDLER(m_pos_handler, get_pos_handler);
GET_HANDLER(m_rot_handler, get_rot_handler);
GET_HANDLER(m_zoom_handler, get_zoom_handler);
#undef GET_HANDLER
LunaNoteColumnRenderer()
{
ADD_METHOD(get_spline);
ADD_METHOD(get_receptor_t);
ADD_METHOD(get_beats_per_t);
ADD_METHOD(set_receptor_t);
ADD_METHOD(set_beats_per_t);
ADD_METHOD(get_pos_handler);
ADD_METHOD(get_rot_handler);
ADD_METHOD(get_zoom_handler);
}
};
LUA_REGISTER_DERIVED_CLASS(NoteColumnRenderer, Actor)
/*
* NoteColumnRenderer and associated spline stuff (c) Eric Reese 2014-2015
* (c) 2001-2006 Brian Bugh, Ben Nordstrom, Chris Danford, Steve Checkoway
* All rights reserved.
*