/* * Copyright 2016 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.rendering.animation; import java.util.ArrayList; import java.util.List; import com.google.common.base.Preconditions; /* * Single animation that traverses frames. */ public final class Animation { private enum AnimState { STOPPED, PAUSED, RUNNING } private enum RepeatMode { RUN_ONCE, REPEAT_INFINITE } private enum Direction { FORWARD, REVERSE; } private final List<AnimationListener> listeners = new ArrayList<AnimationListener>(); private final RepeatMode repeatMode; private final float duration; private final Animator animator; private final TimeModifier timeModifier; private Direction direction = Direction.FORWARD; private AnimState currentState = AnimState.STOPPED; private float elapsedTime; /** * @param animator the animator that is updated over time * @param duration the duration in seconds (must be positive) * @param repeatMode the repeat mode * @param timeModifier the time modifier to apply */ private Animation(Animator animator, float duration, RepeatMode repeatMode, TimeModifier timeModifier) { Preconditions.checkArgument(animator != null); Preconditions.checkArgument(repeatMode != null); Preconditions.checkArgument(timeModifier != null); Preconditions.checkArgument(duration > 0); this.animator = animator; this.duration = duration; this.repeatMode = repeatMode; this.timeModifier = timeModifier; } /** * Constructs a new animation that runs once with linear speed. * @param animator the animator that is updated over time * @param duration the duration in seconds * @param timeModifier the time modifier to apply * @return the animation */ public static Animation once(Animator animator, float duration, TimeModifier timeModifier) { Animation anim = new Animation(animator, duration, RepeatMode.RUN_ONCE, timeModifier); return anim; } /** * Creates an animation that loops infinitely * @param animator the animator that is updated over time * @param duration the duration in seconds (must be positive) * @param timeModifier the time modifier to apply * @return the animation */ public static Animation infinite(Animator animator, float duration, TimeModifier timeModifier) { Animation anim = new Animation(animator, duration, RepeatMode.REPEAT_INFINITE, timeModifier); return anim; } /** * Plays the animation forwards. Can be set at any time. * @return this */ public Animation setForwardMode() { return setDirection(Direction.FORWARD); } /** * Plays the animation reverse. Can be set at any time. * @return this */ public Animation setReverseMode() { return setDirection(Direction.REVERSE); } /** * @return true if in reverse mode, false otherwise */ public boolean isReverse() { return direction == Direction.REVERSE; } private Animation setDirection(Direction newDir) { if (direction != newDir) { direction = newDir; elapsedTime = duration - elapsedTime; } return this; } /** * Updates the animation if {@link #start} has been called and is not finished. * * @param delta elapsed time since last update, in seconds. */ public void update(float delta) { if (currentState != AnimState.RUNNING) { return; } elapsedTime += delta; if (elapsedTime >= duration) { if (repeatMode == RepeatMode.RUN_ONCE) { elapsedTime = duration; stop(); return; } else { elapsedTime %= duration; } } updateAnimator(); } private void updateAnimator() { float time = direction == Direction.FORWARD ? elapsedTime : duration - elapsedTime; float ipol = timeModifier.apply(time / duration); animator.apply(ipol); } /** * Notifies that this animation has been set up and is ready for use. * @return this */ public Animation start() { if (currentState == AnimState.STOPPED) { currentState = AnimState.RUNNING; elapsedTime = 0; for (AnimationListener li : this.listeners) { li.onStart(); } updateAnimator(); } return this; } /** * Notifies that this animation is finished or should end. * @return this */ public Animation stop() { if (currentState == AnimState.RUNNING) { currentState = AnimState.STOPPED; updateAnimator(); for (AnimationListener li : this.listeners) { li.onEnd(); } } return this; } /** * Stops an animation without signaling that it is finished and * maintains its current state. * @return this */ public Animation pause() { if (currentState == AnimState.RUNNING) { currentState = AnimState.PAUSED; } return this; } /** * Resumes a paused animation. * @return this */ public Animation resume() { if (currentState == AnimState.PAUSED) { currentState = AnimState.RUNNING; } return this; } /** * Adds a listener for animation events. * * @param li the listener for animation events */ public void addListener(AnimationListener li) { this.listeners.add(li); } /** * Unsubscribes a listener from animation events. * * @param li the listener to stop receiving animation events for */ public void removeListener(AnimationListener li) { this.listeners.remove(li); } /** * Unsubscribes all listener from animation events. */ public void removeAllListeners() { this.listeners.clear(); } /** * @return true if stopped, false otherwise */ public boolean isStopped() { return currentState == AnimState.STOPPED; } /** * @return true if running, false otherwise */ public boolean isRunning() { return currentState == AnimState.RUNNING; } /** * @return true if paused, false otherwise */ public boolean isPaused() { return currentState == AnimState.PAUSED; } /** * @return the duration of the animation in seconds */ public float getDuration() { return duration; } }