Last active
May 27, 2018 04:03
-
-
Save RustyKnight/ec65186aa1f931d453f604010a602ae4 to your computer and use it in GitHub Desktop.
A concept of a generic, reusable TimeLine which can be used to calculate a value between key frames based on the progression through the timeline
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.TreeMap; | |
public class TimeLine<T> { | |
private Map<Float, KeyFrame<T>> mapEvents; | |
private Blender<T> blender; | |
private SplineInterpolator splineInterpolator; | |
public TimeLine(Blender<T> blender) { | |
this(blender, null); | |
} | |
public TimeLine(Blender<T> blender, SplineInterpolator splineInterpolator) { | |
mapEvents = new TreeMap<>(); | |
this.blender = blender; | |
this.splineInterpolator = splineInterpolator; | |
} | |
public void setBlender(Blender<T> blender) { | |
this.blender = blender; | |
} | |
public Blender<T> getBlender() { | |
return blender; | |
} | |
public void setSplineInterpolator(SplineInterpolator splineInterpolator) { | |
this.splineInterpolator = splineInterpolator; | |
} | |
public SplineInterpolator getSplineInterpolator() { | |
return splineInterpolator; | |
} | |
public void add(float progress, T value) { | |
mapEvents.put(progress, new KeyFrame<T>(progress, value)); | |
} | |
public T getValueAt(float progress) { | |
if (progress < 0) { | |
progress = 0; | |
} else if (progress > 1) { | |
progress = 1; | |
} | |
SplineInterpolator interpolator = getSplineInterpolator(); | |
if (interpolator != null) { | |
progress = (float)interpolator.interpolate(progress); | |
} | |
List<KeyFrame<T>> keyFrames = getKeyFramesBetween(progress); | |
float max = keyFrames.get(1).progress - keyFrames.get(0).progress; | |
float value = progress - keyFrames.get(0).progress; | |
float weight = value / max; | |
T blend = blend(keyFrames.get(0).getValue(), keyFrames.get(1).getValue(), 1f - weight); | |
return blend; | |
} | |
public List<KeyFrame<T>> getKeyFramesBetween(float progress) { | |
List<KeyFrame<T>> frames = new ArrayList<>(2); | |
int startAt = 0; | |
Float[] keyFrames = mapEvents.keySet().toArray(new Float[mapEvents.size()]); | |
while (startAt < keyFrames.length && keyFrames[startAt] <= progress) { | |
startAt++; | |
} | |
if (startAt >= keyFrames.length) { | |
startAt = keyFrames.length - 1; | |
} | |
frames.add(mapEvents.get(keyFrames[startAt - 1])); | |
frames.add(mapEvents.get(keyFrames[startAt])); | |
return frames; | |
} | |
protected T blend(T start, T end, float ratio) { | |
return blender.blend(start, end, ratio); | |
} | |
public static interface Blender<T> { | |
public T blend(T start, T end, float ratio); | |
} | |
public class KeyFrame<T> { | |
private float progress; | |
private T value; | |
public KeyFrame(float progress, T value) { | |
this.progress = progress; | |
this.value = value; | |
} | |
public float getProgress() { | |
return progress; | |
} | |
public T getValue() { | |
return value; | |
} | |
@Override | |
public String toString() { | |
return "KeyFrame progress = " + getProgress() + "; value = " + getValue(); | |
} | |
} | |
} |
The TimeLine
makes use of a SplineInterpolator
which can be found at https://gist.github.com/RustyKnight/4da7747831e172dbb6f77d8310ee0023
This allows the TimeLine
to support interpolations of the value based on the time curve - that is, things like slow-in, slow-out
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A sample implementation of a
Blender
for "float" valuesA simple timeline can be established with a series of "key frames", which specify key values at given points along the timeline
A value can then be extracted from the
TimeLine
usinggetValueAt(float)
(where at is a normalised value between0-1
) which will return the value for the specified progressThe overall concept (and partial motivation) is demonstrated at https://stackoverflow.com/questions/50539835/how-can-i-make-a-polygon-object-in-java-pulsate-like-the-chalice-in-the-atari-g/50540173#50540173
The "concept" is also used at