Skip to content

Commit

Permalink
Add tests and improve library
Browse files Browse the repository at this point in the history
  • Loading branch information
tbressler committed Mar 26, 2021
1 parent ec3d69b commit da32563
Show file tree
Hide file tree
Showing 16 changed files with 823 additions and 106 deletions.
90 changes: 80 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
# Animatronics

[![License](https://img.shields.io/badge/License-APL%202.0-green.svg)](https://opensource.org/licenses/Apache-2.0)
[![Travis CI](https://travis-ci.org/tbressler/java-animatronics.svg?branch=master)](https://travis-ci.org/tbressler/java-animatronics)
[![Travis CI](https://travis-ci.com/tbressler/java-animatronics.svg?branch=main)](https://travis-ci.com/github/tbressler/java-animatronics)

A very simple and low-level animation library for your Java project.

The name *Animatronic* is a homage to mechatronic puppets which are often used in films and in theme park attractions.
The library provides utilities for:

* **keyframe animation** of values over time
* **elastic values** (change values smoothly over time)

*The name [Animatronics](https://en.wikipedia.org/wiki/Animatronics) is a homage to mechatronic puppets which are often used in films and in theme park attractions.*

## Usage

The library requires JDK 9 or higher.

### Animate a simple value
### Keyframe animation of a value

In order to animate a value over different steps you can simply use the ```Animatronic``` classes:

```Java

Expand All @@ -27,12 +34,58 @@ animation.play();

// ...

// Use the interpolated value at the current point in time.
// Use the interpolated value at the current point in time
// (for example when drawing the value on the screen).
double currentValue = animation.getValue();

```

### Animate any object

### Elastic values

When you're dealing with realtime values and you want to change the displayed value smoothly to that new value, you can use the ```Elastic``` classes.

An example:

```Java

// Initialize the value.
ElasticDouble value = new ElasticValue(0d, 1000, Easings.easeInOutCubic());

// When the realtime value changed, set the value.
value.setValue(15d)

// Use the interpolated value at the current point in time
// (for example when drawing the value on the screen).
double smoothedValue = value.getValue();

```


### Easing functions

The library comes with different predefined easing functions.

```Java

Easing ease1 = Easings.easeInBack();
Easing ease2 = Easings.easeInOutCubic();
// ... and so on.

// You can use the easing functions without
// an animatronic or an elastic value:
double easedValue = ease1.ease(0.1d);

```

You can add your own easing functions by implementing the interface ```Easing```.

The predefined easing functions are inspired by [https://easings.net/](https://easings.net/).


### Extend the base classes

#### Custom animatronics

Use the abstract class ```Animatronic<T>``` in order to animate any object you want. Simply create a class which extends ```Animatronic``` and implement the ```calculateValueInBetween()``` method. The method calculates the interpolated value with the given factor. You can use the helper method ```calculateValue()``` to calculate the interpolated value without any hustle.

Expand Down Expand Up @@ -64,19 +117,36 @@ The library has different default implementations:
* Color with ```AnimatronicColor```
* Point2D with ```AnimatronicPoint2D```

#### Custom elastic values

### Easing functions
You can do the same with the abstract class ```Elastic<T>```:

The library comes with different predefined easing functions.
A good example is the ```ElasticDouble``` class:

```Java

Easing e1 = Easings.easeInBack();
Easing e2 = Easings.easeInOutCubic();
// ... and so on.
public class ElasticDouble extends Elastic<Double> {

// ...

@Override
protected Double calculateValueInBetween(Double lastValue, Double nextValue, double factor) {
return calculateValue(lastValue, nextValue, factor);
}

}

```

The library has different default implementations:

* Double with ```ElasticDouble```
* Float with ```ElasticFloat```
* Integer with ```ElasticInteger```
* Color with ```ElasticColor```
* Point2D with ```ElasticPoint2D```


## License

```
Expand Down
9 changes: 4 additions & 5 deletions src/main/java/de/tbressler/animatronics/Animatronic.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public abstract class Animatronic<T, A extends Animatronic<T, A>> {
*/
public Animatronic(T valueAtStart) {
this.valueAtStart = requireNonNull(valueAtStart, "The valueAtStart must not be null");
timeline.put(lastKeyframeAt, new Keyframe(valueAtStart, 0L));
timeline.put(lastKeyframeAt, new Keyframe<>(valueAtStart, 0L));
}


Expand Down Expand Up @@ -74,7 +74,7 @@ public final A keyframe(T value, long duration, Easing easing) {

lastKeyframeAt = lastKeyframeAt + duration;
lastValue = value;
timeline.put(lastKeyframeAt, new Keyframe(value, duration, (easing == null) ? Easings.noEasing() : easing));
timeline.put(lastKeyframeAt, new Keyframe<>(value, duration, (easing == null) ? Easings.noEasing() : easing));

return (A) this;
}
Expand Down Expand Up @@ -162,9 +162,8 @@ public final T getValue(long time) {
double timeInTimeslot = timing - lastEntry.getKey();

Easing easing = nextEntry.getValue().getEasing();
double factor = easing.ease(timeInTimeslot / (double) nextEntry.getValue().getDuration());

return calculateValueInBetween(lastEntry.getValue().getValue(), nextEntry.getValue().getValue(), factor);
return calculateValueInBetween(lastEntry.getValue().getValue(), nextEntry.getValue().getValue(),
easing.ease(timeInTimeslot / (double) nextEntry.getValue().getDuration()));
}

/* Returns the current position in the timeline. */
Expand Down
130 changes: 130 additions & 0 deletions src/main/java/de/tbressler/animatronics/Elastic.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package de.tbressler.animatronics;

import static java.util.Objects.requireNonNull;

/**
* An abstract class for elastic values.
*
* @author Tobias Breßler
* @version 1.0
*/
public abstract class Elastic<T> {

/* The easing from the last to this keyframe. */
private final Easing easing;

/* The duration between the last and the next value. */
private final long duration;

/* The last value. */
private T lastValue;

/* The next value. */
private T nextValue = null;

/* The point in time the value has changed. */
private long changedAt = -1L;

/* The point in time the value should be changed +
to the next value. */
private long changedSince = -1L;


/**
* An abstract class for elastic values.
*
* @param valueAtStart The value at start, must not be null.
* @param duration The duration between values.
*/
public Elastic(T valueAtStart, long duration) {
this(valueAtStart, duration, null);
}

/**
* An abstract class for elastic values.
*
* @param valueAtStart The value at start, must not be null.
* @param duration The duration between values.
* @param easing The easing function.
*/
public Elastic(T valueAtStart, long duration, Easing easing) {
if (duration < 0) throw new IllegalArgumentException("The duration must be greater or equal than 0!");
this.easing = (easing != null) ? easing : Easings.noEasing();
this.duration = duration;
this.lastValue = requireNonNull(valueAtStart, "The value must not be null");
}


/**
* Set the new (next) value.
*
* @param value The new value, must not be null.
*/
public void setValue(T value) {
setValue(System.currentTimeMillis(), value);
}

/**
* Set the new (next) value.
*
* @param time The point in time.
* @param value The new value, must not be null.
*/
public void setValue(long time, T value) {
lastValue = getValue(time);
nextValue = requireNonNull(value, "The value must not be null");
changedAt = time;
changedSince = time + duration;
}


/**
* Get the (interpolated) value at the current point in time.
*
* @return The current value.
*/
public T getValue() {
return getValue(System.currentTimeMillis());
}

/**
* Get the (interpolated) value at the current point in time.
*
* @param time The point in time.
* @return The current value.
*/
public T getValue(long time) {
if ((changedAt == -1L) || (changedAt == time))
return lastValue;
else if (time >= changedSince)
return nextValue;
return calculateValueInBetween(lastValue, nextValue,
easing.ease(((double) time - (double) changedAt) / (double) duration));
}

/**
* Returns the interpolated value between the last and the next value based
* on the factor. Normally the formula should look like this:
*
* interpolated = last + ((next - last) * factor)
*
* @param lastValue The last value.
* @param nextValue The next value.
* @param factor The factor.
* @return The interpolated value.
*/
protected abstract T calculateValueInBetween(T lastValue, T nextValue, double factor);

/**
* Helper function for the calculation of the interpolated value.
*
* @param lastValue The last value.
* @param nextValue The next value.
* @param factor The factor.
* @return The interpolated value.
*/
protected double calculateValue(double lastValue, double nextValue, double factor) {
return lastValue + ((nextValue - lastValue) * factor);
}

}
79 changes: 0 additions & 79 deletions src/main/java/de/tbressler/animatronics/ElasticValue.java

This file was deleted.

Loading

0 comments on commit da32563

Please sign in to comment.