Files
itgmania212121/src/CubicSpline.cpp
T
Kyzentun 2656123464 RageTextureManager now keeps a map of textures by pointer for quicker lookup when deleting.
RageTextureManager now keeps a map of textures that need updating, which is none of them because I couldn't find a class that inherits from RageTexture that doesn't have an empty Update function.
ArrowEffects now requires setting the current PlayerOptions before calling any functions.  This might make moving to per-column mods easier, and reduces the direct usage of PlayerState.
Tipsy calculations for each column are done in ArrowEffects::Update instead of GetYPos and GetYOffset.
DrawHold changed to only call DrawHoldBody once.  DrawHoldBody now does the normal and glow passes together instead of needing to be called twice.
DrawHoldPart changed to take most of its args in a struct.
Giant copy paste mess that NoteField was using to draw timing segment text replaced with a couple functions and a macro.
Selection glow for notefield is only calculated if a section is selected.
Added RageVector3 functions to CubicSplineN for NoteDisplay to use.
Actor effect period is cached now.  Sprites and Models cache their animation length now.
ReceptorArrowRow no longer calls ArrowEffects::Update in gameplay.
2015-04-24 15:55:54 -06:00

1052 lines
29 KiB
C++

#include "global.h"
#include "CubicSpline.h"
#include "RageLog.h"
#include "RageUtil.h"
#include <list>
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<float> diagonals;
vector<float> multiples;
};
void solve_diagonals_straight(vector<float>& diagonals, vector<float>& multiples);
void solve_diagonals_looped(vector<float>& diagonals, vector<float>& multiples);
private:
void prep_inner(size_t last, vector<float>& out);
bool find_in_cache(list<Entry>& cache, vector<float>& outd, vector<float>& outm);
void add_to_cache(list<Entry>& cache, vector<float>& outd, vector<float>& outm);
list<Entry> straight_diagonals;
list<Entry> looped_diagonals;
};
const size_t solution_cache_limit= 16;
bool SplineSolutionCache::find_in_cache(list<Entry>& cache, vector<float>& outd, vector<float>& outm)
{
size_t out_size= outd.size();
for(list<Entry>::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<Entry>& cache, vector<float>& outd, vector<float>& 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<float>& out)
{
for(size_t i= 1; i < last; ++i)
{
out[i]= 4.0f;
}
}
void SplineSolutionCache::solve_diagonals_straight(vector<float>& diagonals, vector<float>& 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<float>& diagonals, vector<float>& 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<float> 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<float> results(m_points.size());
vector<float> diagonals(m_points.size());
vector<float> 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<float> results(m_points.size());
vector<float> diagonals(m_points.size());
vector<float> 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<float>& 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<float>& diagonals, vector<float>& 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<float>(m_points.size());
while(t >= max_t) { t-= max_t; }
while(t < 0.0f) { t+= max_t; }
p= static_cast<size_t>(t);
tfrac= t - static_cast<float>(p);
}
else
{
int flort= static_cast<int>(t);
if(flort < 0)
{
p= 0;
tfrac= 0;
}
else if(static_cast<size_t>(flort) >= m_points.size() - 1)
{
p= m_points.size() - 1;
tfrac= 0;
}
else
{
p= static_cast<size_t>(flort);
tfrac= t - static_cast<float>(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<size_t>(
static_cast<float>(to_size - from_size) * between);
}
else if(to_size < from_size)
{
limit= from_size;
out_size= to_size + static_cast<size_t>(
static_cast<float>(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<float>& 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
#define CSN_EVAL_RV_SOMETHING(something) \
void CubicSplineN::something(float t, RageVector3& v) const \
{ \
ASSERT(m_splines.size() == 3); \
v.x= m_splines[0].something(t, m_loop); \
v.y= m_splines[1].something(t, m_loop); \
v.z= m_splines[2].something(t, m_loop); \
}
CSN_EVAL_RV_SOMETHING(evaluate);
CSN_EVAL_RV_SOMETHING(evaluate_derivative);
#undef CSN_EVAL_RV_SOMETHING
void CubicSplineN::set_point(size_t i, const vector<float>& 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<float>& b,
const vector<float>& c, const vector<float>& 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<float>& b,
vector<float>& c, vector<float>& 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<CubicSplineN>
{
static size_t dimension_index(T* p, lua_State* L, int s)
{
size_t i= static_cast<size_t>(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<size_t>(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<float> 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<float>& 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<float> 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<float> b; get_element_table_from_stack(p, L, s, limit, b);
vector<float> c; get_element_table_from_stack(p, L, s+1, limit, c);
vector<float> 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<vector<float> > 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<size_t>(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<size_t>(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.
*/