From f5a97236461f9c35be7b0082ef2354aa7972dcdb Mon Sep 17 00:00:00 2001 From: CrushedPixel Date: Tue, 21 Jul 2015 03:49:20 +0200 Subject: [PATCH] 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 --- .../replaymod/holders/AdvancedPosition.java | 12 ++++++- .../replaymod/holders/Marker.java | 13 +++++++ .../replaymod/holders/NumberValue.java | 13 +++++-- .../crushedpixel/replaymod/holders/Point.java | 13 +++++-- .../replaymod/holders/Position.java | 12 +++++-- .../replaymod/holders/TimestampValue.java | 12 +++++-- .../AdvancedPositionLinearInterpolation.java | 28 +++++++++++++++ .../AdvancedPositionSplineInterpolation.java | 28 +++++++++++++++ .../GenericSplineInterpolation.java | 8 +++-- .../replaymod/interpolation/KeyframeList.java | 5 +-- .../interpolation/KeyframeValue.java | 2 ++ .../replaymod/replay/ReplayHandler.java | 17 --------- .../replaymod/replay/ReplayProcess.java | 11 +++--- .../replaymod/utils/InterpolationUtils.java | 36 +++++++++++++++++++ .../replaymod/video/VideoRenderer.java | 9 ++--- 15 files changed, 177 insertions(+), 42 deletions(-) create mode 100644 src/main/java/eu/crushedpixel/replaymod/interpolation/AdvancedPositionLinearInterpolation.java create mode 100644 src/main/java/eu/crushedpixel/replaymod/interpolation/AdvancedPositionSplineInterpolation.java create mode 100644 src/main/java/eu/crushedpixel/replaymod/utils/InterpolationUtils.java diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/AdvancedPosition.java b/src/main/java/eu/crushedpixel/replaymod/holders/AdvancedPosition.java index e0924581..151701c3 100755 --- a/src/main/java/eu/crushedpixel/replaymod/holders/AdvancedPosition.java +++ b/src/main/java/eu/crushedpixel/replaymod/holders/AdvancedPosition.java @@ -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(); + } } diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/Marker.java b/src/main/java/eu/crushedpixel/replaymod/holders/Marker.java index bb13c13a..32071cb5 100644 --- a/src/main/java/eu/crushedpixel/replaymod/holders/Marker.java +++ b/src/main/java/eu/crushedpixel/replaymod/holders/Marker.java @@ -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(); + } + + @Override + public Interpolation getCubicInterpolator() { + return new GenericSplineInterpolation(); + } } diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/NumberValue.java b/src/main/java/eu/crushedpixel/replaymod/holders/NumberValue.java index feaa6762..49bb8d7e 100644 --- a/src/main/java/eu/crushedpixel/replaymod/holders/NumberValue.java +++ b/src/main/java/eu/crushedpixel/replaymod/holders/NumberValue.java @@ -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(); + } + + @Override + public Interpolation getCubicInterpolator() { + return new GenericSplineInterpolation(); + } + } diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/Point.java b/src/main/java/eu/crushedpixel/replaymod/holders/Point.java index 7b6aa006..ce80d378 100644 --- a/src/main/java/eu/crushedpixel/replaymod/holders/Point.java +++ b/src/main/java/eu/crushedpixel/replaymod/holders/Point.java @@ -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(); + } + + @Override + public Interpolation getCubicInterpolator() { + return new GenericSplineInterpolation(); + } } diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/Position.java b/src/main/java/eu/crushedpixel/replaymod/holders/Position.java index 1540fa64..3af67614 100644 --- a/src/main/java/eu/crushedpixel/replaymod/holders/Position.java +++ b/src/main/java/eu/crushedpixel/replaymod/holders/Position.java @@ -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(); + } + + @Override + public Interpolation getCubicInterpolator() { + return new GenericSplineInterpolation(); + } } diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/TimestampValue.java b/src/main/java/eu/crushedpixel/replaymod/holders/TimestampValue.java index f76b051d..73cfa0b2 100644 --- a/src/main/java/eu/crushedpixel/replaymod/holders/TimestampValue.java +++ b/src/main/java/eu/crushedpixel/replaymod/holders/TimestampValue.java @@ -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(); + } + + @Override + public Interpolation getCubicInterpolator() { + return new GenericSplineInterpolation(); + } } diff --git a/src/main/java/eu/crushedpixel/replaymod/interpolation/AdvancedPositionLinearInterpolation.java b/src/main/java/eu/crushedpixel/replaymod/interpolation/AdvancedPositionLinearInterpolation.java new file mode 100644 index 00000000..4be5fe12 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/interpolation/AdvancedPositionLinearInterpolation.java @@ -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 { + + @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); + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/interpolation/AdvancedPositionSplineInterpolation.java b/src/main/java/eu/crushedpixel/replaymod/interpolation/AdvancedPositionSplineInterpolation.java new file mode 100644 index 00000000..2e1d9030 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/interpolation/AdvancedPositionSplineInterpolation.java @@ -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 { + + @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); + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/interpolation/GenericSplineInterpolation.java b/src/main/java/eu/crushedpixel/replaymod/interpolation/GenericSplineInterpolation.java index 0b62976e..7b16127d 100755 --- a/src/main/java/eu/crushedpixel/replaymod/interpolation/GenericSplineInterpolation.java +++ b/src/main/java/eu/crushedpixel/replaymod/interpolation/GenericSplineInterpolation.java @@ -10,15 +10,16 @@ import java.util.Vector; public class GenericSplineInterpolation extends BasicSpline implements Interpolation { - private Field[] fields; + protected Field[] fields; - private Vector points; - private List> cubics = Collections.emptyList(); + protected Vector points; + protected List> cubics = Collections.emptyList(); public GenericSplineInterpolation() { this.points = new Vector(); } + @Override public void addPoint(T point) { this.points.add(point); @@ -56,6 +57,7 @@ public class GenericSplineInterpolation extends BasicSp } } + @Override public void applyPoint(float position, T toEdit) { Vector first = cubics.get(0); position = position * first.size(); diff --git a/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeList.java b/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeList.java index 96420ddc..d1f63c9a 100644 --- a/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeList.java +++ b/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeList.java @@ -165,12 +165,13 @@ public class KeyframeList extends ArrayList * 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() : new GenericSplineInterpolation(); + interpolation = linear ? first().getValue().getLinearInterpolator() : first().getValue().getCubicInterpolator(); for(Keyframe keyframe : this) { interpolation.addPoint(keyframe.getValue()); diff --git a/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeValue.java b/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeValue.java index c8a929b7..9720387b 100644 --- a/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeValue.java +++ b/src/main/java/eu/crushedpixel/replaymod/interpolation/KeyframeValue.java @@ -11,4 +11,6 @@ public interface KeyframeValue { KeyframeValue newInstance(); + Interpolation getLinearInterpolator(); + Interpolation getCubicInterpolator(); } diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java index 762cad71..81a8cddb 100755 --- a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java @@ -257,23 +257,6 @@ public class ReplayHandler { positionKeyframes.add(keyframe); selectKeyframe(keyframe); - Float a = null; - Float b; - - for(Keyframe 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(); } diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayProcess.java b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayProcess.java index 44eca2b2..181a09dc 100755 --- a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayProcess.java +++ b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayProcess.java @@ -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 motionSpline = null; - private static GenericLinearInterpolation motionLinear = null; + private static AdvancedPositionSplineInterpolation motionSpline = null; + private static AdvancedPositionLinearInterpolation motionLinear = null; private static GenericLinearInterpolation 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(); + motionSpline = new AdvancedPositionSplineInterpolation(); for(Keyframe 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(); + motionLinear = new AdvancedPositionLinearInterpolation(); for(Keyframe kf : ReplayHandler.getPositionKeyframes()) { motionLinear.addPoint(kf.getValue()); } diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/InterpolationUtils.java b/src/main/java/eu/crushedpixel/replaymod/utils/InterpolationUtils.java new file mode 100644 index 00000000..dad60af9 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/utils/InterpolationUtils.java @@ -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; + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java index f23d9948..d7c86f9a 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/VideoRenderer.java @@ -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(); + movement = new AdvancedPositionLinearInterpolation(); } else { - movement = new GenericSplineInterpolation(); + movement = new AdvancedPositionSplineInterpolation(); } time = new GenericLinearInterpolation();