Files
OAC-Swing/src/main/java/org/openautonomousconnection/oacswing/animated/AnimationPath.java

219 lines
6.3 KiB
Java
Raw Normal View History

2026-02-07 18:54:19 +01:00
/* Author: Maple
* Jan. 24 2026
* */
2026-01-24 12:04:55 +01:00
package org.openautonomousconnection.oacswing.animated;
import lombok.Getter;
import lombok.Setter;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class AnimationPath extends ArrayList<KeyFrame> {
@Getter @Setter
private int inbetweens;
private int animationIterator = 0;
private int subFrameIterator = 0;
public AnimationPath(int inbetweens, Collection<KeyFrame> keyFrames) {
this.addAll(keyFrames);
this.inbetweens = inbetweens;
}
public AnimationPath(int inbetweens, KeyFrame... keyFrames) {
this.addAll(Arrays.stream(keyFrames).toList());
this.inbetweens = inbetweens;
}
public AnimationPath(int inbetweens) {
this.inbetweens = inbetweens;
}
/**
* Get next keyframe according to current path iterator, depending on the current frame without increasing the animation iterators
* @return next keyframe in order, depending on the current frame
*/
public KeyFrame peekNext() {
int tempSubIterator = this.subFrameIterator, tempAnimationIterator = this.animationIterator;
if(tempSubIterator >= this.inbetweens) {
tempSubIterator = 0;
tempAnimationIterator++;
}
if(tempAnimationIterator >= this.size())
return null;
tempSubIterator++;
KeyFrame current = this.get(tempAnimationIterator);
KeyFrame next = this.getNextInOrder();
return getKeyFrame(current, next, tempSubIterator);
}
2026-01-24 12:04:55 +01:00
/**
* Get next keyframe according to current path iterator, depending on the current frame, and increase the animation iterators
2026-01-24 12:04:55 +01:00
* @return next keyframe in order, depending on the current frame
*/
public KeyFrame getNext() {
if(this.subFrameIterator >= this.inbetweens) {
this.subFrameIterator = 0;
this.animationIterator++;
}
if(this.animationIterator >= this.size())
return null;
this.subFrameIterator++;
KeyFrame current = this.get(this.animationIterator);
KeyFrame next = this.getNextInOrder();
return getKeyFrame(current, next, this.subFrameIterator);
}
private KeyFrame getKeyFrame(KeyFrame current, KeyFrame next, int subIterator) {
2026-01-24 12:04:55 +01:00
// How far the transition should be finished
double transition = this.linear(subIterator);
KeyFrame.PathMethod method;
// Translate EASE_IN_AND_OUT to its respective currently relevant counterpart
2026-01-24 12:04:55 +01:00
//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);
2026-01-24 12:04:55 +01:00
}
/**
* Get next keyframe according to current path iterator, not depending on the current frame
* @return next keyframe in order, not depending on the current frame
*/
public KeyFrame getNextInOrder() {
int i = this.animationIterator + 1;
if(i >= this.size())
i--;
return this.get(i);
}
/**
* @return true if there are any more frames coming
*/
public boolean anyMore() {
return this.animationIterator + 1 < this.size();
}
2026-01-24 12:04:55 +01:00
/**
* Reset animation path iterator to start
*/
public void reset() {
this.subFrameIterator = 0;
this.animationIterator = 0;
}
/**
* Utility method needed to get in-betweens
* @param point point to multiply
* @param scalar scalar to multiply with
* @return scalar product point
*/
private static Point multiply(Point point, double scalar) {
int x = Math.toIntExact(Math.round(point.x * scalar));
int y = Math.toIntExact(Math.round(point.y * scalar));
return new Point(x, y);
}
/**
* Add two points together; also needed for in-betweens
* @param point augend
* @param addend addend
* @return sum of both points
*/
private static Point add(Point point, Point addend) {
return new Point(point.x + addend.x, point.y + addend.y);
}
/**
* Subtracts one point from another; also needed for in-betweens
* @param point minuend
* @param subtrahend subtrahend
* @return sum of both points
*/
private static Point subtract(Point point, Point subtrahend) {
return new Point(point.x - subtrahend.x, point.y - subtrahend.y);
}
/**
* Find in-between with given scalar
* @param kf1 first frame
* @param kf2 next frame
* @param scalar factor (ideally between 0 and 1, representing 0% transition to 100% transition)
* @return in-between frame
*/
private static KeyFrame inBetween(KeyFrame kf1, KeyFrame kf2, double scalar, KeyFrame.PathMethod method) {
2026-01-24 12:04:55 +01:00
// position
Point difference = subtract(kf2.position(), kf1.position());
Point pos = add(kf1.position(), multiply(difference, scalar));
// scale
int width, height;
int dW = kf2.width() - kf1.width();
int dH = kf2.height() - kf1.height();
width = Math.toIntExact(Math.round(kf1.width() + dW * scalar));
height = Math.toIntExact(Math.round(kf1.height() + dH * scalar));
return new KeyFrame(pos, width, height, method);
}
@Override
public AnimationPath clone() {
return new AnimationPath(this.inbetweens, new ArrayList<>(this));
}
private double linear(int subIterator) {
return (double) subIterator/2 / this.inbetweens;
}
private double easeIn(int subIterator) {
return this.linear(subIterator/2) * this.linear(subIterator/2);
}
public double easeOut(int subIterator) {
return Math.sqrt(this.linear(subIterator*2));
2026-01-24 12:04:55 +01:00
}
}