diff --git a/src/main/java/schule/ngb/zm/Constants.java b/src/main/java/schule/ngb/zm/Constants.java index 5b150cc..74046ff 100644 --- a/src/main/java/schule/ngb/zm/Constants.java +++ b/src/main/java/schule/ngb/zm/Constants.java @@ -410,6 +410,7 @@ public class Constants { * wirken sich auf die aktuelle Zeichenmaschine aus und sollten nur von der * Zeichenmaschine selbst vorgenommen werden. */ + // TODO: (ngb) volatile ? /** * Aktuell dargestellte Bilder pro Sekunde. diff --git a/src/main/java/schule/ngb/zm/Options.java b/src/main/java/schule/ngb/zm/Options.java index 3560fc6..7f574ec 100644 --- a/src/main/java/schule/ngb/zm/Options.java +++ b/src/main/java/schule/ngb/zm/Options.java @@ -42,7 +42,8 @@ public final class Options { STOPPED, TERMINATED, IDLE, - DELAYED + DELAYED, + DISPATCHING } /** diff --git a/src/main/java/schule/ngb/zm/Spielemaschine.java b/src/main/java/schule/ngb/zm/Spielemaschine.java index bfed76b..e4680fc 100644 --- a/src/main/java/schule/ngb/zm/Spielemaschine.java +++ b/src/main/java/schule/ngb/zm/Spielemaschine.java @@ -1,5 +1,7 @@ package schule.ngb.zm; +import schule.ngb.zm.layers.DrawableLayer; + import java.awt.Graphics2D; import java.util.Collections; import java.util.Iterator; @@ -13,7 +15,7 @@ public class Spielemaschine extends Zeichenmaschine { private LinkedList updatables; - private GraphicsLayer mainLayer; + private GameLayer mainLayer; public Spielemaschine( String title ) { this(DEFAULT_WIDTH, DEFAULT_HEIGHT, title); @@ -26,7 +28,7 @@ public class Spielemaschine extends Zeichenmaschine { drawables = new LinkedList<>(); updatables = new LinkedList<>(); - mainLayer = new GraphicsLayer(); + mainLayer = new GameLayer(); canvas.addLayer(mainLayer); } @@ -83,7 +85,7 @@ public class Spielemaschine extends Zeichenmaschine { @Override public final void update( double delta ) { synchronized( updatables ) { - List it = Collections.unmodifiableList(updatables); + List it = List.copyOf(updatables); for( Updatable u: it ) { if( u.isActive() ) { u.update(delta); @@ -96,23 +98,27 @@ public class Spielemaschine extends Zeichenmaschine { @Override public final void draw() { - mainLayer.clear(); - synchronized( drawables ) { - List it = Collections.unmodifiableList(drawables); - for( Drawable d: it ) { - if( d.isVisible() ) { - d.draw(mainLayer.getGraphics()); - } - } - } + } - static class GraphicsLayer extends Layer { + private class GameLayer extends Layer { public Graphics2D getGraphics() { return drawing; } + @Override + public void draw( Graphics2D pGraphics ) { + clear(); + List it = List.copyOf(drawables); + for( Drawable d: it ) { + if( d.isVisible() ) { + d.draw(drawing); + } + } + super.draw(pGraphics); + } + } } diff --git a/src/main/java/schule/ngb/zm/Zeichenleinwand.java b/src/main/java/schule/ngb/zm/Zeichenleinwand.java index 5f132ec..7855536 100644 --- a/src/main/java/schule/ngb/zm/Zeichenleinwand.java +++ b/src/main/java/schule/ngb/zm/Zeichenleinwand.java @@ -1,6 +1,7 @@ package schule.ngb.zm; import schule.ngb.zm.layers.ColorLayer; +import schule.ngb.zm.util.Log; import java.awt.Canvas; import java.awt.Graphics; @@ -283,4 +284,6 @@ public class Zeichenleinwand extends Canvas { } } + private static final Log LOG = Log.getLogger(Zeichenleinwand.class); + } diff --git a/src/main/java/schule/ngb/zm/Zeichenmaschine.java b/src/main/java/schule/ngb/zm/Zeichenmaschine.java index 2534c00..b16ca5d 100644 --- a/src/main/java/schule/ngb/zm/Zeichenmaschine.java +++ b/src/main/java/schule/ngb/zm/Zeichenmaschine.java @@ -316,17 +316,20 @@ public class Zeichenmaschine extends Constants { public void windowClosing( WindowEvent e ) { if( running ) { running = false; - teardown(); - cleanup(); + mainThread.interrupt(); + //teardown(); + //cleanup(); } // Give the app a minimum amount of time to shut down // then kill it. - try { - Thread.sleep(5); - } catch( InterruptedException ex ) { - } finally { - quit(true); + while( state != Options.AppState.TERMINATED ) { + Thread.yield(); + if( Thread.interrupted() ) { + break; + } } + // Quit + quit(true); } }); @@ -856,7 +859,7 @@ public class Zeichenmaschine extends Constants { updateState = Options.AppState.DELAYED; Thread.sleep(ms - sub, (int) (timer % 1000000L)); - } catch( InterruptedException ex ) { + } catch( InterruptedException ignored ) { // Nothing } finally { updateState = oldState; @@ -1058,10 +1061,12 @@ public class Zeichenmaschine extends Constants { } /* - * Mouse handling + * Input handling */ private void enqueueEvent( InputEvent evt ) { - eventQueue.add(evt); + if( updateState != Options.AppState.DELAYED ) { + eventQueue.add(evt); + } if( isPaused() || isStopped() ) { dispatchEvents(); @@ -1069,7 +1074,7 @@ public class Zeichenmaschine extends Constants { } private void dispatchEvents() { - synchronized( eventQueue ) { + //synchronized( eventQueue ) { while( !eventQueue.isEmpty() ) { InputEvent evt = eventQueue.poll(); @@ -1090,7 +1095,7 @@ public class Zeichenmaschine extends Constants { break; } } - } + //} } private void handleKeyEvent( KeyEvent evt ) { @@ -1329,10 +1334,11 @@ public class Zeichenmaschine extends Constants { // Call to draw() updateState = Options.AppState.DRAWING; Zeichenmaschine.this.draw(); - updateState = Options.AppState.IDLE; + updateState = Options.AppState.DISPATCHING; // Send latest input events after finishing draw // since these may also block dispatchEvents(); + updateState = Options.AppState.IDLE; } }); } @@ -1341,6 +1347,11 @@ public class Zeichenmaschine extends Constants { while( updateThreadExecutor.isRunning() && !updateThreadExecutor.isWaiting() ) { Thread.yield(); + + if( Thread.interrupted() ) { + running = false; + break; + } } // Display the current buffer content diff --git a/src/main/java/schule/ngb/zm/anim/Animation.java b/src/main/java/schule/ngb/zm/anim/Animation.java index afda8cb..100b656 100644 --- a/src/main/java/schule/ngb/zm/anim/Animation.java +++ b/src/main/java/schule/ngb/zm/anim/Animation.java @@ -2,15 +2,16 @@ package schule.ngb.zm.anim; import schule.ngb.zm.Constants; import schule.ngb.zm.Updatable; +import schule.ngb.zm.util.Validator; import schule.ngb.zm.util.events.EventDispatcher; import java.util.function.DoubleUnaryOperator; -public abstract class Animation implements Updatable { +public abstract class Animation extends Constants implements Updatable { protected int runtime; - protected int elapsed_time = 0; + protected int elapsedTime = 0; protected boolean running = false, finished = false; @@ -23,7 +24,7 @@ public abstract class Animation implements Updatable { public Animation( DoubleUnaryOperator easing ) { this.runtime = Constants.DEFAULT_ANIM_RUNTIME; - this.easing = easing; + this.easing = Validator.requireNotNull(easing); } public Animation( int runtime ) { @@ -33,7 +34,7 @@ public abstract class Animation implements Updatable { public Animation( int runtime, DoubleUnaryOperator easing ) { this.runtime = runtime; - this.easing = easing; + this.easing = Validator.requireNotNull(easing); } public int getRuntime() { @@ -56,17 +57,17 @@ public abstract class Animation implements Updatable { public final void start() { this.initialize(); - elapsed_time = 0; + elapsedTime = 0; running = true; finished = false; - interpolate(easing.applyAsDouble(0.0)); + animate(easing.applyAsDouble(0.0)); initializeEventDispatcher().dispatchEvent("start", this); } public final void stop() { running = false; // Make sure the last animation frame was interpolated correctly - interpolate(easing.applyAsDouble((double) elapsed_time / (double) runtime)); + animate(easing.applyAsDouble((double) elapsedTime / (double) runtime)); this.finish(); finished = true; initializeEventDispatcher().dispatchEvent("stop", this); @@ -82,11 +83,7 @@ public abstract class Animation implements Updatable { public final void await() { while( !finished ) { - try { - Thread.sleep(1); - } catch( InterruptedException ex ) { - // Keep waiting - } + Thread.yield(); } } @@ -97,16 +94,16 @@ public abstract class Animation implements Updatable { @Override public void update( double delta ) { - elapsed_time += (int) (delta * 1000); - if( elapsed_time > runtime ) - elapsed_time = runtime; + elapsedTime += (int) (delta * 1000); + if( elapsedTime > runtime ) + elapsedTime = runtime; - double t = (double) elapsed_time / (double) runtime; + double t = (double) elapsedTime / (double) runtime; if( t >= 1.0 ) { running = false; stop(); } else { - interpolate(easing.applyAsDouble(t)); + animate(easing.applyAsDouble(t)); } } @@ -124,7 +121,7 @@ public abstract class Animation implements Updatable { * @param e Fortschritt der Animation nachdem die Easingfunktion angewandt * wurde. */ - public abstract void interpolate( double e ); + public abstract void animate( double e ); EventDispatcher eventDispatcher; diff --git a/src/main/java/schule/ngb/zm/anim/AnimationFacade.java b/src/main/java/schule/ngb/zm/anim/AnimationFacade.java index 192b540..1a0ca6c 100644 --- a/src/main/java/schule/ngb/zm/anim/AnimationFacade.java +++ b/src/main/java/schule/ngb/zm/anim/AnimationFacade.java @@ -19,8 +19,8 @@ public class AnimationFacade extends Animation { } @Override - public void interpolate( double e ) { - anim.interpolate(e); + public void animate( double e ) { + anim.animate(e); } @Override diff --git a/src/main/java/schule/ngb/zm/anim/AnimationGroup.java b/src/main/java/schule/ngb/zm/anim/AnimationGroup.java index ea2e244..16b24ac 100644 --- a/src/main/java/schule/ngb/zm/anim/AnimationGroup.java +++ b/src/main/java/schule/ngb/zm/anim/AnimationGroup.java @@ -1,76 +1,116 @@ package schule.ngb.zm.anim; -import schule.ngb.zm.shapes.Shape; - -import java.util.Arrays; +import java.util.Collection; +import java.util.List; import java.util.function.DoubleUnaryOperator; -public class AnimationGroup extends Animation { +@SuppressWarnings( "unused" ) +public class AnimationGroup extends Animation { - Animation[] anims; - - private boolean overrideRuntime = false; + List> anims; - public AnimationGroup( DoubleUnaryOperator easing, Animation... anims ) { - super(easing); - this.anims = anims; + private boolean overrideEasing = false; - int maxRuntime = Arrays.stream(this.anims).mapToInt((a) -> a.getRuntime()).reduce(0, Integer::max); - setRuntime(maxRuntime); + private int overrideRuntime = -1; + + private int lag = 0; + + private int active = 0; + + public AnimationGroup( Collection> anims ) { + this(0, -1, null, anims); } - public AnimationGroup( int runtime, DoubleUnaryOperator easing, Animation... anims ) { - super(runtime, easing); - this.anims = anims; - overrideRuntime = true; + public AnimationGroup( int lag, Collection> anims ) { + this(lag, -1, null, anims); + } + + public AnimationGroup( DoubleUnaryOperator easing, Collection> anims ) { + this(0, -1, easing, anims); + } + + public AnimationGroup( int lag, DoubleUnaryOperator easing, Collection> anims ) { + this(lag, -1, easing, anims); + } + + public AnimationGroup( int lag, int runtime, DoubleUnaryOperator easing, Collection> anims ) { + super(); + + this.anims = List.copyOf(anims); + this.lag = lag; + + if( easing != null ) { + this.easing = easing; + overrideEasing = true; + } + + if( runtime > 0 ) { + this.runtime = anims.size() * lag + runtime; + this.overrideRuntime = runtime; + } else { + this.runtime = 0; + for( int i = 0; i < this.anims.size(); i++ ) { + if( i * lag + this.anims.get(i).getRuntime() > this.runtime ) { + this.runtime = i * lag + this.anims.get(i).getRuntime(); + } + } + } } @Override - public Shape getAnimationTarget() { - return null; + public T getAnimationTarget() { + for( Animation anim : anims ) { + if( anim.isActive() ) { + return anim.getAnimationTarget(); + } + } + return anims.get(anims.size() - 1).getAnimationTarget(); } @Override public void update( double delta ) { - if( overrideRuntime ) { - synchronized( anims ) { - for( Animation anim: anims ) { - if( anim.isActive() ) { - anim.update(delta); - } + elapsedTime += (int) (delta * 1000); + // Animation is done. Stop all Animations. + if( elapsedTime > runtime ) { + for( int i = 0; i < anims.size(); i++ ) { + if( anims.get(i).isActive() ) { + anims.get(i).elapsedTime = anims.get(i).runtime; + anims.get(i).stop(); } } - } else { - super.update(delta); + running = false; + this.stop(); } - } - @Override - public void interpolate( double e ) { - synchronized( anims ) { - for( Animation anim: anims ) { - anim.interpolate(e); + while( active < anims.size() && elapsedTime >= active * lag ) { + anims.get(active).start(); + active += 1; + } + + for( int i = 0; i < active; i++ ) { + double t = 0.0; + if( overrideRuntime > 0 ) { + t = (double) (elapsedTime - i*lag) / (double) overrideRuntime; + } else { + t = (double) (elapsedTime - i*lag) / (double) anims.get(i).getRuntime(); + } + + if( t >= 1.0 ) { + anims.get(i).elapsedTime = anims.get(i).runtime; + anims.get(i).stop(); + } else { + double e = overrideEasing ? + easing.applyAsDouble(t) : + anims.get(i).easing.applyAsDouble(t); + + anims.get(i).animate(e); } } } @Override - public void initialize() { - synchronized( anims ) { - for( Animation anim: anims ) { - anim.initialize(); - } - } - } - - @Override - public void finish() { - synchronized( anims ) { - for( Animation anim: anims ) { - anim.finish(); - } - } + public void animate( double e ) { } } diff --git a/src/main/java/schule/ngb/zm/anim/Animations.java b/src/main/java/schule/ngb/zm/anim/Animations.java index fb332e2..533e129 100644 --- a/src/main/java/schule/ngb/zm/anim/Animations.java +++ b/src/main/java/schule/ngb/zm/anim/Animations.java @@ -3,7 +3,6 @@ package schule.ngb.zm.anim; import schule.ngb.zm.Color; import schule.ngb.zm.Constants; import schule.ngb.zm.Vector; -import schule.ngb.zm.util.tasks.FrameSynchronizedTask; import schule.ngb.zm.util.tasks.FramerateLimitedTask; import schule.ngb.zm.util.tasks.TaskRunner; import schule.ngb.zm.util.Log; @@ -125,28 +124,28 @@ public class Animations { 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))); + return play(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))); + return play(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))); + return play(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)); + return play(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 play( T target, int runtime, DoubleUnaryOperator easing, DoubleFunction interpolator, BiConsumer applicator ) { + return play(target, runtime, easing, ( e ) -> applicator.accept(target, interpolator.apply(e))); } - public static final Future animate( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) { + public static final Future play( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) { return TaskRunner.run(new FramerateLimitedTask() { double t = 0.0; @@ -167,8 +166,8 @@ public class Animations { }, target); } - public static final T animateAndWait( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) { - Future future = animate(target, runtime, easing, stepper); + public static final T playAndWait( T target, int runtime, DoubleUnaryOperator easing, DoubleConsumer stepper ) { + Future future = play(target, runtime, easing, stepper); while( !future.isDone() ) { try { return future.get(); @@ -191,7 +190,8 @@ public class Animations { ); }*/ - public static Future> animate( Animation animation ) { + public static Future> play( Animation animation ) { + // TODO: (ngb) Don't start when running return TaskRunner.run(new FramerateLimitedTask() { @Override protected void initialize() { @@ -206,13 +206,13 @@ public class Animations { }, animation); } - public static Animation animateAndWait( Animation animation ) { - Future> future = animate(animation); + public static Animation playAndWait( Animation animation ) { + Future> future = play(animation); animation.await(); return animation; } - public static Future> animate( Animation animation, DoubleUnaryOperator easing ) { + public static Future> play( Animation animation, DoubleUnaryOperator easing ) { final AnimationFacade facade = new AnimationFacade<>(animation, animation.getRuntime(), easing); return TaskRunner.run(new FramerateLimitedTask() { @Override diff --git a/src/main/java/schule/ngb/zm/anim/CircleAnimation.java b/src/main/java/schule/ngb/zm/anim/CircleAnimation.java new file mode 100644 index 0000000..4c0cdb8 --- /dev/null +++ b/src/main/java/schule/ngb/zm/anim/CircleAnimation.java @@ -0,0 +1,39 @@ +package schule.ngb.zm.anim; + + +import schule.ngb.zm.Constants; +import schule.ngb.zm.Vector; +import schule.ngb.zm.shapes.Shape; + +import java.util.function.DoubleUnaryOperator; + +public class CircleAnimation extends Animation { + + private Shape object; + + private double centerx, centery, radius, startangle; + + public CircleAnimation( Shape target, double cx, double cy, int runtime, DoubleUnaryOperator easing ) { + super(runtime, easing); + object = target; + centerx = cx; + centery = cy; + Vector vec = new Vector(target.getX(), target.getY()).sub(cx, cy); + startangle = vec.heading(); + radius = vec.length(); + } + + @Override + public Shape getAnimationTarget() { + return object; + } + + @Override + public void animate( double e ) { + double angle = startangle + Constants.radians(Constants.interpolate(0, 360, e)); + double x = centerx + radius * Constants.cos(angle); + double y = centery + radius * Constants.sin(angle); + object.moveTo(x, y); + } + +} diff --git a/src/main/java/schule/ngb/zm/anim/ContinousAnimation.java b/src/main/java/schule/ngb/zm/anim/ContinousAnimation.java new file mode 100644 index 0000000..924adbe --- /dev/null +++ b/src/main/java/schule/ngb/zm/anim/ContinousAnimation.java @@ -0,0 +1,74 @@ +package schule.ngb.zm.anim; + +@SuppressWarnings( "unused" ) +public class ContinousAnimation extends Animation { + + private final Animation baseAnimation; + + private int lag = 0; + + /** + * Speichert eine Approximation der aktuellen Steigung der Easing-Funktion, + * um im Fall {@code easeInOnly == true} nach dem ersten Durchlauf die + * passende Geschwindigkeit beizubehalten. + */ + private double m = 1.0, lastEase = 0.0; + + private boolean easeInOnly = false; + + public ContinousAnimation( Animation baseAnimation ) { + this(baseAnimation, 0, false); + } + + public ContinousAnimation( Animation baseAnimation, int lag ) { + this(baseAnimation, lag, false); + } + + public ContinousAnimation( Animation baseAnimation, boolean easeInOnly ) { + this(baseAnimation, 0, easeInOnly); + } + + private ContinousAnimation( Animation baseAnimation, int lag, boolean easeInOnly ) { + super(baseAnimation.getRuntime(), baseAnimation.getEasing()); + this.baseAnimation = baseAnimation; + this.lag = lag; + this.easeInOnly = easeInOnly; + } + + @Override + public T getAnimationTarget() { + return baseAnimation.getAnimationTarget(); + } + + @Override + public void update( double delta ) { + elapsedTime += (int) (delta * 1000); + if( elapsedTime >= runtime + lag ) { + elapsedTime %= (runtime + lag); + + if( easeInOnly && easing != null ) { + easing = null; + // runtime = (int)((1.0/m)*(runtime + lag)); + } + } + + double t = (double) elapsedTime / (double) runtime; + if( t >= 1.0 ) { + t = 1.0; + } + if( easing != null ) { + double e = easing.applyAsDouble(t); + animate(e); + m = (e-lastEase)/(delta*1000/(asDouble(runtime))); + lastEase = e; + } else { + animate(t); + } + } + + @Override + public void animate( double e ) { + baseAnimation.animate(e); + } + +} diff --git a/src/main/java/schule/ngb/zm/anim/FadeAnimation.java b/src/main/java/schule/ngb/zm/anim/FadeAnimation.java index ba230bd..4895eda 100644 --- a/src/main/java/schule/ngb/zm/anim/FadeAnimation.java +++ b/src/main/java/schule/ngb/zm/anim/FadeAnimation.java @@ -36,7 +36,7 @@ public class FadeAnimation extends Animation { } @Override - public void interpolate( double e ) { + public void animate( double e ) { object.setFillColor(new Color(fill, (int) Constants.interpolate(fillAlpha, tAlpha, e))); object.setStrokeColor(new Color(stroke, (int) Constants.interpolate(strokeAlpha, tAlpha, e))); } diff --git a/src/main/java/schule/ngb/zm/anim/FillAnimation.java b/src/main/java/schule/ngb/zm/anim/FillAnimation.java index f10c375..92c51d5 100644 --- a/src/main/java/schule/ngb/zm/anim/FillAnimation.java +++ b/src/main/java/schule/ngb/zm/anim/FillAnimation.java @@ -26,7 +26,7 @@ public class FillAnimation extends Animation { } @Override - public void interpolate( double e ) { + public void animate( double e ) { object.setFillColor(Color.interpolate(oFill, tFill, e)); } diff --git a/src/main/java/schule/ngb/zm/anim/MorphAnimation.java b/src/main/java/schule/ngb/zm/anim/MorphAnimation.java index 2e04e40..2681d7c 100644 --- a/src/main/java/schule/ngb/zm/anim/MorphAnimation.java +++ b/src/main/java/schule/ngb/zm/anim/MorphAnimation.java @@ -27,7 +27,7 @@ public class MorphAnimation extends Animation { } @Override - public void interpolate( double e ) { + public void animate( double e ) { object.setX(Constants.interpolate(original.getX(), target.getX(), e)); object.setY(Constants.interpolate(original.getY(), target.getY(), e)); object.setFillColor(Color.interpolate(original.getFillColor(), target.getFillColor(), e)); diff --git a/src/main/java/schule/ngb/zm/anim/MoveAnimation.java b/src/main/java/schule/ngb/zm/anim/MoveAnimation.java index a068a0f..3a826ce 100644 --- a/src/main/java/schule/ngb/zm/anim/MoveAnimation.java +++ b/src/main/java/schule/ngb/zm/anim/MoveAnimation.java @@ -31,7 +31,7 @@ public class MoveAnimation extends Animation { } @Override - public void interpolate( double e ) { + public void animate( double e ) { object.setX(Constants.interpolate(oX, tX, e)); object.setY(Constants.interpolate(oY, tY, e)); } diff --git a/src/main/java/schule/ngb/zm/anim/RotateAnimation.java b/src/main/java/schule/ngb/zm/anim/RotateAnimation.java index 70d1c93..aea03f4 100644 --- a/src/main/java/schule/ngb/zm/anim/RotateAnimation.java +++ b/src/main/java/schule/ngb/zm/anim/RotateAnimation.java @@ -25,7 +25,7 @@ public class RotateAnimation extends Animation { } @Override - public void interpolate( double e ) { + public void animate( double e ) { object.rotateTo(Constants.interpolate(oA, tA, e)); } diff --git a/src/main/java/schule/ngb/zm/anim/StrokeAnimation.java b/src/main/java/schule/ngb/zm/anim/StrokeAnimation.java index ea46455..ea2d5e5 100644 --- a/src/main/java/schule/ngb/zm/anim/StrokeAnimation.java +++ b/src/main/java/schule/ngb/zm/anim/StrokeAnimation.java @@ -25,7 +25,7 @@ public class StrokeAnimation extends Animation { } @Override - public void interpolate( double e ) { + public void animate( double e ) { object.setStrokeColor(Color.interpolate(oFill, tFill, e)); } diff --git a/src/main/java/schule/ngb/zm/anim/WaveAnimation.java b/src/main/java/schule/ngb/zm/anim/WaveAnimation.java new file mode 100644 index 0000000..e551e2f --- /dev/null +++ b/src/main/java/schule/ngb/zm/anim/WaveAnimation.java @@ -0,0 +1,37 @@ +package schule.ngb.zm.anim; + +import schule.ngb.zm.Constants; +import schule.ngb.zm.Options; +import schule.ngb.zm.shapes.Shape; + +import java.util.function.DoubleUnaryOperator; + +public class WaveAnimation extends Animation { + + private Shape object; + + private double strength, sinOffset, previousDelta = 0.0; + + private Options.Direction dir; + + public WaveAnimation( Shape target, double strength, Options.Direction dir, double sinOffset, int runtime, DoubleUnaryOperator easing ) { + super(runtime, easing); + this.object = target; + this.dir = dir; + this.strength = strength; + this.sinOffset = sinOffset; + } + + @Override + public Shape getAnimationTarget() { + return object; + } + + @Override + public void animate( double e ) { + double delta = this.strength * Constants.sin(Constants.interpolate(0.0, Constants.TWO_PI, e) + sinOffset); + object.move((delta - previousDelta) * dir.x, (delta - previousDelta) * dir.y); + previousDelta = delta; + } + +} diff --git a/src/main/java/schule/ngb/zm/layers/ShapesLayer.java b/src/main/java/schule/ngb/zm/layers/ShapesLayer.java index 4f719f6..6e5d7bc 100644 --- a/src/main/java/schule/ngb/zm/layers/ShapesLayer.java +++ b/src/main/java/schule/ngb/zm/layers/ShapesLayer.java @@ -116,6 +116,14 @@ public class ShapesLayer extends Layer { anim.start(); } + + public void play( Animation... anims ) { + for( Animation anim: anims ) { + this.animations.add(anim); + anim.start(); + } + } + public void play( Animation anim, int runtime ) { play(anim, runtime, Easing.DEFAULT_EASING); } diff --git a/src/main/java/schule/ngb/zm/media/Music.java b/src/main/java/schule/ngb/zm/media/Music.java index d6d3813..539e1e5 100644 --- a/src/main/java/schule/ngb/zm/media/Music.java +++ b/src/main/java/schule/ngb/zm/media/Music.java @@ -122,7 +122,7 @@ public class Music implements Audio { * {@inheritDoc} */ @Override - public void play() { + public synchronized void play() { if( openLine() ) { TaskRunner.run(new Runnable() { @Override @@ -147,7 +147,7 @@ public class Music implements Audio { * {@inheritDoc} */ @Override - public void loop() { + public synchronized void loop() { looping = true; play(); } @@ -156,7 +156,7 @@ public class Music implements Audio { * {@inheritDoc} */ @Override - public void stop() { + public synchronized void stop() { playing = false; looping = false; dispose(); @@ -166,7 +166,7 @@ public class Music implements Audio { * {@inheritDoc} */ @Override - public void dispose() { + public synchronized void dispose() { if( audioLine != null ) { if( audioLine.isRunning() ) { playing = false; @@ -175,7 +175,6 @@ public class Music implements Audio { if( audioLine.isOpen() ) { audioLine.drain(); audioLine.close(); - } } try { @@ -189,7 +188,7 @@ public class Music implements Audio { audioStream = null; } - private void stream() { + private synchronized void stream() { audioLine.start(); playing = true; if( eventDispatcher != null ) { diff --git a/src/main/java/schule/ngb/zm/media/Sound.java b/src/main/java/schule/ngb/zm/media/Sound.java index 61dc4a8..fc329c8 100644 --- a/src/main/java/schule/ngb/zm/media/Sound.java +++ b/src/main/java/schule/ngb/zm/media/Sound.java @@ -1,8 +1,8 @@ package schule.ngb.zm.media; import schule.ngb.zm.util.Log; -import schule.ngb.zm.util.io.ResourceStreamProvider; import schule.ngb.zm.util.Validator; +import schule.ngb.zm.util.io.ResourceStreamProvider; import javax.sound.sampled.*; import java.io.IOException; @@ -125,7 +125,7 @@ public class Sound implements Audio { * {@inheritDoc} */ @Override - public void stop() { + public synchronized void stop() { looping = false; if( audioClip.isRunning() ) { audioClip.stop(); @@ -137,7 +137,7 @@ public class Sound implements Audio { * {@inheritDoc} */ @Override - public void play() { + public synchronized void play() { if( this.openClip() ) { audioClip.start(); playing = true; @@ -176,7 +176,7 @@ public class Sound implements Audio { * allerdings wird der aufrufende Thread nicht blockiert und * {@link #dispose()} automatisch am Ende aufgerufen. */ - public void playOnce() { + public synchronized void playOnce() { disposeAfterPlay = true; play(); } @@ -200,16 +200,17 @@ public class Sound implements Audio { * {@inheritDoc} */ @Override - public void loop() { + public synchronized void loop() { loop(Clip.LOOP_CONTINUOUSLY); } /** - * Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und stoppt - * die Wiedergabe dann. + * Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und + * stoppt die Wiedergabe dann. + * * @param count Anzahl der Wiederholungen. */ - public void loop( int count ) { + public synchronized void loop( int count ) { if( count > 0 ) { int loopCount = count; if( loopCount != Clip.LOOP_CONTINUOUSLY ) { @@ -231,7 +232,7 @@ public class Sound implements Audio { * {@inheritDoc} */ @Override - public void dispose() { + public synchronized void dispose() { if( audioClip != null ) { if( audioClip.isRunning() ) { audioClip.stop(); diff --git a/src/main/java/schule/ngb/zm/shapes/Rectangle.java b/src/main/java/schule/ngb/zm/shapes/Rectangle.java index ce8cdb9..170ffec 100644 --- a/src/main/java/schule/ngb/zm/shapes/Rectangle.java +++ b/src/main/java/schule/ngb/zm/shapes/Rectangle.java @@ -15,6 +15,8 @@ public class Rectangle extends Shape { this.width = width; this.height = height; this.anchor = Options.Direction.NORTHWEST; + + //this.cacheEnabled = getClass().equals(Rectangle.class); } public Rectangle( Rectangle pRechteck ) { @@ -64,12 +66,10 @@ public class Rectangle extends Shape { public void setWidth( double width ) { this.width = width; - invalidate(); } public void setHeight( double height ) { this.height = height; - invalidate(); } @Override @@ -92,7 +92,6 @@ public class Rectangle extends Shape { super.scale(factor); width *= factor; height *= factor; - invalidate(); } @Override diff --git a/src/main/java/schule/ngb/zm/shapes/Shape.java b/src/main/java/schule/ngb/zm/shapes/Shape.java index e2bd2e0..398d957 100644 --- a/src/main/java/schule/ngb/zm/shapes/Shape.java +++ b/src/main/java/schule/ngb/zm/shapes/Shape.java @@ -66,11 +66,6 @@ public abstract class Shape extends FilledShape { */ protected Options.Direction anchor = Options.Direction.CENTER; - /** - * Zwischenspeicher für die AWT-Shape zu dieser Form. - */ - protected java.awt.Shape awtShape = null; - /** * Setzt die x- und y-Koordinate der Form auf 0. */ @@ -406,13 +401,6 @@ public abstract class Shape extends FilledShape { */ public abstract java.awt.Shape getShape(); - /** - * Interne Methode, um den Zwischenspeicher der Java-AWT Shape zu löschen. - */ - protected void invalidate() { - awtShape = null; - } - /** * Gibt die Begrenzungen der Form zurück. *

@@ -566,12 +554,8 @@ public abstract class Shape extends FilledShape { return; } - if( awtShape == null ) { - awtShape = getShape(); - } - - if( awtShape != null ) { - java.awt.Shape shape = awtShape; + java.awt.Shape shape = getShape(); + if( shape != null ) { if( transform != null ) { shape = transform.createTransformedShape(shape); } diff --git a/src/main/java/schule/ngb/zm/util/Log.java b/src/main/java/schule/ngb/zm/util/Log.java index 3dde791..efa2697 100644 --- a/src/main/java/schule/ngb/zm/util/Log.java +++ b/src/main/java/schule/ngb/zm/util/Log.java @@ -77,7 +77,7 @@ public final class Log { */ public static void enableGlobalLevel( Level level ) { int lvl = Validator.requireNotNull(level).intValue(); - ensureRootLoggerIntialized(); + ensureRootLoggerInitialized(); // Decrease level of root level ConsoleHandlers for output Logger rootLogger = Logger.getLogger(""); @@ -116,22 +116,24 @@ public final class Log { } public static Log getLogger( Class clazz ) { - ensureRootLoggerIntialized(); + ensureRootLoggerInitialized(); return new Log(clazz); } - private static void ensureRootLoggerIntialized() { + private static void ensureRootLoggerInitialized() { if( LOGGING_INIT ) { return; } + if( System.getProperty("java.util.logging.SimpleFormatter.format") == null ) { + System.setProperty("java.util.logging.SimpleFormatter.format", DEFAULT_LOG_FORMAT); + } Logger rootLogger = Logger.getLogger(ROOT_LOGGER); rootLogger.setLevel(Level.INFO); if( System.getProperty("java.util.logging.SimpleFormatter.format") == null && LogManager.getLogManager().getProperty("java.util.logging.SimpleFormatter.format") == null ) { - System.setProperty("java.util.logging.SimpleFormatter.format", DEFAULT_LOG_FORMAT); - //rootLogger.addHandler(new StreamHandler(System.err, new LogFormatter())); + // System.setProperty("java.util.logging.SimpleFormatter.format", DEFAULT_LOG_FORMAT); rootLogger.addHandler(new StreamHandler(System.err, new LogFormatter()) { @Override public synchronized void publish(final LogRecord record) { @@ -139,7 +141,7 @@ public final class Log { flush(); } }); - rootLogger.setUseParentHandlers(false); + // rootLogger.setUseParentHandlers(false); } if( rootLogger.getUseParentHandlers() ) { // This logger was not configured somewhere else diff --git a/src/main/java/schule/ngb/zm/util/io/FontLoader.java b/src/main/java/schule/ngb/zm/util/io/FontLoader.java index 1971f70..87648b0 100644 --- a/src/main/java/schule/ngb/zm/util/io/FontLoader.java +++ b/src/main/java/schule/ngb/zm/util/io/FontLoader.java @@ -4,6 +4,7 @@ import schule.ngb.zm.util.Log; import java.awt.Font; import java.awt.FontFormatException; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Map; @@ -14,20 +15,50 @@ public class FontLoader { private static final Map fontCache = new ConcurrentHashMap<>(); + /** + * Lädt eine Schrift aus einer Datei. + *

+ * Die Schrift wird unter ihrem Dateinamen in den Schriftenspeicher geladen + * und kann danach in der Zeichenmaschine benutzt werden. + * + * Ein Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen + * "Font-Name" geladen und kann danach zum Beispiel in einem + * {@link schule.ngb.zm.shapes.Text} mit {@code text.setFont("Font-Name");} + * verwendet werden. + * + * @param source + * @return + */ public static Font loadFont( String source ) { + String name = source; + // Dateipfad entfernen + int lastIndex = source.lastIndexOf(File.separatorChar); + if( lastIndex > -1 ) { + source.substring(lastIndex + 1); + } + // Dateiendung entfernen + lastIndex = name.lastIndexOf('.'); + if( lastIndex > -1 ) { + name = name.substring(0, lastIndex); + } + return loadFont(name, source); + } + + public static Font loadFont( String name, String source ) { Objects.requireNonNull(source, "Font source may not be null"); if( source.length() == 0 ) { throw new IllegalArgumentException("Font source may not be empty."); } - if( fontCache.containsKey(source) ) { - LOG.trace("Retrieved font <%s> from font cache.", source); - return fontCache.get(source); + if( fontCache.containsKey(name) ) { + LOG.trace("Retrieved font <%s> from font cache.", name); + return fontCache.get(name); } // Look for System fonts Font font = Font.decode(source); if( font != null && source.toLowerCase().contains(font.getFamily().toLowerCase()) ) { + fontCache.put(name, font); fontCache.put(source, font); LOG.debug("Loaded system font for <%s>.", source); return font; @@ -40,10 +71,11 @@ public class FontLoader { font = Font.createFont(Font.TRUETYPE_FONT, in).deriveFont(Font.PLAIN); if( font != null ) { + fontCache.put(name, font); fontCache.put(source, font); //ge.registerFont(font); } - LOG.debug("Loaded custom font from <%s>.", source); + LOG.debug("Loaded custom font from source <%s>.", source); } catch( IOException ioex ) { LOG.error(ioex, "Error loading custom font file from source <%s>.", source); } catch( FontFormatException ffex ) { diff --git a/src/test/java/schule/ngb/zm/anim/AnimationsTest.java b/src/test/java/schule/ngb/zm/anim/AnimationsTest.java index e01e228..21cb20e 100644 --- a/src/test/java/schule/ngb/zm/anim/AnimationsTest.java +++ b/src/test/java/schule/ngb/zm/anim/AnimationsTest.java @@ -61,7 +61,7 @@ class AnimationsTest { private void _animateMove( Shape s, int runtime, DoubleUnaryOperator easing ) { s.moveTo(0, 0); - Future future = Animations.animate( + Future future = Animations.play( s, runtime, easing, ( e ) -> Constants.interpolate(0, zm.getWidth(), e), @@ -90,25 +90,11 @@ class AnimationsTest { 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); + Future future = Animations.play( + s, runtime, easing, (e) -> { + double rad = Math.toRadians(Constants.interpolate(0, 360, e)); 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()); @@ -147,7 +133,7 @@ class AnimationsTest { private void _animateRotate( Shape s, int runtime, DoubleUnaryOperator easing ) { s.moveTo(zm.getWidth() * .5, zm.getHeight() * .5); s.rotateTo(0); - Future future = Animations.animate( + Future future = Animations.play( s, runtime, easing, ( e ) -> s.rotateTo(Constants.interpolate(0, 720, e)) @@ -179,7 +165,7 @@ class AnimationsTest { 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( + Future future = Animations.play( s, runtime, easing, ( e ) -> Color.interpolate(from, to, e),