package toritools.pathing.interpolator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import toritools.math.Vector2;
/**
* Interpolate a path based on keyframes.
*
* @author toriscope
*
*/
public class HermiteKeyFrameInterpolator {
private final List<HermiteKeyFrame> keyFrames;
/**
* Spawn an interpolator, can be used as a pathing mechanism for smooth
* animations.
*
* @param keyFrames
* the list of keyframes. They will be copied and sorted into
* proper time order.
* @throws RuntimeException
* if less than two keyframes are provided, this will be thrown
* with proper error message.
*/
public HermiteKeyFrameInterpolator(final List<HermiteKeyFrame> keyFrames) {
if (keyFrames.size() < 2)
throw new RuntimeException("Please include at least two keyframes!");
this.keyFrames = new ArrayList<HermiteKeyFrame>(keyFrames);
Collections.sort(this.keyFrames);
}
public HermiteKeyFrameInterpolator(final HermiteKeyFrame ... keyFrames) {
this(Arrays.asList(keyFrames));
}
public float getStartTime() {
return keyFrames.get(0).time;
}
public float getEndTime() {
return keyFrames.get(keyFrames.size() - 1).time;
}
public void scaleTime(final float scalar) {
for (HermiteKeyFrame keyframe : keyFrames) {
keyframe.time *= scalar;
}
}
/**
* Get the relative position for a given time.
*
* @param time
* the time to fetch the interpolated position for.
* @return the relative position at time.
*/
public Vector2 getPositionDeltaAtTime(final float time) {
// No Keyframes
if (keyFrames.isEmpty())
return new Vector2();
// Time below frames.
if (time <= keyFrames.get(0).time)
return keyFrames.get(0).pos;
// Time above frames.
if (time >= keyFrames.get(keyFrames.size() - 1).time)
return keyFrames.get(keyFrames.size() - 1).pos;
for (int i = 1; i < keyFrames.size(); i++) {
if (time >= keyFrames.get(i - 1).time
&& time <= keyFrames.get(i).time) {
return getPositionInterpolated((time - keyFrames.get(i - 1).time)
/ (keyFrames.get(i).time - keyFrames.get(i - 1).time),
keyFrames.get(i - 1), keyFrames.get(i));
}
}
throw new RuntimeException("A time " + time
+ " could not be found in the keyframes.");
}
private Vector2 getPositionInterpolated(float s, final HermiteKeyFrame p1,
final HermiteKeyFrame p2) {
Vector2 r1 = p1.pos.scale(HermiteBasisEquation.h1(s));
Vector2 r2 = p2.pos.scale(HermiteBasisEquation.h2(s));
Vector2 r3 = p1.mag.scale(HermiteBasisEquation.h3(s));
Vector2 r4 = p2.mag.scale(HermiteBasisEquation.h4(s));
return r1.add(r2).add(r3).add(r4);
}
/**
* A simple keyframe with a position, direction at position, and a time.
* Comparable by time.
*
* @author toriscope
*
*/
public static class HermiteKeyFrame implements Comparable<HermiteKeyFrame> {
public Vector2 pos;
public Vector2 mag;
public float time;
/**
* A keyFrame for a hermite curve path.
*
* @param pos
* the position at the given time.
* @param mag
* the direction and magnitude at that time, essentially the
* point handle.
* @param time
* the time.
*/
public HermiteKeyFrame(final Vector2 pos, final Vector2 mag,
final float time) {
this.pos = pos;
this.mag = mag;
this.time = time;
}
/**
* A keyFrame for a hermite curve path. No velocity handle.
*
* @param pos
* the position at the given time.
* @param time
* the time.
*/
public HermiteKeyFrame(final Vector2 pos, final float time) {
this(pos, new Vector2(), time);
}
public HermiteKeyFrame clone() {
return new HermiteKeyFrame(pos, mag, time);
}
@Override
public int compareTo(HermiteKeyFrame other) {
if (this.time > other.time)
return 1;
else if (this.time < other.time)
return -1;
else
return 0;
}
}
/**
* The 2D hermite basis equations.
*
* @author toriscope
*
*/
private static class HermiteBasisEquation {
/**
* h1(s) = 2s^3 - 3s^2 + 1
*/
public static float h1(final float s) {
return (float) (2 * Math.pow(s, 3) - 3 * Math.pow(s, 2) + 1);
}
/**
* h2(s) = -2s^3 + 3s^2
*/
public static float h2(final float s) {
return (float) (-2 * Math.pow(s, 3) + 3 * Math.pow(s, 2));
}
/**
* h3(s) = s^3 - 2s^2 + s
*/
public static float h3(final float s) {
return (float) (Math.pow(s, 3) - 2 * Math.pow(s, 2) + s);
}
/**
* h4(s) = s^3 - s^2
*/
public static float h4(final float s) {
return (float) (Math.pow(s, 3) - Math.pow(s, 2));
}
}
}