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();