package edu.gatech.cs2340.trydent.math.curve;
import edu.gatech.cs2340.trydent.TrydentException;
/**
* Represents a parametric spline of points. (Advanced functionality).
*
* @param <P>
* the type of the control points of this spline
*/
public class SplineCurve<P> extends Curve<P> {
private ArrayPointStream<P> pointStream;
private Interpolation<P> interpolation;
private SplineTimingHandler timing;
private TimeWrapMode timeWrap;
/**
* Creates a new spline from the input points where the input points are
* spaced evenly apart in /time/, in which the duration is set to 1.0.
*
* @param interpolation
* interpolation strategy to use for constructing the spans of
* the spline.
* @param indexWrap
* bounds-handling strategy to use for managing indices outside
* the range 0 to # points-1.
* @param points
* array of points to interpolate, such that given points (P0,
* P1, P2, ..., Pn-1), the spline will pass through each point Pi
* at time i/(n-1).
*/
public SplineCurve(Interpolation<P> interpolation, IndexWrapMode indexWrap, P[] points) {
this.pointStream = new ArrayPointStream<P>(points, indexWrap);
this.interpolation = interpolation;
this.timing = new UniformTiming(points.length);
this.timeWrap = TimeWrapMode.WRAP;
}
/**
* Creates a new spline from the input points where the points are spaced
* apart arbitrarily far apart in time by the durations given in the
* durations array.
*
* @param interpolation
* interpolation strategy to use for constructing the spans of
* the spline.
* @param bounds
* bounds-handling strategy to use for managing times outside the
* range 0.0 to 1.0.
* @param points
* array of points to interpolate.
* @param durations
* array of scalar durations, such that it takes duration[i] time
* to move from points[i] to points[i+1]. The durations are
* expected to sum to 1.0; if they do not, the array will be
* normalize to force the duration to be 1.0.
*/
public SplineCurve(Interpolation<P> interpolation, IndexWrapMode bounds, P[] points, double[] durations) {
double sum = 0;
for (double d : durations) {
sum += d;
}
if (sum != 1.0) {
if (sum <= 0)
throw new TrydentException("Durations must sum to a positive number.");
double[] normalized = new double[durations.length];
for (int i = 0; i < normalized.length; i++) {
normalized[i] = durations[i] * 1.0 / sum;
}
durations = normalized;
}
this.pointStream = new ArrayPointStream<>(points, bounds);
this.interpolation = interpolation;
this.timing = new ArbitraryTiming(durations);
this.timeWrap = TimeWrapMode.WRAP;
}
/**
* Sets the interpolation strategy used by this spline.
*
* @param interpolation
* typically one of the constants in Interpolation
*/
public void setInterpolation(Interpolation<P> interpolation) {
this.interpolation = interpolation;
}
/**
* Sets the index wrapping used by this spline.
*
* @param handler
* typically one of the constants in IndexWrapMode
*/
public void setIndexWrap(IndexWrapMode handler) {
this.pointStream.setBoundsHandler(handler);
}
/**
* Sets the time wrapping used by this spline.
*
* @param mode
* typically one of the constants in TimeWrapMode
*/
public void setTimeWrap(TimeWrapMode mode) {
this.timeWrap = mode;
}
@Override
public P sample(double t) {
// There is some subtle wrapping behavior occurring in this function.
// Our first step is to wrap the floating-point time value, so that
// our TimingHandler can correctly translate the time value to a span
// and a sub-time of that span. The TimingHandler requires its input
// to be between 0 and 1.
t = this.timeWrap.handle(t, 1.0);
SpanTime time = timing.transformTime(t);
pointStream.seek(time.getIndex());
// Next, the actual interpolation uses its IndexWrapMode to find the
// adjacent keyframe points for use in interpolation. This is important
// because an interpolation near the boundary of the spline may need
// to get extra points before or after it that are outside the normal
// bounds of the stream.
// This behavior is handled by the pointStream, which wraps according to
// its IndexWrapMode.
return interpolation.interpolate(time.getTime(), pointStream);
}
}