Added getLinearInterpolator() and getCubicInterpolator() methods to KeyframeValue interface to allow for specific Interpolators instead of the Generic Interpolators

Created InterpolationUtils class with a method to fix the Euler Rotation Break problem
Create AdvancedPositionSplineInterpolation and AdvancedPositionLinearInterpolation which always use the shortest possible paths between the Euler Rotation values of the Camera's Yaw and Roll
Remove Position Keyframe filtering when adding the Keyframe. This is now done by the AdvancedPositionInterpolation classes
This commit is contained in:
CrushedPixel
2015-07-21 03:49:20 +02:00
parent ff6b09dcba
commit f5a9723646
15 changed files with 177 additions and 42 deletions

View File

@@ -1,6 +1,6 @@
package eu.crushedpixel.replaymod.holders;
import eu.crushedpixel.replaymod.interpolation.Interpolate;
import eu.crushedpixel.replaymod.interpolation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@@ -69,4 +69,14 @@ public class AdvancedPosition extends Position {
public AdvancedPosition newInstance() {
return new AdvancedPosition();
}
@Override
public Interpolation getCubicInterpolator() {
return new AdvancedPositionSplineInterpolation();
}
@Override
public Interpolation getLinearInterpolator() {
return new AdvancedPositionLinearInterpolation();
}
}

View File

@@ -1,5 +1,8 @@
package eu.crushedpixel.replaymod.holders;
import eu.crushedpixel.replaymod.interpolation.GenericLinearInterpolation;
import eu.crushedpixel.replaymod.interpolation.GenericSplineInterpolation;
import eu.crushedpixel.replaymod.interpolation.Interpolation;
import eu.crushedpixel.replaymod.interpolation.KeyframeValue;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -17,4 +20,14 @@ public class Marker implements KeyframeValue {
public KeyframeValue newInstance() {
return new Marker();
}
@Override
public Interpolation getLinearInterpolator() {
return new GenericLinearInterpolation<Marker>();
}
@Override
public Interpolation getCubicInterpolator() {
return new GenericSplineInterpolation<Marker>();
}
}

View File

@@ -1,7 +1,6 @@
package eu.crushedpixel.replaymod.holders;
import eu.crushedpixel.replaymod.interpolation.Interpolate;
import eu.crushedpixel.replaymod.interpolation.KeyframeValue;
import eu.crushedpixel.replaymod.interpolation.*;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@@ -17,4 +16,14 @@ public class NumberValue implements KeyframeValue {
return new NumberValue();
}
@Override
public Interpolation getLinearInterpolator() {
return new GenericLinearInterpolation<NumberValue>();
}
@Override
public Interpolation getCubicInterpolator() {
return new GenericSplineInterpolation<NumberValue>();
}
}

View File

@@ -1,7 +1,6 @@
package eu.crushedpixel.replaymod.holders;
import eu.crushedpixel.replaymod.interpolation.Interpolate;
import eu.crushedpixel.replaymod.interpolation.KeyframeValue;
import eu.crushedpixel.replaymod.interpolation.*;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@@ -16,4 +15,14 @@ public class Point implements KeyframeValue {
public Point newInstance() {
return new Point();
}
@Override
public Interpolation getLinearInterpolator() {
return new GenericLinearInterpolation<Point>();
}
@Override
public Interpolation getCubicInterpolator() {
return new GenericSplineInterpolation<Point>();
}
}

View File

@@ -1,7 +1,6 @@
package eu.crushedpixel.replaymod.holders;
import eu.crushedpixel.replaymod.interpolation.Interpolate;
import eu.crushedpixel.replaymod.interpolation.KeyframeValue;
import eu.crushedpixel.replaymod.interpolation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -26,4 +25,13 @@ public class Position implements KeyframeValue {
this.z = pos.getZ();
}
@Override
public Interpolation getLinearInterpolator() {
return new GenericLinearInterpolation<Position>();
}
@Override
public Interpolation getCubicInterpolator() {
return new GenericSplineInterpolation<Position>();
}
}

View File

@@ -1,7 +1,6 @@
package eu.crushedpixel.replaymod.holders;
import eu.crushedpixel.replaymod.interpolation.Interpolate;
import eu.crushedpixel.replaymod.interpolation.KeyframeValue;
import eu.crushedpixel.replaymod.interpolation.*;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@@ -21,4 +20,13 @@ public class TimestampValue implements KeyframeValue {
return new TimestampValue();
}
@Override
public Interpolation getLinearInterpolator() {
return new GenericLinearInterpolation<TimestampValue>();
}
@Override
public Interpolation getCubicInterpolator() {
return new GenericSplineInterpolation<TimestampValue>();
}
}

View File

