From fc7ee363678442ec6ee19d02b406dc110881bed7 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Thu, 7 Jul 2022 15:42:47 +0200 Subject: [PATCH 01/11] Implementierung von Easing-Funktionen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Implementierungen wurden von https://easings.net übernommen. --- src/schule/ngb/zm/anim/Easing.java | 252 +++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 src/schule/ngb/zm/anim/Easing.java diff --git a/src/schule/ngb/zm/anim/Easing.java b/src/schule/ngb/zm/anim/Easing.java new file mode 100644 index 0000000..8dea06b --- /dev/null +++ b/src/schule/ngb/zm/anim/Easing.java @@ -0,0 +1,252 @@ +package schule.ngb.zm.anim; + +import java.util.function.DoubleUnaryOperator; + +/** + * @see Cheat Sheet für Easing-Funktionen + */ +public class Easing { + + public static final DoubleUnaryOperator DEFAULT_EASING = Easing::cubicInOut; + + 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); + } + } + + + 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; + } + + private Easing() { + } + +} From 2f59d29d088ec5cc0b7e4e109097087ad9b95772 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Thu, 7 Jul 2022 15:44:36 +0200 Subject: [PATCH 02/11] Animationen als eigener Prozess Die Animations API verwendet Funktionale Aspekte der Java 8 API und erlaubt die Animation beliebiger Objekte, aber ist vor allem auf die `shape.*` Klassen ausgelegt. --- src/schule/ngb/zm/anim/Animations.java | 183 +++++++++++++++++++++++++ src/schule/ngb/zm/anim/Animator.java | 11 ++ 2 files changed, 194 insertions(+) create mode 100644 src/schule/ngb/zm/anim/Animations.java create mode 100644 src/schule/ngb/zm/anim/Animator.java diff --git a/src/schule/ngb/zm/anim/Animations.java b/src/schule/ngb/zm/anim/Animations.java new file mode 100644 index 0000000..c4e7b96 --- /dev/null +++ b/src/schule/ngb/zm/anim/Animations.java @@ -0,0 +1,183 @@ +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.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 Future 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 Future 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 Future 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 R callGetter( T target, String propName, Class 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 Method findSetter( T target, String propName, Class 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 Future 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 Future animateProperty( T target, final Color from, final Color to, int runtime, DoubleUnaryOperator easing, Consumer propSetter ) { + return animate(target, runtime, easing, ( e ) -> propSetter.accept(Color.interpolate(from, to, e))); + } + + + public static final Future animateProperty( T target, final Vector from, final Vector to, int runtime, DoubleUnaryOperator easing, Consumer propSetter ) { + return animate(target, runtime, easing, ( e ) -> propSetter.accept(Vector.interpolate(from, to, e))); + } + + public static final Future animateProperty( T target, R from, R to, int runtime, DoubleUnaryOperator easing, DoubleFunction interpolator, Consumer propSetter ) { + return animate(target, runtime, easing, interpolator, ( t, r ) -> propSetter.accept(r)); + } + + + public static final Future animate( T target, int runtime, DoubleUnaryOperator easing, DoubleFunction interpolator, BiConsumer applicator ) { + return animate(target, runtime, easing, ( e ) -> applicator.accept(target, interpolator.apply(e))); + } + + public static final Future 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); + } + + public static final Future animate( T target, int runtime, Animator animator ) { + return animate( + target, runtime, + animator::easing, + animator::interpolator, + animator::applicator + ); + } + + public static Future animate( Animation animation ) { + animation.start(); + return null; + } + + public static Future animate( Animation animation, DoubleUnaryOperator easing ) { + animation.start(easing); + return null; + } + + public static final Log LOG = Log.getLogger(Animations.class); + +} diff --git a/src/schule/ngb/zm/anim/Animator.java b/src/schule/ngb/zm/anim/Animator.java new file mode 100644 index 0000000..951279d --- /dev/null +++ b/src/schule/ngb/zm/anim/Animator.java @@ -0,0 +1,11 @@ +package schule.ngb.zm.anim; + +public interface Animator { + + double easing(double t); + + R interpolator(double e); + + void applicator(T target, R value); + +} From 303b667cbffe1cc9d29babc1ee78f491b6c989a4 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Thu, 7 Jul 2022 15:45:25 +0200 Subject: [PATCH 03/11] =?UTF-8?q?Tests=20f=C3=BCr=20Animationen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schule/ngb/zm/anim/AnimationsTest.java | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 test/src/schule/ngb/zm/anim/AnimationsTest.java diff --git a/test/src/schule/ngb/zm/anim/AnimationsTest.java b/test/src/schule/ngb/zm/anim/AnimationsTest.java new file mode 100644 index 0000000..7aa9eb3 --- /dev/null +++ b/test/src/schule/ngb/zm/anim/AnimationsTest.java @@ -0,0 +1,238 @@ +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.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 future = Animations.animate( + s, runtime, + easing, + ( e ) -> Constants.interpolate(0, 400, 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 ani = new Animator() { + @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 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 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 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 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); + } + } + +} From 9ee7c606fe413f9e5329307a76d641c5d5e468f7 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Thu, 7 Jul 2022 21:18:45 +0200 Subject: [PATCH 04/11] Implementierung verschiedener Task-Typen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Tasks erfüllen verschiedene Aufgaben und können vom TaskRunner parallel ausgeführt werden. Ob ein so komplexes Task-Management notwendig ist, bleibt offen. --- src/schule/ngb/zm/tasks/DelayedTask.java | 65 +++++++++++++++++++ .../ngb/zm/tasks/FrameSynchronizedTask.java | 51 +++++++++++++++ .../ngb/zm/tasks/FramerateLimitedTask.java | 11 ++++ src/schule/ngb/zm/tasks/RateLimitedTask.java | 53 +++++++++++++++ src/schule/ngb/zm/tasks/Task.java | 24 +++++++ src/schule/ngb/zm/tasks/TaskRunner.java | 30 ++++++--- 6 files changed, 225 insertions(+), 9 deletions(-) create mode 100644 src/schule/ngb/zm/tasks/DelayedTask.java create mode 100644 src/schule/ngb/zm/tasks/FrameSynchronizedTask.java create mode 100644 src/schule/ngb/zm/tasks/FramerateLimitedTask.java create mode 100644 src/schule/ngb/zm/tasks/RateLimitedTask.java create mode 100644 src/schule/ngb/zm/tasks/Task.java diff --git a/src/schule/ngb/zm/tasks/DelayedTask.java b/src/schule/ngb/zm/tasks/DelayedTask.java new file mode 100644 index 0000000..02f3ed5 --- /dev/null +++ b/src/schule/ngb/zm/tasks/DelayedTask.java @@ -0,0 +1,65 @@ +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() { + long delay = getDelay(TimeUnit.MILLISECONDS); + while( delay > 0 ) { + try { + wait(delay); + } catch( InterruptedException e ) { + // Keep waiting + } + delay = getDelay(TimeUnit.MILLISECONDS); + } + + running = true; + this.update(0.0); + running = false; + done = true; + } + +} diff --git a/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java b/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java new file mode 100644 index 0000000..73bda60 --- /dev/null +++ b/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java @@ -0,0 +1,51 @@ +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("Zeichenthread") ) { + // Need to search for main Zeichenthread ... + } + } + return mainThread; + } + + @Override + public void run() { + running = true; + int lastTick = 0; + Thread lock = getMainThread(); + + while( running ) { + lastTick = Constants.tick; + this.update(lastTick); + + synchronized( lock ) { + while( lastTick >= Constants.tick ) { + /*try { + lock.wait(); + } catch( InterruptedException e ) { + // We got interrupted ... + }*/ + Thread.yield(); + } + } + } + + running = false; + done = true; + } + + @Override + public boolean isActive() { + return false; + } + +} diff --git a/src/schule/ngb/zm/tasks/FramerateLimitedTask.java b/src/schule/ngb/zm/tasks/FramerateLimitedTask.java new file mode 100644 index 0000000..8fab4f6 --- /dev/null +++ b/src/schule/ngb/zm/tasks/FramerateLimitedTask.java @@ -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; + } + +} diff --git a/src/schule/ngb/zm/tasks/RateLimitedTask.java b/src/schule/ngb/zm/tasks/RateLimitedTask.java new file mode 100644 index 0000000..aadc06d --- /dev/null +++ b/src/schule/ngb/zm/tasks/RateLimitedTask.java @@ -0,0 +1,53 @@ +package schule.ngb.zm.tasks; + +public abstract class RateLimitedTask extends Task { + + public abstract int getRate(); + + @Override + public final void run() { + if( running || done ) { + return; + } + + // 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; + } + +} diff --git a/src/schule/ngb/zm/tasks/Task.java b/src/schule/ngb/zm/tasks/Task.java new file mode 100644 index 0000000..a8ae158 --- /dev/null +++ b/src/schule/ngb/zm/tasks/Task.java @@ -0,0 +1,24 @@ +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; + } + +} diff --git a/src/schule/ngb/zm/tasks/TaskRunner.java b/src/schule/ngb/zm/tasks/TaskRunner.java index 5bd20da..0173fca 100644 --- a/src/schule/ngb/zm/tasks/TaskRunner.java +++ b/src/schule/ngb/zm/tasks/TaskRunner.java @@ -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 From ced0aa6842699f0466aed0a0b5144d1caa6165c2 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Fri, 8 Jul 2022 07:44:49 +0200 Subject: [PATCH 05/11] =?UTF-8?q?Synchronisation=20=C3=BCber=20einen=20glo?= =?UTF-8?q?balen=20Monitor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schule/ngb/zm/Zeichenmaschine.java | 35 +++++++++++++++++-- .../ngb/zm/tasks/FrameSynchronizedTask.java | 9 +++-- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/schule/ngb/zm/Zeichenmaschine.java b/src/schule/ngb/zm/Zeichenmaschine.java index 151de10..93ed677 100644 --- a/src/schule/ngb/zm/Zeichenmaschine.java +++ b/src/schule/ngb/zm/Zeichenmaschine.java @@ -1276,10 +1276,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. + *

