mirror of
https://github.com/jneug/zeichenmaschine.git
synced 2026-04-14 06:33:34 +02:00
Merge branch 'anim' into events
This commit is contained in:
@@ -9,6 +9,7 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
## Added
|
||||
- Interface `Audio` extrahiert, mit Basisfunktionen von `Sound` und `Music`.
|
||||
- Klasse `Mixer` steuert mehrere Audio-Objekte gleichzeitig.
|
||||
- Klasse `tasks.RateLimitedTask`, `tasks.FramerateLimitedTask`, `tasks.FrameSynchronizedTask` und `tasks.DelayedTask`.
|
||||
|
||||
## Changed
|
||||
- Neue Package-Struktur:
|
||||
|
||||
@@ -1277,10 +1277,37 @@ public class Zeichenmaschine extends Constants {
|
||||
// Zeichenthread
|
||||
////
|
||||
|
||||
/**
|
||||
* Globaler Monitor, der einmal pro Frame vom Zeichenthread freigegeben
|
||||
* wird. Andere Threads können {@link Object#wait()} auf dem Monitor
|
||||
* aufrufen, um sich mit dem Zeichenthread zu synchronisieren. Der
|
||||
* {@code wait()} Aufruf sollte sich zur Sicherheit in einer Schleife
|
||||
* befinden, die prüft, ob sich der Aktuelle {@link #tick} erhöht hat.
|
||||
* <pre><code>
|
||||
* int lastTick = Constants.tick;
|
||||
*
|
||||
* // Do some work
|
||||
*
|
||||
* while( lastTick >= Constants.tick ) {
|
||||
* synchronized( Zeichenmaschine.globalSyncLock ) {
|
||||
* try {
|
||||
* Zeichenmaschine.globalSyncLock.wait();
|
||||
* } catch( InterruptedException ex ) {}
|
||||
* }
|
||||
* }
|
||||
* // Next frame has started
|
||||
* </code></pre>
|
||||
* <p>
|
||||
* Die {@link schule.ngb.zm.tasks.FrameSynchronizedTask} implementiert eine
|
||||
* {@link schule.ngb.zm.tasks.Task}, die sich automatisch auf diese Wiese
|
||||
* mit dem Zeichenthread synchronisiert.
|
||||
*/
|
||||
public static final Object globalSyncLock = new Object[0];
|
||||
|
||||
class Zeichenthread extends Thread {
|
||||
|
||||
public Zeichenthread() {
|
||||
super(Zeichenthread.class.getSimpleName());
|
||||
super(APP_NAME);
|
||||
//super(APP_NAME + " " + APP_VERSION);
|
||||
}
|
||||
|
||||
@@ -1329,6 +1356,9 @@ public class Zeichenmaschine extends Constants {
|
||||
}
|
||||
|
||||
runTasks();
|
||||
synchronized( globalSyncLock ) {
|
||||
globalSyncLock.notifyAll();
|
||||
}
|
||||
|
||||
// delta time in ns
|
||||
long afterTime = System.nanoTime();
|
||||
@@ -1415,6 +1445,7 @@ public class Zeichenmaschine extends Constants {
|
||||
}
|
||||
|
||||
class InputListener implements MouseInputListener, MouseMotionListener, MouseWheelListener, KeyListener {
|
||||
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e ) {
|
||||
enqueueEvent(e);
|
||||
|
||||
200
src/main/java/schule/ngb/zm/anim/Animations.java
Normal file
200
src/main/java/schule/ngb/zm/anim/Animations.java
Normal file
@@ -0,0 +1,200 @@
|
||||
package schule.ngb.zm.anim;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Vector;
|
||||
import schule.ngb.zm.tasks.FrameSynchronizedTask;
|
||||
import schule.ngb.zm.tasks.TaskRunner;
|
||||
import schule.ngb.zm.util.Log;
|
||||
import schule.ngb.zm.util.Validator;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.*;
|
||||
|
||||
public class Animations {
|
||||
|
||||
public static final <T> Future<T> animateProperty( String propName, T target, double to, int runtime, DoubleUnaryOperator easing ) {
|
||||
double from;
|
||||
try {
|
||||
from = callGetter(target, propName, double.class);
|
||||
} catch( InvocationTargetException | NoSuchMethodException |
|
||||
IllegalAccessException ex ) {
|
||||
throw new RuntimeException("Can't access property getter for animation.", ex);
|
||||
}
|
||||
|
||||
Method propSetter;
|
||||
try {
|
||||
propSetter = findSetter(target, propName, double.class);
|
||||
} catch( NoSuchMethodException ex ) {
|
||||
throw new RuntimeException("Can't find property setter for animation.", ex);
|
||||
}
|
||||
|
||||
return animateProperty(target, from, to, runtime, easing, ( d ) -> {
|
||||
try {
|
||||
propSetter.invoke(target, d);
|
||||
} catch( IllegalAccessException | InvocationTargetException e ) {
|
||||
throw new RuntimeException("Can't access property setter for animation.", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static final <T> Future<T> animateProperty( String propName, T target, Color to, int runtime, DoubleUnaryOperator easing ) {
|
||||
Color from;
|
||||
try {
|
||||
from = callGetter(target, propName, Color.class);
|
||||
} catch( InvocationTargetException | NoSuchMethodException |
|
||||
IllegalAccessException ex ) {
|
||||
throw new RuntimeException("Can't access property getter for animation.", ex);
|
||||
}
|
||||
|
||||
Method propSetter;
|
||||
try {
|
||||
propSetter = findSetter(target, propName, Color.class);
|
||||
} catch( NoSuchMethodException ex ) {
|
||||
throw new RuntimeException("Can't find property setter for animation.", ex);
|
||||
}
|
||||
|
||||
return animateProperty(target, from, to, runtime, easing, ( d ) -> {
|
||||
try {
|
||||
propSetter.invoke(target, d);
|
||||
} catch( IllegalAccessException | InvocationTargetException e ) {
|
||||
throw new RuntimeException("Can't access property setter for animation.", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static final <T> Future<T> animateProperty( String propName, T target, Vector to, int runtime, DoubleUnaryOperator easing ) {
|
||||
Vector from;
|
||||
try {
|
||||
from = callGetter(target, propName, Vector.class);
|
||||
} catch( InvocationTargetException | NoSuchMethodException |
|
||||
IllegalAccessException ex ) {
|
||||
throw new RuntimeException("Can't access property getter for animation.", ex);
|
||||
}
|
||||
|
||||
Method propSetter;
|
||||
try {
|
||||
propSetter = findSetter(target, propName, Vector.class);
|
||||
} catch( NoSuchMethodException ex ) {
|
||||
throw new RuntimeException("Can't find property setter for animation.", ex);
|
||||
}
|
||||
|
||||
return animateProperty(target, from, to, runtime, easing, ( d ) -> {
|
||||
try {
|
||||
propSetter.invoke(target, d);
|
||||
} catch( IllegalAccessException | InvocationTargetException e ) {
|
||||
throw new RuntimeException("Can't access property setter for animation.", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static final <T, R> R callGetter( T target, String propName, Class<R> propType ) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
String getterName = makeMethodName("get", propName);
|
||||
Method getter = target.getClass().getMethod(getterName);
|
||||
if( getter != null && getter.getReturnType().equals(propType) ) {
|
||||
return (R) getter.invoke(target);
|
||||
} else {
|
||||
throw new NoSuchMethodException(String.format("No getter for property <%s> found.", propName));
|
||||
}
|
||||
}
|
||||
|
||||
private static final <T, R> Method findSetter( T target, String propName, Class<R> propType ) throws NoSuchMethodException {
|
||||
String setterName = makeMethodName("set", propName);
|
||||
Method setter = target.getClass().getMethod(setterName, propType);
|
||||
if( setter != null && setter.getReturnType().equals(void.class) && setter.getParameterCount() == 1 ) {
|
||||
return setter;
|
||||
} else {
|
||||
throw new NoSuchMethodException(String.format("No setter for property <%s> found.", propName));
|
||||
}
|
||||
}
|
||||
|
||||
private static final String makeMethodName( String prefix, String propName ) {
|
||||
String firstChar = propName.substring(0, 1).toUpperCase();
|
||||
String tail = "";
|
||||
if( propName.length() > 1 ) {
|
||||
tail = propName.substring(1);
|
||||
}
|
||||
return prefix + firstChar + tail;
|
||||
}
|
||||
|
||||
public static final <T> Future<T> animateProperty( T target, final double from, final double to, int runtime, DoubleUnaryOperator easing, DoubleConsumer propSetter ) {
|
||||
Validator.requireNotNull(target);
|
||||
Validator.requireNotNull(propSetter);
|
||||
return animate(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e)));
|
||||
}
|
||||
|
||||
public static final <T> Future<T> animateProperty( T target, final Color from, final Color to, int runtime, DoubleUnaryOperator easing, Consumer<Color> propSetter ) {
|
||||
return animate(target, runtime, easing, ( e ) -> propSetter.accept(Color.interpolate(from, to, e)));
|
||||
}
|
||||
|
||||
|
||||
public static final <T> Future<T> animateProperty( T target, final Vector from, final Vector to, int runtime, DoubleUnaryOperator easing, Consumer<Vector> propSetter ) {
|
||||
return animate(target, runtime, easing, ( e ) -> propSetter.accept(Vector.interpolate(from, to, e)));
|
||||
}
|
||||
|
||||
public static final <T, R> Future<T> animateProperty( T target, R from, R to, int runtime, DoubleUnaryOperator easing, DoubleFunction<R> interpolator, Consumer<R> propSetter ) {
|
||||
return animate(target, runtime, easing, interpolator, ( t, r ) -> propSetter.accept(r));
|
||||
}
|
||||
|
||||
|
||||
public static final <T, R> Future<T> animate( T target, int runtime, DoubleUnaryOperator easing, DoubleFunction<R> interpolator, BiConsumer<T, R> applicator ) {
|
||||
return animate(target, runtime, easing, ( e ) -> applicator.accept(target, interpolator.apply(e)));
|
||||
}
|
||||
|
||||
public static final <T> Future<T> animate( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) {
|
||||
/*final long starttime = System.currentTimeMillis();
|
||||
return TaskRunner.run(() -> {
|
||||
double t = 0.0;
|
||||
do {
|
||||
// One animation step for t in [0,1]
|
||||
stepper.accept(easing.applyAsDouble(t));
|
||||
try {
|
||||
Thread.sleep(1000 / Constants.framesPerSecond);
|
||||
} catch( InterruptedException ex ) {
|
||||
}
|
||||
t = (double) (System.currentTimeMillis() - starttime) / (double) runtime;
|
||||
} while( t < 1.0 );
|
||||
stepper.accept(easing.applyAsDouble(1.0));
|
||||
}, target);*/
|
||||
return TaskRunner.run(new FrameSynchronizedTask() {
|
||||
double t = 0.0;
|
||||
final long starttime = System.currentTimeMillis();
|
||||
@Override
|
||||
public void update( double delta ) {
|
||||
// One animation step for t in [0,1]
|
||||
stepper.accept(easing.applyAsDouble(t));
|
||||
t = (double) (System.currentTimeMillis() - starttime) / (double) runtime;
|
||||
running = (t <= 1.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finish() {
|
||||
stepper.accept(easing.applyAsDouble(1.0));
|
||||
}
|
||||
}, target);
|
||||
}
|
||||
|
||||
public static final <T, R> Future<T> animate( T target, int runtime, Animator<T, R> animator ) {
|
||||
return animate(
|
||||
target, runtime,
|
||||
animator::easing,
|
||||
animator::interpolator,
|
||||
animator::applicator
|
||||
);
|
||||
}
|
||||
|
||||
/*public static <T> Future<?> animate( Animation<T> animation ) {
|
||||
animation.start();
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> Future<?> animate( Animation<T> animation, DoubleUnaryOperator easing ) {
|
||||
animation.start(easing);
|
||||
return null;
|
||||
}*/
|
||||
|
||||
public static final Log LOG = Log.getLogger(Animations.class);
|
||||
|
||||
}
|
||||
11
src/main/java/schule/ngb/zm/anim/Animator.java
Normal file
11
src/main/java/schule/ngb/zm/anim/Animator.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package schule.ngb.zm.anim;
|
||||
|
||||
public interface Animator<T, R> {
|
||||
|
||||
double easing(double t);
|
||||
|
||||
R interpolator(double e);
|
||||
|
||||
void applicator(T target, R value);
|
||||
|
||||
}
|
||||
320
src/main/java/schule/ngb/zm/anim/Easing.java
Normal file
320
src/main/java/schule/ngb/zm/anim/Easing.java
Normal file
@@ -0,0 +1,320 @@
|
||||
package schule.ngb.zm.anim;
|
||||
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
/**
|
||||
* @see <a href="https://easings.net/de">Cheat Sheet für Easing-Funktionen</a>
|
||||
*/
|
||||
public class Easing {
|
||||
|
||||
public static final DoubleUnaryOperator DEFAULT_EASING = Easing::smooth;
|
||||
|
||||
public static final DoubleUnaryOperator thereAndBack() {
|
||||
return Easing::thereAndBack;
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator thereAndBack( final DoubleUnaryOperator baseEasing ) {
|
||||
return ( t ) -> thereAndBack(t, baseEasing);
|
||||
}
|
||||
|
||||
public static final double thereAndBack( double t ) {
|
||||
return thereAndBack(t, DEFAULT_EASING);
|
||||
}
|
||||
|
||||
public static final double thereAndBack( double t, DoubleUnaryOperator baseEasing ) {
|
||||
if( t < 0.5 ) {
|
||||
return baseEasing.applyAsDouble(2 * t);
|
||||
} else {
|
||||
return baseEasing.applyAsDouble(2 - 2 * t);
|
||||
}
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator halfAndHalf( final DoubleUnaryOperator firstEasing, final DoubleUnaryOperator secondEasing ) {
|
||||
return ( t ) -> halfAndHalf(t, firstEasing, secondEasing);
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator halfAndHalf( final DoubleUnaryOperator firstEasing, final DoubleUnaryOperator secondEasing, final double split ) {
|
||||
return ( t ) -> halfAndHalf(t, firstEasing, secondEasing, split);
|
||||
}
|
||||
|
||||
public static final double halfAndHalf( double t, DoubleUnaryOperator firstEasing, DoubleUnaryOperator secondEasing ) {
|
||||
return halfAndHalf(t, firstEasing, secondEasing, 0.5);
|
||||
}
|
||||
|
||||
public static final double halfAndHalf( double t, DoubleUnaryOperator firstEasing, DoubleUnaryOperator secondEasing, double split ) {
|
||||
if( t < split ) {
|
||||
return firstEasing.applyAsDouble(2 * t);
|
||||
} else {
|
||||
return secondEasing.applyAsDouble(1 - 2 * t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Functions taken from easings.net
|
||||
*/
|
||||
|
||||
public static final DoubleUnaryOperator linear() {
|
||||
return Easing::linear;
|
||||
}
|
||||
|
||||
public static final double linear( double t ) {
|
||||
return t;
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator quadIn() {
|
||||
return Easing::quadIn;
|
||||
}
|
||||
|
||||
public static final double quadIn( double t ) {
|
||||
return t * t;
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator quadOut() {
|
||||
return Easing::quadOut;
|
||||
}
|
||||
|
||||
public static final double quadOut( double t ) {
|
||||
return 1 - (1 - t) * (1 - t);
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator quadInOut() {
|
||||
return Easing::quadInOut;
|
||||
}
|
||||
|
||||
public static final double quadInOut( double t ) {
|
||||
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator cubicIn() {
|
||||
return Easing::cubicIn;
|
||||
}
|
||||
|
||||
public static final double cubicIn( double t ) {
|
||||
return t * t * t;
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator cubicOut() {
|
||||
return Easing::cubicOut;
|
||||
}
|
||||
|
||||
public static final double cubicOut( double t ) {
|
||||
return 1 - Math.pow(1 - t, 3);
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator cubicInOut() {
|
||||
return Easing::cubicInOut;
|
||||
}
|
||||
|
||||
public static final double cubicInOut( double t ) {
|
||||
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator sineIn() {
|
||||
return Easing::sineIn;
|
||||
}
|
||||
|
||||
public static final double sineIn( double t ) {
|
||||
return 1 - Math.cos((t * Math.PI) / 2);
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator sineOut() {
|
||||
return Easing::sineOut;
|
||||
}
|
||||
|
||||
public static final double sineOut( double t ) {
|
||||
return Math.sin((t * Math.PI) / 2);
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator sineInOut() {
|
||||
return Easing::sineInOut;
|
||||
}
|
||||
|
||||
public static final double sineInOut( double t ) {
|
||||
return -(Math.cos(Math.PI * t) - 1) / 2;
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator elasticIn() {
|
||||
return Easing::elasticIn;
|
||||
}
|
||||
|
||||
public static final double elasticIn( double t ) {
|
||||
double c4 = (2 * Math.PI) / 3;
|
||||
|
||||
return t == 0
|
||||
? 0
|
||||
: t == 1
|
||||
? 1
|
||||
: -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4);
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator elasticOut() {
|
||||
return Easing::elasticOut;
|
||||
}
|
||||
|
||||
public static final double elasticOut( double t ) {
|
||||
double c4 = (2 * Math.PI) / 3;
|
||||
|
||||
return t == 0
|
||||
? 0
|
||||
: t == 1
|
||||
? 1
|
||||
: Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator elasticInOut() {
|
||||
return Easing::elasticInOut;
|
||||
}
|
||||
|
||||
public static final double elasticInOut( double t ) {
|
||||
double c5 = (2 * Math.PI) / 4.5;
|
||||
|
||||
return t == 0
|
||||
? 0
|
||||
: t == 1
|
||||
? 1
|
||||
: t < 0.5
|
||||
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2
|
||||
: (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + 1;
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator bounceIn() {
|
||||
return Easing::bounceIn;
|
||||
}
|
||||
|
||||
public static final double bounceIn( double t ) {
|
||||
return 1 - bounceOut(1 - t);
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator bounceOut() {
|
||||
return Easing::bounceOut;
|
||||
}
|
||||
|
||||
public static final double bounceOut( double t ) {
|
||||
double n1 = 7.5625;
|
||||
double d1 = 2.75;
|
||||
|
||||
if( t < 1.0 / d1 ) {
|
||||
return n1 * t * t;
|
||||
} else if( t < 2.0 / d1 ) {
|
||||
return n1 * (t -= 1.5 / d1) * t + 0.75;
|
||||
} else if( t < 2.5 / d1 ) {
|
||||
return n1 * (t -= 2.25 / d1) * t + 0.9375;
|
||||
} else {
|
||||
return n1 * (t -= 2.625 / d1) * t + 0.984375;
|
||||
}
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator bounceInOut() {
|
||||
return Easing::bounceInOut;
|
||||
}
|
||||
|
||||
public static final double bounceInOut( double t ) {
|
||||
return t < 0.5
|
||||
? (1 - bounceOut(1 - 2 * t)) / 2
|
||||
: (1 + bounceOut(2 * t - 1)) / 2;
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator backIn() {
|
||||
return Easing::backIn;
|
||||
}
|
||||
|
||||
public static final double backIn( double t ) {
|
||||
double c1 = 1.70158;
|
||||
double c3 = c1 + 1;
|
||||
|
||||
return c3 * t * t * t - c1 * t * t;
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator backOut() {
|
||||
return Easing::backOut;
|
||||
}
|
||||
|
||||
public static final double backOut( double t ) {
|
||||
double c1 = 1.70158;
|
||||
double c3 = c1 + 1;
|
||||
|
||||
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator backInOut() {
|
||||
return Easing::backInOut;
|
||||
}
|
||||
|
||||
public static final double backInOut( double t ) {
|
||||
double c1 = 1.70158;
|
||||
double c2 = c1 * 1.525;
|
||||
|
||||
return t < 0.5
|
||||
? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
|
||||
: (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Functions from manim community
|
||||
*/
|
||||
|
||||
public static final DoubleUnaryOperator smooth() {
|
||||
return Easing::smooth;
|
||||
}
|
||||
|
||||
public static final double smooth( double t ) {
|
||||
double error = sigmoid(-INFLECTION / 2.0);
|
||||
return Math.min(
|
||||
Math.max(
|
||||
(sigmoid(INFLECTION * (t - 0.5)) - error) / (1 - 2 * error),
|
||||
0
|
||||
),
|
||||
1.0
|
||||
);
|
||||
}
|
||||
|
||||
public static final double rushIn( double t ) {
|
||||
return 2 * smooth(t / 2.0);
|
||||
}
|
||||
|
||||
public static final double rushOut( double t ) {
|
||||
return 2 * smooth(t / 2.0 + 0.5) - 1;
|
||||
}
|
||||
|
||||
public static final double doubleSmooth( double t ) {
|
||||
if( t < 0.5 )
|
||||
return 0.5 * smooth(2 * t);
|
||||
else
|
||||
return 0.5 * (1 + smooth(2 * t - 1));
|
||||
}
|
||||
|
||||
public static final double hobbit( double t ) {
|
||||
double new_t = t < 0.5 ? 2 * t : 2 * (1 - t);
|
||||
return smooth(new_t);
|
||||
}
|
||||
|
||||
|
||||
public static final DoubleUnaryOperator wiggle() {
|
||||
return Easing::wiggle;
|
||||
}
|
||||
|
||||
public static final DoubleUnaryOperator wiggle( final int wiggles ) {
|
||||
return (t) -> Easing.wiggle(t, wiggles);
|
||||
}
|
||||
|
||||
public static final double wiggle( double t ) {
|
||||
return wiggle(t, 2);
|
||||
}
|
||||
|
||||
public static final double wiggle( double t, int wiggles ) {
|
||||
return hobbit(t) * Math.sin(wiggles * Math.PI * t);
|
||||
}
|
||||
|
||||
public static double INFLECTION = 10.0;
|
||||
|
||||
public static final double sigmoid( double x ) {
|
||||
return 1.0 / (1 + Math.exp(-x));
|
||||
}
|
||||
|
||||
|
||||
private Easing() {
|
||||
}
|
||||
|
||||
}
|
||||
69
src/main/java/schule/ngb/zm/tasks/DelayedTask.java
Normal file
69
src/main/java/schule/ngb/zm/tasks/DelayedTask.java
Normal file
@@ -0,0 +1,69 @@
|
||||
package schule.ngb.zm.tasks;
|
||||
|
||||
import schule.ngb.zm.Zeichenmaschine;
|
||||
|
||||
import java.util.concurrent.Delayed;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public abstract class DelayedTask extends Task implements Delayed {
|
||||
|
||||
protected long startTime = System.currentTimeMillis(); // in ms
|
||||
|
||||
/**
|
||||
* Gibt die absolute Verzögerung der Task zurück. Im Gegensatz zu
|
||||
* {@link #getDelay(TimeUnit)} sollte das Ergebnis von {@code getDelay()}
|
||||
* bei mehrmaligem Aufruf konstant bleiben.
|
||||
*
|
||||
* @return Die ursprüngliche Verzögerung in Millisekunden
|
||||
*/
|
||||
public abstract int getDelay();
|
||||
|
||||
public long getStartTime() {
|
||||
return startTime + getDelay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die verbleibende Verzögerung bis zur Ausführung der Task zurück. Im
|
||||
* Gegensatz zu {@link #getDelay()} sollte für mehrere Aufrufe von
|
||||
* {@code getDelay(TimeUnit)} gelten, dass der zeitlich spätere Aufruf einen
|
||||
* kleineren Wert zurückgibt, als der Frühere (abhängig von der gewählten
|
||||
* {@link TimeUnit}).
|
||||
*
|
||||
* @param unit Die Zeiteinheit für die Verzögerung.
|
||||
* @return Die verbleibende Verzögerung in der angegebenen Zeiteinheit.
|
||||
*/
|
||||
@Override
|
||||
public long getDelay( TimeUnit unit ) {
|
||||
int diff = (int) (getStartTime() - System.currentTimeMillis());
|
||||
return unit.convert(diff, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo( Delayed o ) {
|
||||
return (int) (getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while( getDelay(TimeUnit.MILLISECONDS) > 0 ) {
|
||||
try {
|
||||
System.out.println("Waiting for " + getDelay(TimeUnit.MILLISECONDS) + " ms");
|
||||
Thread.sleep(getDelay(TimeUnit.MILLISECONDS));
|
||||
//wait(getDelay(TimeUnit.MILLISECONDS));
|
||||
} catch( InterruptedException e ) {
|
||||
// Keep waiting
|
||||
}
|
||||
}
|
||||
|
||||
initialize();
|
||||
|
||||
running = true;
|
||||
this.update(0.0);
|
||||
running = false;
|
||||
done = true;
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
55
src/main/java/schule/ngb/zm/tasks/FrameSynchronizedTask.java
Normal file
55
src/main/java/schule/ngb/zm/tasks/FrameSynchronizedTask.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package schule.ngb.zm.tasks;
|
||||
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Zeichenmaschine;
|
||||
|
||||
public abstract class FrameSynchronizedTask extends Task {
|
||||
|
||||
private static Thread mainThread;
|
||||
|
||||
private static final Thread getMainThread() {
|
||||
if( mainThread == null ) {
|
||||
mainThread = Thread.currentThread();
|
||||
if( !mainThread.getName().equals(Constants.APP_NAME) ) {
|
||||
// Need to search for main Zeichenthread ...
|
||||
}
|
||||
}
|
||||
return mainThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
initialize();
|
||||
|
||||
running = true;
|
||||
int lastTick = 0;
|
||||
Object lock = Zeichenmaschine.globalSyncLock;
|
||||
|
||||
while( running ) {
|
||||
lastTick = Constants.tick;
|
||||
this.update(lastTick);
|
||||
|
||||
synchronized( lock ) {
|
||||
while( lastTick >= Constants.tick ) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch( InterruptedException e ) {
|
||||
// We got interrupted ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
running = false;
|
||||
done = true;
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
11
src/main/java/schule/ngb/zm/tasks/FramerateLimitedTask.java
Normal file
11
src/main/java/schule/ngb/zm/tasks/FramerateLimitedTask.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package schule.ngb.zm.tasks;
|
||||
|
||||
import schule.ngb.zm.Constants;
|
||||
|
||||
public abstract class FramerateLimitedTask extends RateLimitedTask {
|
||||
|
||||
public int getRate() {
|
||||
return Constants.framesPerSecond;
|
||||
}
|
||||
|
||||
}
|
||||
57
src/main/java/schule/ngb/zm/tasks/RateLimitedTask.java
Normal file
57
src/main/java/schule/ngb/zm/tasks/RateLimitedTask.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package schule.ngb.zm.tasks;
|
||||
|
||||
public abstract class RateLimitedTask extends Task {
|
||||
|
||||
public abstract int getRate();
|
||||
|
||||
@Override
|
||||
public final void run() {
|
||||
if( running || done ) {
|
||||
return;
|
||||
}
|
||||
|
||||
initialize();
|
||||
|
||||
// current time in ns
|
||||
long beforeTime = System.nanoTime();
|
||||
// store for deltas
|
||||
long overslept = 0L;
|
||||
// delta in ms
|
||||
double delta = 0;
|
||||
|
||||
running = true;
|
||||
while( running ) {
|
||||
// delta in seconds
|
||||
delta = (System.nanoTime() - beforeTime) / 1000000000.0;
|
||||
beforeTime = System.nanoTime();
|
||||
|
||||
this.update(delta);
|
||||
|
||||
// delta time in ns
|
||||
long afterTime = System.nanoTime();
|
||||
long dt = afterTime - beforeTime;
|
||||
long sleep = 0;
|
||||
if( getRate() > 0 ) {
|
||||
sleep = ((1000000000L / getRate()) - dt) - overslept;
|
||||
}
|
||||
|
||||
if( sleep > 0 ) {
|
||||
try {
|
||||
Thread.sleep(sleep / 1000000L, (int) (sleep % 1000000L));
|
||||
} catch( InterruptedException e ) {
|
||||
// Interrupt not relevant
|
||||
}
|
||||
} else {
|
||||
Thread.yield();
|
||||
}
|
||||
// Did we sleep to long?
|
||||
overslept = (System.nanoTime() - afterTime) - sleep;
|
||||
}
|
||||
|
||||
running = false;
|
||||
done = true;
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
30
src/main/java/schule/ngb/zm/tasks/Task.java
Normal file
30
src/main/java/schule/ngb/zm/tasks/Task.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package schule.ngb.zm.tasks;
|
||||
|
||||
import schule.ngb.zm.Updatable;
|
||||
|
||||
public abstract class Task implements Runnable, Updatable {
|
||||
|
||||
protected boolean running = false;
|
||||
|
||||
protected boolean done = false;
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return running;
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return !running & done;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
running = false;
|
||||
}
|
||||
|
||||
protected void initialize() {
|
||||
}
|
||||
|
||||
protected void finish() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,6 +25,11 @@ public class TaskRunner {
|
||||
return runner;
|
||||
}
|
||||
|
||||
public static Future<?> run( Task task ) {
|
||||
TaskRunner r = getTaskRunner();
|
||||
return r.pool.submit(task);
|
||||
}
|
||||
|
||||
public static Future<?> run( Runnable task ) {
|
||||
TaskRunner r = getTaskRunner();
|
||||
return r.pool.submit(task);
|
||||
@@ -35,18 +40,13 @@ public class TaskRunner {
|
||||
return r.pool.submit(task, result);
|
||||
}
|
||||
|
||||
public static Future<?> schedule( Runnable task, int ms ) {
|
||||
TaskRunner r = getTaskRunner();
|
||||
return r.pool.schedule(task, ms, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public static void invokeLater( Runnable task ) {
|
||||
SwingUtilities.invokeLater(task);
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
if( runner != null ) {
|
||||
runner.pool.shutdown();
|
||||
/*runner.pool.shutdown();
|
||||
try {
|
||||
runner.pool.awaitTermination(SHUTDOWN_TIME, TimeUnit.MILLISECONDS);
|
||||
} catch( InterruptedException ex ) {
|
||||
@@ -55,15 +55,27 @@ public class TaskRunner {
|
||||
if( !runner.pool.isTerminated() ) {
|
||||
runner.pool.shutdownNow();
|
||||
}
|
||||
}
|
||||
}*/
|
||||
runner.pool.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
ScheduledExecutorService pool;
|
||||
ExecutorService pool;
|
||||
|
||||
private TaskRunner() {
|
||||
//pool = new ScheduledThreadPoolExecutor(4);
|
||||
pool = Executors.newScheduledThreadPool(POOL_SIZE, new ThreadFactory() {
|
||||
/*pool = Executors.newScheduledThreadPool(POOL_SIZE, new ThreadFactory() {
|
||||
private final ThreadFactory threadFactory = Executors.defaultThreadFactory();
|
||||
|
||||
@Override
|
||||
public Thread newThread( Runnable r ) {
|
||||
Thread t = threadFactory.newThread(r);
|
||||
t.setName("TaskRunner-" + t.getName());
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
});*/
|
||||
pool = Executors.newCachedThreadPool(new ThreadFactory() {
|
||||
private final ThreadFactory threadFactory = Executors.defaultThreadFactory();
|
||||
|
||||
@Override
|
||||
|
||||
262
src/test/java/schule/ngb/zm/anim/AnimationsTest.java
Normal file
262
src/test/java/schule/ngb/zm/anim/AnimationsTest.java
Normal file
@@ -0,0 +1,262 @@
|
||||
package schule.ngb.zm.anim;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import schule.ngb.zm.Color;
|
||||
import schule.ngb.zm.Constants;
|
||||
import schule.ngb.zm.Options;
|
||||
import schule.ngb.zm.Zeichenmaschine;
|
||||
import schule.ngb.zm.shapes.*;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class AnimationsTest {
|
||||
|
||||
private static Zeichenmaschine zm;
|
||||
|
||||
private static ShapesLayer shapes;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
zm = new Zeichenmaschine(400, 400, "zm-test: Animations", false);
|
||||
shapes = zm.getShapesLayer();
|
||||
assertNotNull(shapes);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() {
|
||||
zm.exit();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
shapes.removeAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void animateMove() {
|
||||
Shape s = new Circle(0, 0, 10);
|
||||
shapes.add(s);
|
||||
|
||||
_animateMove(s, 2500, Easing.DEFAULT_EASING);
|
||||
assertEquals(zm.getWidth(), s.getX(), 0.0001);
|
||||
assertEquals(zm.getHeight(), s.getY(), 0.0001);
|
||||
|
||||
_animateMove(s, 2500, Easing.thereAndBack(Easing.linear()));
|
||||
assertEquals(0.0, s.getX(), 0.0001);
|
||||
assertEquals(0.0, s.getY(), 0.0001);
|
||||
|
||||
_animateMove(s, 4000, Easing::bounceInOut);
|
||||
assertEquals(zm.getWidth(), s.getX(), 0.0001);
|
||||
assertEquals(zm.getHeight(), s.getY(), 0.0001);
|
||||
}
|
||||
|
||||
private void _animateMove( Shape s, int runtime, DoubleUnaryOperator easing ) {
|
||||
s.moveTo(0, 0);
|
||||
Future<Shape> future = Animations.animate(
|
||||
s, runtime,
|
||||
easing,
|
||||
( e ) -> Constants.interpolate(0, zm.getWidth(), e),
|
||||
( t, p ) -> {
|
||||
t.moveTo(p, p);
|
||||
}
|
||||
);
|
||||
assertNotNull(future);
|
||||
try {
|
||||
assertEquals(s, future.get());
|
||||
} catch( Exception e ) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void animateCircle() {
|
||||
Shape s = new Circle(0, 0, 10);
|
||||
shapes.add(s);
|
||||
|
||||
_animateCircle(s, 5000, Easing.linear());
|
||||
}
|
||||
|
||||
private void _animateCircle( Shape s, final int runtime, final DoubleUnaryOperator easing ) {
|
||||
final int midX = (int) (zm.getWidth() * .5);
|
||||
final int midY = (int) (zm.getHeight() * .5);
|
||||
final int radius = (int) (zm.getWidth() * .25);
|
||||
|
||||
Animator<Shape, Double> ani = new Animator<Shape, Double>() {
|
||||
@Override
|
||||
public double easing( double t ) {
|
||||
return easing.applyAsDouble(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double interpolator( double e ) {
|
||||
return Constants.interpolate(0, 360, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applicator( Shape s, Double angle ) {
|
||||
double rad = Math.toRadians(angle);
|
||||
s.moveTo(midX + radius * Math.cos(rad), midY + radius * Math.sin(rad));
|
||||
}
|
||||
};
|
||||
|
||||
Future<Shape> future = Animations.animate(s, runtime, ani);
|
||||
assertNotNull(future);
|
||||
try {
|
||||
assertEquals(s, future.get());
|
||||
} catch( Exception e ) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void animateRotate() {
|
||||
Shape s = new Rectangle(0, 0, 129, 80);
|
||||
s.setAnchor(Constants.CENTER);
|
||||
shapes.add(s);
|
||||
|
||||
_animateRotate(s, 3000, Easing::cubicIn);
|
||||
assertEquals(zm.getWidth() * 0.5, s.getX(), 0.0001);
|
||||
assertEquals(zm.getHeight() * 0.5, s.getY(), 0.0001);
|
||||
assertEquals(0.0, s.getRotation(), 0.0001);
|
||||
|
||||
_animateRotate(s, 500, Easing::elasticInOut);
|
||||
assertEquals(zm.getWidth() * 0.5, s.getX(), 0.0001);
|
||||
assertEquals(zm.getHeight() * 0.5, s.getY(), 0.0001);
|
||||
assertEquals(0.0, s.getRotation(), 0.0001);
|
||||
|
||||
_animateRotate(s, 1000, Easing::bounceOut);
|
||||
assertEquals(zm.getWidth() * 0.5, s.getX(), 0.0001);
|
||||
assertEquals(zm.getHeight() * 0.5, s.getY(), 0.0001);
|
||||
assertEquals(0.0, s.getRotation(), 0.0001);
|
||||
|
||||
_animateRotate(s, 6000, Easing::backInOut);
|
||||
assertEquals(zm.getWidth() * 0.5, s.getX(), 0.0001);
|
||||
assertEquals(zm.getHeight() * 0.5, s.getY(), 0.0001);
|
||||
assertEquals(0.0, s.getRotation(), 0.0001);
|
||||
}
|
||||
|
||||
private void _animateRotate( Shape s, int runtime, DoubleUnaryOperator easing ) {
|
||||
s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5);
|
||||
s.rotateTo(0);
|
||||
Future<Shape> future = Animations.animate(
|
||||
s, runtime,
|
||||
easing,
|
||||
( e ) -> s.rotateTo(Constants.interpolate(0, 720, e))
|
||||
);
|
||||
assertNotNull(future);
|
||||
try {
|
||||
assertEquals(s, future.get());
|
||||
} catch( Exception e ) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void animateColor() {
|
||||
Shape s = new Ellipse(0, 0, 129, 80);
|
||||
s.setAnchor(Constants.CENTER);
|
||||
shapes.add(s);
|
||||
|
||||
_animateColor(s, Color.RED, 1000, Easing.DEFAULT_EASING);
|
||||
assertEquals(Color.RED, s.getFillColor());
|
||||
_animateColor(s, Color.BLUE, 1500, Easing::backInOut);
|
||||
assertEquals(Color.BLUE, s.getFillColor());
|
||||
_animateColor(s, Color.GREEN, 2000, Easing::bounceOut);
|
||||
assertEquals(Color.GREEN, s.getFillColor());
|
||||
_animateColor(s, Color.YELLOW, 300, Easing::thereAndBack);
|
||||
assertEquals(Color.GREEN, s.getFillColor());
|
||||
}
|
||||
|
||||
private void _animateColor( Shape s, Color to, int runtime, DoubleUnaryOperator easing ) {
|
||||
s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5);
|
||||
final Color from = s.getFillColor();
|
||||
Future<Shape> future = Animations.animate(
|
||||
s, runtime,
|
||||
easing,
|
||||
( e ) -> Color.interpolate(from, to, e),
|
||||
( t, c ) -> t.setFillColor(c)
|
||||
);
|
||||
assertNotNull(future);
|
||||
try {
|
||||
assertEquals(s, future.get());
|
||||
} catch( Exception e ) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void animatePropertyColor() {
|
||||
Shape s = new Ellipse(0, 0, 129, 80);
|
||||
s.setAnchor(Constants.CENTER);
|
||||
shapes.add(s);
|
||||
|
||||
_animatePropertyColor(s, Color.RED, 1000, Easing.DEFAULT_EASING);
|
||||
assertEquals(Color.RED, s.getFillColor());
|
||||
_animatePropertyColor(s, Color.BLUE, 1500, Easing::backInOut);
|
||||
assertEquals(Color.BLUE, s.getFillColor());
|
||||
_animatePropertyColor(s, Color.GREEN, 2000, Easing::bounceOut);
|
||||
assertEquals(Color.GREEN, s.getFillColor());
|
||||
_animatePropertyColor(s, Color.YELLOW, 300, Easing::thereAndBack);
|
||||
assertEquals(Color.GREEN, s.getFillColor());
|
||||
}
|
||||
|
||||
private void _animatePropertyColor( Shape s, Color to, int runtime, DoubleUnaryOperator easing ) {
|
||||
s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5);
|
||||
final Color from = s.getFillColor();
|
||||
Future<Shape> future = Animations.animateProperty(
|
||||
s, from, to, runtime, easing, s::setFillColor
|
||||
);
|
||||
assertNotNull(future);
|
||||
try {
|
||||
assertEquals(s, future.get());
|
||||
} catch( Exception e ) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void animatePropertyReflect() {
|
||||
Shape s = new Ellipse(0, 200, 129, 80);
|
||||
shapes.add(s);
|
||||
|
||||
try {
|
||||
Animations.animateProperty("x", s, 400, 1000, Easing.DEFAULT_EASING);
|
||||
Animations.animateProperty("strokeColor", s, Color.RED, 1000, Easing.DEFAULT_EASING).get();
|
||||
} catch( InterruptedException | ExecutionException e ) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void animateManim() {
|
||||
Shape s = new Circle(0, 0, 10);
|
||||
shapes.add(s);
|
||||
Text t = new Text(0, 0, "Easing");
|
||||
t.setAnchor(Options.Direction.EAST);
|
||||
t.alignTo(Options.Direction.NORTHEAST, -20.0);
|
||||
shapes.add(t);
|
||||
|
||||
t.setText("rushIn");
|
||||
_animateMove(s, 2500, Easing::rushIn);
|
||||
t.setText("rushOut");
|
||||
_animateMove(s, 2500, Easing::rushOut);
|
||||
t.setText("hobbit");
|
||||
_animateMove(s, 2500, Easing::hobbit);
|
||||
t.setText("wiggle(2)");
|
||||
_animateMove(s, 2500, Easing::wiggle);
|
||||
t.setText("wiggle(4)");
|
||||
_animateMove(s, 2500, Easing.wiggle(4));
|
||||
t.setText("doubleSmooth");
|
||||
_animateMove(s, 2500, Easing::doubleSmooth);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user