Further work on fixing keyframe path methods (still not working), and declared "momentum" functionality for replaying AnimationPaths through AnimatedComponents (not implemented yet). Added Javadocs.
This commit is contained in:
@@ -7,6 +7,8 @@ package org.openautonomousconnection.oacswing.animated;
|
|||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
// TODO: Implement pause functionality
|
||||||
|
// (preferably not interfering with play() always reliably playing the animation from the start; aka define it as "setPaused(bool)" or "pause()" and "unpause()")
|
||||||
public interface AnimatedComponent {
|
public interface AnimatedComponent {
|
||||||
void setCurrentRun(Timer timer);
|
void setCurrentRun(Timer timer);
|
||||||
Timer getCurrentRun();
|
Timer getCurrentRun();
|
||||||
@@ -16,7 +18,15 @@ public interface AnimatedComponent {
|
|||||||
|
|
||||||
void setBounds(int x, int y, int width, int height);
|
void setBounds(int x, int y, int width, int height);
|
||||||
|
|
||||||
default void play(double speed, boolean loop) {
|
//TODO: implement momentum
|
||||||
|
/**
|
||||||
|
* Plays this object's animation path
|
||||||
|
* @param speed how fast the animation should play
|
||||||
|
* @param loop if should the animation should loop
|
||||||
|
* @param momentum basically how far an object "shoots off" from a specified keyframe when stopping at proportional speed
|
||||||
|
* (linear keyframes are excluded from this)
|
||||||
|
*/
|
||||||
|
default void play(double speed, boolean loop, double momentum) {
|
||||||
AtomicInteger ticksPassed = new AtomicInteger();
|
AtomicInteger ticksPassed = new AtomicInteger();
|
||||||
|
|
||||||
// cloning the animation path to not mess with the original,
|
// cloning the animation path to not mess with the original,
|
||||||
@@ -29,8 +39,6 @@ public interface AnimatedComponent {
|
|||||||
|
|
||||||
this.setCurrentRun(new Timer(0, e -> {
|
this.setCurrentRun(new Timer(0, e -> {
|
||||||
if (!playedPath.anyMore()) {
|
if (!playedPath.anyMore()) {
|
||||||
System.out.println("called");
|
|
||||||
|
|
||||||
if (loop)
|
if (loop)
|
||||||
playedPath.reset();
|
playedPath.reset();
|
||||||
else
|
else
|
||||||
@@ -43,8 +51,6 @@ public interface AnimatedComponent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("still executing");
|
|
||||||
|
|
||||||
KeyFrame next = playedPath.getNext();
|
KeyFrame next = playedPath.getNext();
|
||||||
|
|
||||||
this.setBounds(next.position().x, next.position().y, next.width(), next.height());
|
this.setBounds(next.position().x, next.position().y, next.width(), next.height());
|
||||||
@@ -55,14 +61,33 @@ public interface AnimatedComponent {
|
|||||||
this.getCurrentRun().start();
|
this.getCurrentRun().start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays this object's animation path
|
||||||
|
* @param speed how fast the animation should play
|
||||||
|
* @param loop if should the animation should loop
|
||||||
|
*/
|
||||||
|
default void play(double speed, boolean loop) {
|
||||||
|
this.play(speed, loop, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays this object's animation path
|
||||||
|
* @param speed how fast the animation should play
|
||||||
|
*/
|
||||||
default void play(double speed) {
|
default void play(double speed) {
|
||||||
this.play(speed, false);
|
this.play(speed, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays this object's animation path
|
||||||
|
*/
|
||||||
default void play() {
|
default void play() {
|
||||||
this.play(1, false);
|
this.play(1, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops and resets replay of this object's animation path
|
||||||
|
*/
|
||||||
default void stop() {
|
default void stop() {
|
||||||
if(this.getCurrentRun() != null)
|
if(this.getCurrentRun() != null)
|
||||||
if(this.getCurrentRun().isRunning())
|
if(this.getCurrentRun().isRunning())
|
||||||
|
|||||||
@@ -84,28 +84,95 @@ public class AnimationPath extends ArrayList<KeyFrame> {
|
|||||||
|
|
||||||
double transition = this.linear(subIterator);
|
double transition = this.linear(subIterator);
|
||||||
|
|
||||||
KeyFrame.PathMethod method;
|
CombinedPathMethod method;
|
||||||
|
|
||||||
|
KeyFrame.PathMethod
|
||||||
|
currentMethod = current.pathMethod(),
|
||||||
|
nextMethod = next.pathMethod();
|
||||||
|
|
||||||
// Translate EASE_IN_AND_OUT to its respective currently relevant counterpart
|
// Translate EASE_IN_AND_OUT to its respective currently relevant counterpart
|
||||||
|
|
||||||
//TODO: non-linear keyframes can't behandled this way. This just makes it bug around
|
if(currentMethod.equals(KeyFrame.PathMethod.EASE_OUT) || currentMethod.equals(KeyFrame.PathMethod.EASE_IN_AND_OUT))
|
||||||
|
currentMethod = KeyFrame.PathMethod.EASE_OUT;
|
||||||
|
|
||||||
if(transition < 0.5)
|
if(nextMethod.equals(KeyFrame.PathMethod.EASE_OUT) || nextMethod.equals(KeyFrame.PathMethod.EASE_IN_AND_OUT))
|
||||||
method = current.pathMethod().equals(KeyFrame.PathMethod.EASE_IN_AND_OUT) ?
|
nextMethod = KeyFrame.PathMethod.EASE_OUT;
|
||||||
KeyFrame.PathMethod.EASE_OUT : current.pathMethod();
|
|
||||||
else
|
|
||||||
method = next.pathMethod().equals(KeyFrame.PathMethod.EASE_IN_AND_OUT) ?
|
|
||||||
KeyFrame.PathMethod.EASE_OUT : next.pathMethod();
|
|
||||||
|
|
||||||
// Else-case would be linear, which doesn't change anything
|
method = switch (currentMethod) {
|
||||||
|
case LINEAR -> switch (nextMethod) {
|
||||||
|
case LINEAR -> CombinedPathMethod.LINEAR;
|
||||||
|
case EASE_IN -> CombinedPathMethod.LINEAR_EASE_IN;
|
||||||
|
case EASE_OUT -> CombinedPathMethod.LINEAR_EASE_OUT;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + nextMethod);
|
||||||
|
};
|
||||||
|
|
||||||
if(method.equals(KeyFrame.PathMethod.EASE_IN))
|
case EASE_IN -> switch (nextMethod) {
|
||||||
transition = this.easeIn(subIterator);
|
case LINEAR -> CombinedPathMethod.EASE_IN_LINEAR;
|
||||||
|
case EASE_IN -> CombinedPathMethod.EASE_IN;
|
||||||
|
case EASE_OUT -> CombinedPathMethod.EASE_OUT_LINEAR;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + nextMethod);
|
||||||
|
};
|
||||||
|
|
||||||
else if(method.equals(KeyFrame.PathMethod.EASE_OUT))
|
case EASE_OUT -> switch (nextMethod) {
|
||||||
transition = this.easeOut(subIterator);
|
case LINEAR -> CombinedPathMethod.EASE_OUT_LINEAR;
|
||||||
|
case EASE_IN -> CombinedPathMethod.EASE_OUT_AND_IN;
|
||||||
|
case EASE_OUT -> CombinedPathMethod.EASE_OUT;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + nextMethod);
|
||||||
|
};
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + currentMethod);
|
||||||
|
};
|
||||||
|
|
||||||
return inBetween(current, next, transition, method);
|
boolean overHalf = transition > 0.5;
|
||||||
|
|
||||||
|
if(overHalf)
|
||||||
|
transition = 2 * transition - 1;
|
||||||
|
|
||||||
|
transition = switch (method) {
|
||||||
|
case LINEAR -> transition;
|
||||||
|
|
||||||
|
case LINEAR_EASE_IN -> overHalf ? easeIn(subIterator) : transition;
|
||||||
|
|
||||||
|
case EASE_IN -> easeIn(subIterator);
|
||||||
|
|
||||||
|
case EASE_IN_LINEAR -> overHalf ? transition : easeIn(subIterator);
|
||||||
|
|
||||||
|
case LINEAR_EASE_OUT -> overHalf ? easeOut(subIterator) : transition;
|
||||||
|
|
||||||
|
case EASE_OUT -> easeOut(subIterator);
|
||||||
|
|
||||||
|
case EASE_OUT_LINEAR -> overHalf ? transition : easeOut(subIterator);
|
||||||
|
|
||||||
|
case EASE_OUT_AND_IN -> overHalf ? easeOut(subIterator) : easeIn(subIterator);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
System.out.println(method + " " + transition + " - linear: " + linear(subIterator));
|
||||||
|
|
||||||
|
return inBetween(current, next, transition, overHalf);
|
||||||
|
|
||||||
|
|
||||||
|
// KeyFrame.PathMethod method;
|
||||||
|
//
|
||||||
|
// // Translate EASE_IN_AND_OUT to its respective currently relevant counterpart
|
||||||
|
//
|
||||||
|
// //TODO: non-linear keyframes can't behandled this way. This just makes it bug around
|
||||||
|
//
|
||||||
|
// if(transition < 0.5)
|
||||||
|
// method = current.pathMethod().equals(KeyFrame.PathMethod.EASE_IN_AND_OUT) ?
|
||||||
|
// KeyFrame.PathMethod.EASE_OUT : current.pathMethod();
|
||||||
|
// else
|
||||||
|
// method = next.pathMethod().equals(KeyFrame.PathMethod.EASE_IN_AND_OUT) ?
|
||||||
|
// KeyFrame.PathMethod.EASE_OUT : next.pathMethod();
|
||||||
|
//
|
||||||
|
// // Else-case would be linear, which doesn't change anything
|
||||||
|
//
|
||||||
|
// if(method.equals(KeyFrame.PathMethod.EASE_IN))
|
||||||
|
// transition = this.easeIn(subIterator);
|
||||||
|
//
|
||||||
|
// else if(method.equals(KeyFrame.PathMethod.EASE_OUT))
|
||||||
|
// transition = this.easeOut(subIterator);
|
||||||
|
//
|
||||||
|
// return inBetween(current, next, transition, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -122,6 +189,19 @@ public class AnimationPath extends ArrayList<KeyFrame> {
|
|||||||
return this.get(i);
|
return this.get(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get previous keyframe according to current path iterator, not depending on the current frame
|
||||||
|
* @return previous keyframe in order, not depending on the current frame
|
||||||
|
*/
|
||||||
|
public KeyFrame getPreviousInOrder() {
|
||||||
|
int i = this.animationIterator - 1;
|
||||||
|
|
||||||
|
if(i < 0)
|
||||||
|
i++;
|
||||||
|
|
||||||
|
return this.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if there are any more frames coming
|
* @return true if there are any more frames coming
|
||||||
*/
|
*/
|
||||||
@@ -171,14 +251,36 @@ public class AnimationPath extends ArrayList<KeyFrame> {
|
|||||||
return new Point(point.x - subtrahend.x, point.y - subtrahend.y);
|
return new Point(point.x - subtrahend.x, point.y - subtrahend.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate point between both points
|
||||||
|
* @param p1 first point
|
||||||
|
* @param p2 second point
|
||||||
|
* @return point in the middle between both points
|
||||||
|
*/
|
||||||
|
private static Point middle(Point p1, Point p2) {
|
||||||
|
return new Point(
|
||||||
|
multiply(
|
||||||
|
add(p1, p2),
|
||||||
|
0.5
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find in-between with given scalar
|
* Find in-between with given scalar
|
||||||
* @param kf1 first frame
|
* @param kf1 first frame
|
||||||
* @param kf2 next frame
|
* @param kf2 next frame
|
||||||
* @param scalar factor (ideally between 0 and 1, representing 0% transition to 100% transition)
|
* @param scalar factor (ideally between 0 and 1, representing 0% transition to 100% transition)
|
||||||
|
* @param overHalf if this inbetween is over halfway through the transition process
|
||||||
* @return in-between frame
|
* @return in-between frame
|
||||||
*/
|
*/
|
||||||
private static KeyFrame inBetween(KeyFrame kf1, KeyFrame kf2, double scalar, KeyFrame.PathMethod method) {
|
private static KeyFrame inBetween(KeyFrame kf1, KeyFrame kf2, double scalar, boolean overHalf) {
|
||||||
|
// create halfway marked point
|
||||||
|
if(overHalf)
|
||||||
|
kf1.position().setLocation(
|
||||||
|
middle(kf1.position(), kf2.position())
|
||||||
|
);
|
||||||
|
|
||||||
// position
|
// position
|
||||||
|
|
||||||
Point difference = subtract(kf2.position(), kf1.position());
|
Point difference = subtract(kf2.position(), kf1.position());
|
||||||
@@ -196,7 +298,7 @@ public class AnimationPath extends ArrayList<KeyFrame> {
|
|||||||
|
|
||||||
height = Math.toIntExact(Math.round(kf1.height() + dH * scalar));
|
height = Math.toIntExact(Math.round(kf1.height() + dH * scalar));
|
||||||
|
|
||||||
return new KeyFrame(pos, width, height, method);
|
return new KeyFrame(pos, width, height, kf2.pathMethod());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -209,10 +311,25 @@ public class AnimationPath extends ArrayList<KeyFrame> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private double easeIn(int subIterator) {
|
private double easeIn(int subIterator) {
|
||||||
return this.linear(subIterator/2) * this.linear(subIterator/2);
|
return this.linear(subIterator*2) * this.linear(subIterator*2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public double easeOut(int subIterator) {
|
public double easeOut(int subIterator) {
|
||||||
return Math.sqrt(this.linear(subIterator*2));
|
return Math.sqrt(this.linear(subIterator*2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumerator purely to describe two combined PathMethods, like a 2-dimensional PathMethod enum
|
||||||
|
*/
|
||||||
|
private enum CombinedPathMethod {
|
||||||
|
LINEAR,
|
||||||
|
LINEAR_EASE_IN,
|
||||||
|
EASE_IN,
|
||||||
|
EASE_IN_LINEAR,
|
||||||
|
LINEAR_EASE_OUT,
|
||||||
|
EASE_OUT,
|
||||||
|
EASE_OUT_LINEAR,
|
||||||
|
EASE_OUT_AND_IN;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class AnimationTests {
|
|||||||
|
|
||||||
frame.add(animatedPanel);
|
frame.add(animatedPanel);
|
||||||
|
|
||||||
animatedPanel.play(10, true);
|
animatedPanel.play(5, true);
|
||||||
|
|
||||||
frame.setVisible(true);
|
frame.setVisible(true);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user