Warning, GIGANTIC COMMIT!
Replaced Keyframe subclasses with Generic Types of Keyframe and replaced all instanceof calls Replaced implementations of Linear and Spline interpolation to interpolate any Object that extends KeyframeValue and contains at least one public double Field. I'll need this for Keyframe interpolation in CustomImageObject Transformations Made MarkerKeyframe *no* subclass of Keyframe to avoid conflicts with PositionKeyframe in instanceof checks for Keyframe#getValue Created KeyframeList which extends ArrayList to provide some helping functions which DRY up the ReplayHandler Split up ReplayHandler's keyframeList into timeKeyframeList and positionKeyframeList
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
package eu.crushedpixel.replaymod.interpolation;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GenericLinearInterpolation<T extends KeyframeValue> implements Interpolation<T> {
|
||||
|
||||
private Field[] fields;
|
||||
|
||||
protected List<T> points = new ArrayList<T>();
|
||||
|
||||
public GenericLinearInterpolation() {
|
||||
points = new ArrayList<T>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare() {}
|
||||
|
||||
public void addPoint(T point) {
|
||||
points.add(point);
|
||||
|
||||
List<Field> fields = new ArrayList<Field>();
|
||||
for(Field f : point.getClass().getDeclaredFields()) {
|
||||
if(Modifier.isPublic(f.getModifiers())) {
|
||||
fields.add(f);
|
||||
}
|
||||
}
|
||||
this.fields = fields.toArray(new Field[fields.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyPoint(float position, T toEdit) {
|
||||
if(points.isEmpty()) {
|
||||
throw new IllegalStateException("At least one Value needs to be added for this operation");
|
||||
}
|
||||
|
||||
//first, get previous and next T for given position
|
||||
float relative = position * (points.size()-1);
|
||||
int previousIndex = (int)Math.floor(relative);
|
||||
int nextIndex = (int)Math.ceil(relative);
|
||||
float percentage = relative - previousIndex;
|
||||
|
||||
T previous = points.get(previousIndex);
|
||||
T next = points.get(nextIndex);
|
||||
|
||||
for(Field f : fields) {
|
||||
try {
|
||||
f.set(toEdit, getInterpolatedValue(f.getDouble(previous), f.getDouble(next), percentage));
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double getInterpolatedValue(double val1, double val2, float perc) {
|
||||
return val1 + ((val2 - val1) * perc);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package eu.crushedpixel.replaymod.interpolation;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
public class GenericSplineInterpolation<T extends KeyframeValue> extends BasicSpline implements Interpolation<T> {
|
||||
|
||||
private Field[] fields;
|
||||
|
||||
private Vector<T> points;
|
||||
private Vector<Cubic>[] cubics = new Vector[0];
|
||||
|
||||
public GenericSplineInterpolation() {
|
||||
this.points = new Vector<T>();
|
||||
}
|
||||
|
||||
public void addPoint(T point) {
|
||||
this.points.add(point);
|
||||
|
||||
List<Field> fields = new ArrayList<Field>();
|
||||
for(Field f : point.getClass().getDeclaredFields()) {
|
||||
if(Modifier.isPublic(f.getModifiers())) {
|
||||
fields.add(f);
|
||||
}
|
||||
}
|
||||
this.fields = fields.toArray(new Field[fields.size()]);
|
||||
}
|
||||
|
||||
public Vector<T> getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare() {
|
||||
if(fields.length <= 0) {
|
||||
throw new IllegalStateException("The passed KeyframeValue class" +
|
||||
" has to contain at least one Field");
|
||||
}
|
||||
if(!points.isEmpty()) {
|
||||
cubics = new Vector[fields.length];
|
||||
for(int i=0; i<fields.length; i++) {
|
||||
cubics[i] = new Vector<Cubic>();
|
||||
try {
|
||||
calcNaturalCubic(points, fields[i], cubics[i]);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("At least one Value needs to be added" +
|
||||
" before preparing this Spline");
|
||||
}
|
||||
}
|
||||
|
||||
public void applyPoint(float position, T toEdit) {
|
||||
Vector<Cubic> first = cubics[0];
|
||||
position = position * first.size();
|
||||
int cubicNum = (int) Math.min(cubics[0].size() - 1, position);
|
||||
float cubicPos = (position - cubicNum);
|
||||
|
||||
int i = 0;
|
||||
for(Field f : fields) {
|
||||
try {
|
||||
f.set(toEdit, cubics[i].get(cubicNum).eval(cubicPos));
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package eu.crushedpixel.replaymod.interpolation;
|
||||
|
||||
public interface Interpolation<T> {
|
||||
public interface Interpolation<T extends KeyframeValue> {
|
||||
void prepare();
|
||||
T getPoint(float position);
|
||||
void applyPoint(float position, T toEdit);
|
||||
void addPoint(T pos);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
package eu.crushedpixel.replaymod.interpolation;
|
||||
|
||||
import eu.crushedpixel.replaymod.holders.Keyframe;
|
||||
import eu.crushedpixel.replaymod.holders.KeyframeComparator;
|
||||
import eu.crushedpixel.replaymod.holders.Position;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class KeyframeList<T extends Keyframe> extends ArrayList<T> {
|
||||
|
||||
private static final KeyframeComparator KEYFRAME_COMPARATOR = new KeyframeComparator();
|
||||
|
||||
@Override
|
||||
public boolean add(T t) {
|
||||
boolean success = super.add(t);
|
||||
sort();
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, T element) {
|
||||
super.add(index, element);
|
||||
sort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(int index) {
|
||||
T removed = super.remove(index);
|
||||
sort();
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
boolean success = super.remove(o);
|
||||
sort();
|
||||
return success;
|
||||
}
|
||||
|
||||
public void sort() {
|
||||
sort(KEYFRAME_COMPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first Keyframe that comes before a given value.
|
||||
* @param realTime The value to use
|
||||
* @return The first Keyframe prior to the given value
|
||||
*/
|
||||
public T getPreviousKeyframe(int realTime) {
|
||||
if(this.isEmpty()) return null;
|
||||
|
||||
T backup = null;
|
||||
|
||||
List<T> found = new ArrayList<T>();
|
||||
|
||||
for(T kf : this) {
|
||||
|
||||
if(kf.getRealTimestamp() < realTime) {
|
||||
found.add(kf);
|
||||
} else if(kf.getRealTimestamp() == realTime) {
|
||||
backup = kf;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(found.size() > 0)
|
||||
return found.get(found.size() - 1); //last element is nearest
|
||||
|
||||
else return backup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first Keyframe that comes after a given value.
|
||||
* @param realTime The value to use
|
||||
* @return The first Keyframe after the given value
|
||||
*/
|
||||
public T getNextKeyframe(int realTime) {
|
||||
if(this.isEmpty()) return null;
|
||||
|
||||
T backup = null;
|
||||
|
||||
for(T kf : this) {
|
||||
|
||||
if(kf.getRealTimestamp() > realTime) {
|
||||
return kf; //first found element is next
|
||||
} else if(kf.getRealTimestamp() == realTime) {
|
||||
backup = kf;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return backup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Keyframe that is closest to the given timestamp.
|
||||
* @param realTime The timestamp to start searching at
|
||||
* @param tolerance The threshold to allow for close Keyframes
|
||||
* @return The closest Keyframe, or null if no Keyframe within treshold
|
||||
*/
|
||||
public T getClosestKeyframeForTimestamp(int realTime, int tolerance) {
|
||||
List<T> found = new ArrayList<T>();
|
||||
for(T kf : this) {
|
||||
if(!(kf.getValue() instanceof Position)) continue;
|
||||
if(Math.abs(kf.getRealTimestamp() - realTime) <= tolerance) {
|
||||
found.add(kf);
|
||||
}
|
||||
}
|
||||
|
||||
T closest = null;
|
||||
|
||||
for(T kf : found) {
|
||||
if(closest == null || Math.abs(closest.getRealTimestamp() - realTime) > Math.abs(kf.getRealTimestamp() - realTime)) {
|
||||
closest = kf;
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
public T first() {
|
||||
if(isEmpty()) return null;
|
||||
return get(0);
|
||||
}
|
||||
|
||||
public T last() {
|
||||
if(isEmpty()) return null;
|
||||
return get(size()-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value between 0 and 1, representing the number that should be passed
|
||||
* to Interpolation#getValue() calls on this list of Keyframes.
|
||||
* @param timestamp The value to use
|
||||
* @return A value between 0 and 1
|
||||
*/
|
||||
public float getPositionOnSpline(int timestamp) {
|
||||
Keyframe previousKeyframe = getPreviousKeyframe(timestamp);
|
||||
Keyframe nextKeyframe = getNextKeyframe(timestamp);
|
||||
|
||||
int previousTimestamp = 0;
|
||||
int nextTimestamp = 0;
|
||||
|
||||
if(nextKeyframe != null || previousKeyframe != null) {
|
||||
if(nextKeyframe != null) {
|
||||
nextTimestamp = nextKeyframe.getRealTimestamp();
|
||||
} else {
|
||||
nextTimestamp = previousKeyframe.getRealTimestamp();
|
||||
}
|
||||
|
||||
if(previousKeyframe != null) {
|
||||
previousTimestamp = previousKeyframe.getRealTimestamp();
|
||||
} else {
|
||||
previousTimestamp = nextKeyframe.getRealTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
int currentPosDiff = nextTimestamp - previousTimestamp;
|
||||
int currentPos = timestamp - previousTimestamp;
|
||||
|
||||
float currentStepPercentage = (float) currentPos / (float) currentPosDiff;
|
||||
if(Float.isInfinite(currentStepPercentage)) currentStepPercentage = 0;
|
||||
|
||||
float value = (indexOf(previousKeyframe) + currentStepPercentage) /
|
||||
(float)(size() - 1);
|
||||
|
||||
return Math.max(0, Math.min(1, value));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package eu.crushedpixel.replaymod.interpolation;
|
||||
|
||||
/**
|
||||
* An abstract class that GenericSplineInterpolation can process.
|
||||
* Subclasses simply have to declare at least one <b>public</b> double field,
|
||||
* and the GenericSplineInterpolation will interpolate these.
|
||||
* <br><br>
|
||||
* It is recommended for KeyframeValue subclasses to have a @NoArgsConstructor annotation.
|
||||
*/
|
||||
public abstract class KeyframeValue {
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.interpolation;
|
||||
|
||||
import akka.japi.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class LinearInterpolation<K> implements Interpolation<K> {
|
||||
|
||||
protected List<K> points = new ArrayList<K>();
|
||||
|
||||
public LinearInterpolation() {
|
||||
points = new ArrayList<K>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare() {
|
||||
|
||||
}
|
||||
|
||||
public abstract K getPoint(float position);
|
||||
|
||||
public void addPoint(K point) {
|
||||
points.add(point);
|
||||
}
|
||||
|
||||
protected Pair<Float, Pair<K, K>> getCurrentPoints(float position) {
|
||||
if(points.size() == 0) return null;
|
||||
position = position * (points.size() - 1);
|
||||
int cubicNum = (int) Math.min(points.size() - 1, position);
|
||||
float cubicPos = (position - cubicNum);
|
||||
|
||||
if(cubicNum == points.size() - 1) {
|
||||
cubicNum--;
|
||||
cubicPos++;
|
||||
}
|
||||
|
||||
if(cubicNum < 0) {
|
||||
return new Pair<Float, Pair<K, K>>(cubicPos, new Pair<K, K>(points.get(cubicNum + 1), points.get(cubicNum + 1)));
|
||||
}
|
||||
return new Pair<Float, Pair<K, K>>(cubicPos, new Pair<K, K>(points.get(cubicNum), points.get(cubicNum + 1)));
|
||||
}
|
||||
|
||||
protected double getInterpolatedValue(double val1, double val2, float perc) {
|
||||
return val1 + ((val2 - val1) * perc);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.interpolation;
|
||||
|
||||
import akka.japi.Pair;
|
||||
import eu.crushedpixel.replaymod.holders.Position;
|
||||
|
||||
public class LinearPoint extends LinearInterpolation<Position> {
|
||||
|
||||
@Override
|
||||
public Position getPoint(float positionIn) {
|
||||
Pair<Float, Pair<Position, Position>> pair = getCurrentPoints(positionIn);
|
||||
if(pair == null) return null;
|
||||
|
||||
float perc = pair.first();
|
||||
|
||||
Position first = pair.second().first();
|
||||
Position second = pair.second().second();
|
||||
|
||||
double x = getInterpolatedValue(first.getX(), second.getX(), perc);
|
||||
double y = getInterpolatedValue(first.getY(), second.getY(), perc);
|
||||
double z = getInterpolatedValue(first.getZ(), second.getZ(), perc);
|
||||
|
||||
float pitch = (float)getInterpolatedValue(first.getPitch(), second.getPitch(), perc);
|
||||
float yaw = (float)getInterpolatedValue(first.getYaw(), second.getYaw(), perc);
|
||||
|
||||
float rot = (float)getInterpolatedValue(first.getRoll(), second.getRoll(), perc);
|
||||
|
||||
return new Position(x, y, z, pitch, yaw, rot);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.interpolation;
|
||||
|
||||
import akka.japi.Pair;
|
||||
|
||||
public class LinearTimestamp extends LinearInterpolation<Integer> {
|
||||
|
||||
@Override
|
||||
public Integer getPoint(float position) {
|
||||
Pair<Float, Pair<Integer, Integer>> pair = getCurrentPoints(position);
|
||||
if(pair == null) return null;
|
||||
|
||||
float perc = pair.first();
|
||||
|
||||
int first = pair.second().first();
|
||||
int second = pair.second().second();
|
||||
|
||||
return (int) getInterpolatedValue(first, second, perc);
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package eu.crushedpixel.replaymod.interpolation;
|
||||
|
||||
import eu.crushedpixel.replaymod.holders.Position;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Vector;
|
||||
|
||||
public class SplinePoint extends BasicSpline implements Interpolation<Position> {
|
||||
private Vector<Position> points;
|
||||
private Vector<Cubic> xCubics;
|
||||
private Vector<Cubic> yCubics;
|
||||
private Vector<Cubic> zCubics;
|
||||
private Vector<Cubic> pitchCubics;
|
||||
private Vector<Cubic> yawCubics;
|
||||
private Vector<Cubic> rotCubics;
|
||||
private Field vectorX;
|
||||
private Field vectorY;
|
||||
private Field vectorZ;
|
||||
private Field vectorPitch;
|
||||
private Field vectorYaw;
|
||||
private Field vectorRot;
|
||||
|
||||
public SplinePoint() {
|
||||
this.points = new Vector<Position>();
|
||||
|
||||
this.xCubics = new Vector<Cubic>();
|
||||
this.yCubics = new Vector<Cubic>();
|
||||
this.zCubics = new Vector<Cubic>();
|
||||
this.pitchCubics = new Vector<Cubic>();
|
||||
this.yawCubics = new Vector<Cubic>();
|
||||
this.rotCubics = new Vector<Cubic>();
|
||||
|
||||
try {
|
||||
vectorX = Position.class.getDeclaredField("x");
|
||||
vectorY = Position.class.getDeclaredField("y");
|
||||
vectorZ = Position.class.getDeclaredField("z");
|
||||
vectorPitch = Position.class.getDeclaredField("pitch");
|
||||
vectorYaw = Position.class.getDeclaredField("yaw");
|
||||
vectorRot = Position.class.getDeclaredField("roll");
|
||||
vectorX.setAccessible(true);
|
||||
vectorY.setAccessible(true);
|
||||
vectorZ.setAccessible(true);
|
||||
vectorPitch.setAccessible(true);
|
||||
vectorYaw.setAccessible(true);
|
||||
vectorRot.setAccessible(true);
|
||||
} catch(SecurityException e) {
|
||||
e.printStackTrace();
|
||||
} catch(NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void addPoint(Position point) {
|
||||
this.points.add(point);
|
||||
}
|
||||
|
||||
public Vector<Position> getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare() {
|
||||
try {
|
||||
calcNaturalCubic(points, vectorX, xCubics);
|
||||
calcNaturalCubic(points, vectorY, yCubics);
|
||||
calcNaturalCubic(points, vectorZ, zCubics);
|
||||
calcNaturalCubic(points, vectorPitch, pitchCubics);
|
||||
calcNaturalCubic(points, vectorYaw, yawCubics);
|
||||
calcNaturalCubic(points, vectorRot, rotCubics);
|
||||
} catch(IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch(IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch(InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public Position getPoint(float position) {
|
||||
position = position * xCubics.size();
|
||||
int cubicNum = (int) Math.min(xCubics.size() - 1, position);
|
||||
float cubicPos = (position - cubicNum);
|
||||
|
||||
return new Position(xCubics.get(cubicNum).eval(cubicPos),
|
||||
yCubics.get(cubicNum).eval(cubicPos),
|
||||
zCubics.get(cubicNum).eval(cubicPos),
|
||||
(float) pitchCubics.get(cubicNum).eval(cubicPos),
|
||||
(float) yawCubics.get(cubicNum).eval(cubicPos),
|
||||
(float) rotCubics.get(cubicNum).eval(cubicPos));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user