@@ -0,0 +1,28 @@
package eu.crushedpixel.replaymod.interpolation;
import eu.crushedpixel.replaymod.holders.AdvancedPosition;
import eu.crushedpixel.replaymod.utils.InterpolationUtils;
public class AdvancedPositionLinearInterpolation extends GenericLinearInterpolation<AdvancedPosition> {
@Override
public void addPoint(AdvancedPosition point) {
double normalizedYaw = (point.getYaw() + 180) % 360;
double normalizedRoll = (point.getRoll()) % 360;
if(!points.isEmpty()) {
AdvancedPosition last = points.get(points.size()-1);
double yaw = InterpolationUtils.fixEulerRotation(last.getYaw(), point.getYaw(), 180);
double roll = InterpolationUtils.fixEulerRotation(last.getRoll(), point.getRoll(), 0);
point.setYaw(yaw);
point.setRoll(roll);
} else {
point.setYaw(normalizedYaw-180);
point.setRoll(normalizedRoll);
}
super.addPoint(point);
}
}

View File

@@ -0,0 +1,28 @@
package eu.crushedpixel.replaymod.interpolation;
import eu.crushedpixel.replaymod.holders.AdvancedPosition;
import eu.crushedpixel.replaymod.utils.InterpolationUtils;
public class AdvancedPositionSplineInterpolation extends GenericSplineInterpolation<AdvancedPosition> {
@Override
public void addPoint(AdvancedPosition point) {
double normalizedYaw = (point.getYaw() + 180) % 360;
double normalizedRoll = (point.getRoll()) % 360;
if(!points.isEmpty()) {
AdvancedPosition last = points.get(points.size()-1);
double yaw = InterpolationUtils.fixEulerRotation(last.getYaw(), point.getYaw(), 180);
double roll = InterpolationUtils.fixEulerRotation(last.getRoll(), point.getRoll(), 0);
point.setYaw(yaw);
point.setRoll(roll);
} else {
point.setYaw(normalizedYaw-180);
point.setRoll(normalizedRoll);
}
super.addPoint(point);
}
}

View File