+	 * 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
+	 * 
+ *

+ * 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); } @@ -1328,6 +1355,9 @@ public class Zeichenmaschine extends Constants { } runTasks(); + synchronized( globalSyncLock ) { + globalSyncLock.notifyAll(); + } // delta time in ns long afterTime = System.nanoTime(); @@ -1413,7 +1443,8 @@ public class Zeichenmaschine extends Constants { } - class InputListener implements MouseInputListener, MouseMotionListener, MouseWheelListener, KeyListener{ + class InputListener implements MouseInputListener, MouseMotionListener, MouseWheelListener, KeyListener { + @Override public void mouseClicked( MouseEvent e ) { enqueueEvent(e); diff --git a/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java b/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java index 73bda60..416b9db 100644 --- a/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java +++ b/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java @@ -10,7 +10,7 @@ public abstract class FrameSynchronizedTask extends Task { private static final Thread getMainThread() { if( mainThread == null ) { mainThread = Thread.currentThread(); - if( !mainThread.getName().equals("Zeichenthread") ) { + if( !mainThread.getName().equals(Constants.APP_NAME) ) { // Need to search for main Zeichenthread ... } } @@ -21,7 +21,7 @@ public abstract class FrameSynchronizedTask extends Task { public void run() { running = true; int lastTick = 0; - Thread lock = getMainThread(); + Object lock = Zeichenmaschine.globalSyncLock; while( running ) { lastTick = Constants.tick; @@ -29,12 +29,11 @@ public abstract class FrameSynchronizedTask extends Task { synchronized( lock ) { while( lastTick >= Constants.tick ) { - /*try { + try { lock.wait(); } catch( InterruptedException e ) { // We got interrupted ... - }*/ - Thread.yield(); + } } } } From a8bbce72a2a5109c1c42f4ca51deab6b966977a2 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Fri, 8 Jul 2022 07:52:35 +0200 Subject: [PATCH 06/11] =?UTF-8?q?DelayedTask=20wartet=20selbstst=C3=A4ndig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schule/ngb/zm/tasks/DelayedTask.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/schule/ngb/zm/tasks/DelayedTask.java b/src/schule/ngb/zm/tasks/DelayedTask.java index 02f3ed5..7597ab3 100644 --- a/src/schule/ngb/zm/tasks/DelayedTask.java +++ b/src/schule/ngb/zm/tasks/DelayedTask.java @@ -46,14 +46,14 @@ public abstract class DelayedTask extends Task implements Delayed { @Override public void run() { - long delay = getDelay(TimeUnit.MILLISECONDS); - while( delay > 0 ) { + while( getDelay(TimeUnit.MILLISECONDS) > 0 ) { try { - wait(delay); + System.out.println("Waiting for " + getDelay(TimeUnit.MILLISECONDS) + " ms"); + Thread.sleep(getDelay(TimeUnit.MILLISECONDS)); + //wait(getDelay(TimeUnit.MILLISECONDS)); } catch( InterruptedException e ) { // Keep waiting } - delay = getDelay(TimeUnit.MILLISECONDS); } running = true; From 476545f721b3195a55537a1577034e560b05f6d4 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Fri, 8 Jul 2022 07:52:39 +0200 Subject: [PATCH 07/11] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b7ab68..69cb1ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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: From 98a62f35cd0317ed9a3c12513e7fdeb1a58986fb Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Fri, 8 Jul 2022 08:01:24 +0200 Subject: [PATCH 08/11] =?UTF-8?q?initialize=20und=20finish=20methoden=20f?= =?UTF-8?q?=C3=BCr=20Tasks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schule/ngb/zm/tasks/DelayedTask.java | 4 ++++ src/schule/ngb/zm/tasks/FrameSynchronizedTask.java | 5 +++++ src/schule/ngb/zm/tasks/RateLimitedTask.java | 4 ++++ src/schule/ngb/zm/tasks/Task.java | 6 ++++++ 4 files changed, 19 insertions(+) diff --git a/src/schule/ngb/zm/tasks/DelayedTask.java b/src/schule/ngb/zm/tasks/DelayedTask.java index 7597ab3..a39ece6 100644 --- a/src/schule/ngb/zm/tasks/DelayedTask.java +++ b/src/schule/ngb/zm/tasks/DelayedTask.java @@ -56,10 +56,14 @@ public abstract class DelayedTask extends Task implements Delayed { } } + initialize(); + running = true; this.update(0.0); running = false; done = true; + + finish(); } } diff --git a/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java b/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java index 416b9db..454818a 100644 --- a/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java +++ b/src/schule/ngb/zm/tasks/FrameSynchronizedTask.java @@ -19,6 +19,8 @@ public abstract class FrameSynchronizedTask extends Task { @Override public void run() { + initialize(); + running = true; int lastTick = 0; Object lock = Zeichenmaschine.globalSyncLock; @@ -40,6 +42,8 @@ public abstract class FrameSynchronizedTask extends Task { running = false; done = true; + + finish(); } @Override @@ -47,4 +51,5 @@ public abstract class FrameSynchronizedTask extends Task { return false; } + } diff --git a/src/schule/ngb/zm/tasks/RateLimitedTask.java b/src/schule/ngb/zm/tasks/RateLimitedTask.java index aadc06d..8f1be37 100644 --- a/src/schule/ngb/zm/tasks/RateLimitedTask.java +++ b/src/schule/ngb/zm/tasks/RateLimitedTask.java @@ -10,6 +10,8 @@ public abstract class RateLimitedTask extends Task { return; } + initialize(); + // current time in ns long beforeTime = System.nanoTime(); // store for deltas @@ -48,6 +50,8 @@ public abstract class RateLimitedTask extends Task { running = false; done = true; + + finish(); } } diff --git a/src/schule/ngb/zm/tasks/Task.java b/src/schule/ngb/zm/tasks/Task.java index a8ae158..56ac04a 100644 --- a/src/schule/ngb/zm/tasks/Task.java +++ b/src/schule/ngb/zm/tasks/Task.java @@ -21,4 +21,10 @@ public abstract class Task implements Runnable, Updatable { running = false; } + protected void initialize() { + } + + protected void finish() { + } + } From 447accc567da11d2a991860aa5c8e9e1b8c91153 Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Fri, 8 Jul 2022 08:01:53 +0200 Subject: [PATCH 09/11] Animationen nutzen FrameSynchronizedTasks --- src/schule/ngb/zm/anim/Animations.java | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/schule/ngb/zm/anim/Animations.java b/src/schule/ngb/zm/anim/Animations.java index c4e7b96..d9f63a2 100644 --- a/src/schule/ngb/zm/anim/Animations.java +++ b/src/schule/ngb/zm/anim/Animations.java @@ -3,6 +3,7 @@ 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; @@ -143,7 +144,7 @@ public class Animations { } public static final Future animate( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) { - final long starttime = System.currentTimeMillis(); + /*final long starttime = System.currentTimeMillis(); return TaskRunner.run(() -> { double t = 0.0; do { @@ -156,6 +157,22 @@ public class Animations { 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); } @@ -168,7 +185,7 @@ public class Animations { ); } - public static Future animate( Animation animation ) { + /*public static Future animate( Animation animation ) { animation.start(); return null; } @@ -176,7 +193,7 @@ public class Animations { public static Future animate( Animation animation, DoubleUnaryOperator easing ) { animation.start(easing); return null; - } + }*/ public static final Log LOG = Log.getLogger(Animations.class); From b575c47ab351a187d484c28af179031851d6e03b Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Fri, 8 Jul 2022 08:52:31 +0200 Subject: [PATCH 10/11] Neue Easing funktionen --- src/schule/ngb/zm/anim/Easing.java | 76 ++++++++++++++++++- .../schule/ngb/zm/anim/AnimationsTest.java | 26 ++++++- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/src/schule/ngb/zm/anim/Easing.java b/src/schule/ngb/zm/anim/Easing.java index 8dea06b..7679e23 100644 --- a/src/schule/ngb/zm/anim/Easing.java +++ b/src/schule/ngb/zm/anim/Easing.java @@ -7,7 +7,7 @@ import java.util.function.DoubleUnaryOperator; */ public class Easing { - public static final DoubleUnaryOperator DEFAULT_EASING = Easing::cubicInOut; + public static final DoubleUnaryOperator DEFAULT_EASING = Easing::smooth; public static final DoubleUnaryOperator thereAndBack() { return Easing::thereAndBack; @@ -50,6 +50,10 @@ public class Easing { } + /* + * Functions taken from easings.net + */ + public static final DoubleUnaryOperator linear() { return Easing::linear; } @@ -190,11 +194,11 @@ public class Easing { double n1 = 7.5625; double d1 = 2.75; - if (t < 1.0 / d1) { + if( t < 1.0 / d1 ) { return n1 * t * t; - } else if (t < 2.0 / d1) { + } else if( t < 2.0 / d1 ) { return n1 * (t -= 1.5 / d1) * t + 0.75; - } else if (t < 2.5 / d1) { + } 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; @@ -246,6 +250,70 @@ public class Easing { : (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() { } diff --git a/test/src/schule/ngb/zm/anim/AnimationsTest.java b/test/src/schule/ngb/zm/anim/AnimationsTest.java index 7aa9eb3..d4f0f89 100644 --- a/test/src/schule/ngb/zm/anim/AnimationsTest.java +++ b/test/src/schule/ngb/zm/anim/AnimationsTest.java @@ -7,6 +7,7 @@ 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.*; @@ -62,7 +63,7 @@ class AnimationsTest { Future future = Animations.animate( s, runtime, easing, - ( e ) -> Constants.interpolate(0, 400, e), + ( e ) -> Constants.interpolate(0, zm.getWidth(), e), ( t, p ) -> { t.moveTo(p, p); } @@ -235,4 +236,27 @@ class AnimationsTest { } } + @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); + } + } From 921e2fb3ef2ea7af3d1dd9ce0708631c2bbe45ea Mon Sep 17 00:00:00 2001 From: "J. Neugebauer" Date: Mon, 11 Jul 2022 14:06:47 +0200 Subject: [PATCH 11/11] An Gradle Struktur angepasst --- src/{ => main/java}/schule/ngb/zm/anim/Animations.java | 0 src/{ => main/java}/schule/ngb/zm/anim/Animator.java | 0 src/{ => main/java}/schule/ngb/zm/anim/Easing.java | 0 .../src => src/test/java}/schule/ngb/zm/anim/AnimationsTest.java | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/{ => main/java}/schule/ngb/zm/anim/Animations.java (100%) rename src/{ => main/java}/schule/ngb/zm/anim/Animator.java (100%) rename src/{ => main/java}/schule/ngb/zm/anim/Easing.java (100%) rename {test/src => src/test/java}/schule/ngb/zm/anim/AnimationsTest.java (100%) diff --git a/src/schule/ngb/zm/anim/Animations.java b/src/main/java/schule/ngb/zm/anim/Animations.java similarity index 100% rename from src/schule/ngb/zm/anim/Animations.java rename to src/main/java/schule/ngb/zm/anim/Animations.java diff --git a/src/schule/ngb/zm/anim/Animator.java b/src/main/java/schule/ngb/zm/anim/Animator.java similarity index 100% rename from src/schule/ngb/zm/anim/Animator.java rename to src/main/java/schule/ngb/zm/anim/Animator.java diff --git a/src/schule/ngb/zm/anim/Easing.java b/src/main/java/schule/ngb/zm/anim/Easing.java similarity index 100% rename from src/schule/ngb/zm/anim/Easing.java rename to src/main/java/schule/ngb/zm/anim/Easing.java diff --git a/test/src/schule/ngb/zm/anim/AnimationsTest.java b/src/test/java/schule/ngb/zm/anim/AnimationsTest.java similarity index 100% rename from test/src/schule/ngb/zm/anim/AnimationsTest.java rename to src/test/java/schule/ngb/zm/anim/AnimationsTest.java