@@ -10,15 +10,16 @@ import java.util.Vector;
public class GenericSplineInterpolation<T extends KeyframeValue> extends BasicSpline implements Interpolation<T> {
private Field[] fields;
protected Field[] fields;
private Vector<T> points;
private List<Vector<Cubic>> cubics = Collections.emptyList();
protected Vector<T> points;
protected List<Vector<Cubic>> cubics = Collections.emptyList();
public GenericSplineInterpolation() {
this.points = new Vector<T>();
}
@Override
public void addPoint(T point) {
this.points.add(point);
@@ -56,6 +57,7 @@ public class GenericSplineInterpolation<T extends KeyframeValue> extends BasicSp
}
}
@Override
public void applyPoint(float position, T toEdit) {
Vector<Cubic> first = cubics.get(0);
position = position * first.size();

View File

@@ -165,12 +165,13 @@ public class KeyframeList<K extends KeyframeValue> extends ArrayList<Keyframe<K>
* Recalculates the underlying Interpolation instances.
* @param linear Whether to prepare linear or cubic interpolation
*/
@SuppressWarnings("unchecked")
public void recalculate(boolean linear) {
previousCallLinear = linear;
if(isEmpty()) return;
if(size() < 2) return;
interpolation = linear ? new GenericLinearInterpolation<K>() : new GenericSplineInterpolation<K>();
interpolation = linear ? first().getValue().getLinearInterpolator() : first().getValue().getCubicInterpolator();
for(Keyframe<K> keyframe : this) {
interpolation.addPoint(keyframe.getValue());

View File

@@ -11,4 +11,6 @@ public interface KeyframeValue {
KeyframeValue newInstance();
Interpolation getLinearInterpolator();
Interpolation getCubicInterpolator();
}

View File

@@ -257,23 +257,6 @@ public class ReplayHandler {
positionKeyframes.add(keyframe);
selectKeyframe(keyframe);
Float a = null;
Float b;
for(Keyframe<AdvancedPosition> kf : positionKeyframes) {
AdvancedPosition pos = kf.getValue();
b = (float)pos.getYaw() % 360;
if(a != null) {
float diff = b-a;
if(Math.abs(diff) > 180) {
b = a - (360 - diff) % 360;
pos.setYaw(b);
kf.setValue(pos);
}
}
a = b;
}
fireKeyframesModifyEvent();
}

View File

@@ -5,8 +5,9 @@ import eu.crushedpixel.replaymod.chat.ChatMessageHandler.ChatMessageType;
import eu.crushedpixel.replaymod.holders.AdvancedPosition;
import eu.crushedpixel.replaymod.holders.Keyframe;
import eu.crushedpixel.replaymod.holders.TimestampValue;
import eu.crushedpixel.replaymod.interpolation.AdvancedPositionLinearInterpolation;
import eu.crushedpixel.replaymod.interpolation.AdvancedPositionSplineInterpolation;
import eu.crushedpixel.replaymod.interpolation.GenericLinearInterpolation;
import eu.crushedpixel.replaymod.interpolation.GenericSplineInterpolation;
import eu.crushedpixel.replaymod.settings.RenderOptions;
import eu.crushedpixel.replaymod.timer.EnchantmentTimer;
import eu.crushedpixel.replaymod.timer.ReplayTimer;
@@ -31,8 +32,8 @@ public class ReplayProcess {
private static boolean linear = false;
private static GenericSplineInterpolation<AdvancedPosition> motionSpline = null;
private static GenericLinearInterpolation<AdvancedPosition> motionLinear = null;
private static AdvancedPositionSplineInterpolation motionSpline = null;
private static AdvancedPositionLinearInterpolation motionLinear = null;
private static GenericLinearInterpolation<TimestampValue> timeLinear = null;
private static double previousReplaySpeed = 0;
@@ -176,7 +177,7 @@ public class ReplayProcess {
if(!linear && motionSpline == null) {
//set up spline path
motionSpline = new GenericSplineInterpolation<AdvancedPosition>();
motionSpline = new AdvancedPositionSplineInterpolation();
for(Keyframe<AdvancedPosition> kf : ReplayHandler.getPositionKeyframes()) {
motionSpline.addPoint(kf.getValue());
}
@@ -184,7 +185,7 @@ public class ReplayProcess {
if(linear && motionLinear == null) {
//set up linear path
motionLinear = new GenericLinearInterpolation<AdvancedPosition>();
motionLinear = new AdvancedPositionLinearInterpolation();
for(Keyframe<AdvancedPosition> kf : ReplayHandler.getPositionKeyframes()) {
motionLinear.addPoint(kf.getValue());
}

View File

@@ -0,0 +1,36 @@
package eu.crushedpixel.replaymod.utils;
public class InterpolationUtils {
/**
* Note: I invented the word "Euler break". If there are any better suggestions, let me know.
* @param first The previous, fixed Rotation value
* @param second The new Rotation value
* @param eulerBreak The Euler break, e.g. 180 for Minecraft's Camera Yaw
* @return The new Rotation value, modified to make the Interpolation algorithms
* find the closest path between two Euler Rotation values
*/
public static double fixEulerRotation(double first, double second, int eulerBreak) {
if(first == second) return first;
//converting the values to values between 0 and 359,
//essentially moving the euler break to 0
double normalizedFirst = (first + eulerBreak) % 360;
double normalizedSecond = (second + eulerBreak) % 360;
//the difference between the rotation values
//if using the "conventional" path
double pathDifference = Math.abs(normalizedSecond-normalizedFirst);
int factor = normalizedSecond > normalizedFirst ? 1 : -1;
//if the "conventional" path takes more than half the rotation,
//use the path crossing the euler break
if(pathDifference > 180) {
//invert the path difference to rotate in the other direction
pathDifference = -1*(360-pathDifference);
}
return first + factor*pathDifference;
}
}

View File

@@ -6,10 +6,7 @@ import eu.crushedpixel.replaymod.gui.GuiVideoRenderer;
import eu.crushedpixel.replaymod.holders.AdvancedPosition;
import eu.crushedpixel.replaymod.holders.Keyframe;
import eu.crushedpixel.replaymod.holders.TimestampValue;
import eu.crushedpixel.replaymod.interpolation.GenericLinearInterpolation;
import eu.crushedpixel.replaymod.interpolation.GenericSplineInterpolation;
import eu.crushedpixel.replaymod.interpolation.Interpolation;
import eu.crushedpixel.replaymod.interpolation.KeyframeList;
import eu.crushedpixel.replaymod.interpolation.*;
import eu.crushedpixel.replaymod.renderer.ChunkLoadingRenderGlobal;
import eu.crushedpixel.replaymod.replay.ReplayHandler;
import eu.crushedpixel.replaymod.replay.ReplaySender;
@@ -146,9 +143,9 @@ public class VideoRenderer implements RenderInfo {
fps = options.getFps();
if (options.isLinearMovement()) {
movement = new GenericLinearInterpolation<AdvancedPosition>();
movement = new AdvancedPositionLinearInterpolation();
} else {
movement = new GenericSplineInterpolation<AdvancedPosition>();
movement = new AdvancedPositionSplineInterpolation();
}
time = new GenericLinearInterpolation<TimestampValue>();