com.brashmonkey.spriter.Animation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of overlap2d-runtime-libgdx Show documentation
Show all versions of overlap2d-runtime-libgdx Show documentation
overlap2d-runtime-libgdx provides functionality to load, manipulate and render scenes generated by Overlap2D.
package com.brashmonkey.spriter;
import java.util.HashMap;
import com.brashmonkey.spriter.Mainline.Key;
import com.brashmonkey.spriter.Mainline.Key.BoneRef;
import com.brashmonkey.spriter.Mainline.Key.ObjectRef;
import com.brashmonkey.spriter.Timeline.Key.Bone;
import com.brashmonkey.spriter.Timeline.Key.Object;
/**
* Represents an animation of a Spriter SCML file.
* An animation holds {@link Timeline}s and a {@link Mainline} to animate objects.
* Furthermore it holds an {@link #id}, a {@link #length}, a {@link #name} and whether it is {@link #looping} or not.
* @author Trixt0r
*
*/
public class Animation {
public final Mainline mainline;
private final Timeline[] timelines;
private int timelinePointer = 0;
private final HashMap nameToTimeline;
public final int id, length;
public final String name;
public final boolean looping;
Key currentKey;
Timeline.Key[] tweenedKeys, unmappedTweenedKeys;
private boolean prepared;
public Animation(Mainline mainline, int id, String name, int length, boolean looping, int timelines){
this.mainline = mainline;
this.id = id;
this.name = name;
this.length = length;
this.looping = looping;
this.timelines = new Timeline[timelines];
this.prepared = false;
this.nameToTimeline = new HashMap();
//this.currentKey = mainline.getKey(0);
}
/**
* Returns a {@link Timeline} with the given index.
* @param index the index of the timeline
* @return the timeline with the given index
* @throws IndexOutOfBoundsException if the index is out of range
*/
public Timeline getTimeline(int index){
return this.timelines[index];
}
/**
* Returns a {@link Timeline} with the given name.
* @param name the name of the time line
* @return the time line with the given name or null if no time line exists with the given name.
*/
public Timeline getTimeline(String name){
return this.nameToTimeline.get(name);
}
void addTimeline(Timeline timeline){
this.timelines[timelinePointer++] = timeline;
this.nameToTimeline.put(timeline.name, timeline);
}
/**
* Returns the number of time lines this animation holds.
* @return the number of time lines
*/
public int timelines(){
return timelines.length;
}
public String toString(){
String toReturn = getClass().getSimpleName()+"|[id: "+id+", "+name+", duration: "+length+", is looping: "+looping;
toReturn +="Mainline:\n";
toReturn += mainline;
toReturn += "Timelines\n";
for(Timeline timeline: this.timelines)
toReturn += timeline;
toReturn+="]";
return toReturn;
}
/**
* Updates the bone and object structure with the given time to the given root bone.
* @param time The time which has to be between 0 and {@link #length} to work properly.
* @param root The root bone which is not allowed to be null. The whole animation runs relative to the root bone.
*/
public void update(int time, Bone root){
if(!this.prepared) throw new SpriterException("This animation is not ready yet to animate itself. Please call prepare()!");
if(root == null) throw new SpriterException("The root can not be null! Set a root bone to apply this animation relative to the root bone.");
this.currentKey = mainline.getKeyBeforeTime(time);
for(Timeline.Key timelineKey: this.unmappedTweenedKeys)
timelineKey.active = false;
for(BoneRef ref: currentKey.boneRefs)
this.update(ref, root, time);
for(ObjectRef ref: currentKey.objectRefs)
this.update(ref, root, time);
}
protected void update(BoneRef ref, Bone root, int time){
boolean isObject = ref instanceof ObjectRef;
//Get the timelines, the refs pointing to
Timeline timeline = getTimeline(ref.timeline);
Timeline.Key key = timeline.getKey(ref.key);
Timeline.Key nextKey = timeline.getKey((ref.key+1)%timeline.keys.length);
int currentTime = key.time;
int nextTime = nextKey.time;
if(nextTime < currentTime){
if(!looping) nextKey = key;
else nextTime = length;
}
//Normalize the time
float t = (float)(time - currentTime)/(float)(nextTime - currentTime);
if(Float.isNaN(t) || Float.isInfinite(t)) t = 1f;
if(currentKey.time > currentTime){
float tMid = (float)(currentKey.time - currentTime)/(float)(nextTime - currentTime);
if(Float.isNaN(tMid) || Float.isInfinite(tMid)) tMid = 0f;
t = (float)(time - currentKey.time)/(float)(nextTime - currentKey.time);
if(Float.isNaN(t) || Float.isInfinite(t)) t = 1f;
t = currentKey.curve.tween(tMid, 1f, t);
}
else
t = currentKey.curve.tween(0f, 1f, t);
//Tween bone/object
Bone bone1 = key.object();
Bone bone2 = nextKey.object();
Bone tweenTarget = this.tweenedKeys[ref.timeline].object();
if(isObject) this.tweenObject((Object)bone1, (Object)bone2, (Object)tweenTarget, t, key.curve, key.spin);
else this.tweenBone(bone1, bone2, tweenTarget, t, key.curve, key.spin);
this.unmappedTweenedKeys[ref.timeline].active = true;
this.unmapTimelineObject(ref.timeline, isObject,(ref.parent != null) ?
this.unmappedTweenedKeys[ref.parent.timeline].object(): root);
}
void unmapTimelineObject(int timeline, boolean isObject, Bone root){
Bone tweenTarget = this.tweenedKeys[timeline].object();
Bone mapTarget = this.unmappedTweenedKeys[timeline].object();
if(isObject) ((Object)mapTarget).set((Object)tweenTarget);
else mapTarget.set(tweenTarget);
mapTarget.unmap(root);
}
protected void tweenBone(Bone bone1, Bone bone2, Bone target, float t, Curve curve, int spin){
target.angle = curve.tweenAngle(bone1.angle, bone2.angle, t, spin);
curve.tweenPoint(bone1.position, bone2.position, t, target.position);
curve.tweenPoint(bone1.scale, bone2.scale, t, target.scale);
curve.tweenPoint(bone1.pivot, bone2.pivot, t, target.pivot);
}
protected void tweenObject(Object object1, Object object2, Object target, float t, Curve curve, int spin){
this.tweenBone(object1, object2, target, t, curve, spin);
target.alpha = curve.tweenAngle(object1.alpha, object2.alpha, t);
target.ref.set(object1.ref);
}
Timeline getSimilarTimeline(Timeline t){
Timeline found = getTimeline(t.name);
if(found == null && t.id < this.timelines()) found = this.getTimeline(t.id);
return found;
}
/*Timeline getSimilarTimeline(BoneRef ref, Collection coveredTimelines){
if(ref.parent == null) return null;
for(BoneRef boneRef: this.currentKey.objectRefs){
Timeline t = this.getTimeline(boneRef.timeline);
if(boneRef.parent != null && boneRef.parent.id == ref.parent.id && !coveredTimelines.contains(t))
return t;
}
return null;
}
Timeline getSimilarTimeline(ObjectRef ref, Collection coveredTimelines){
if(ref.parent == null) return null;
for(ObjectRef objRef: this.currentKey.objectRefs){
Timeline t = this.getTimeline(objRef.timeline);
if(objRef.parent != null && objRef.parent.id == ref.parent.id && !coveredTimelines.contains(t))
return t;
}
return null;
}*/
/**
* Prepares this animation to set this animation in any time state.
* This method has to be called before {@link #update(int, Bone)}.
*/
public void prepare(){
if(this.prepared) return;
this.tweenedKeys = new Timeline.Key[timelines.length];
this.unmappedTweenedKeys = new Timeline.Key[timelines.length];
for(int i = 0; i < this.tweenedKeys.length; i++){
this.tweenedKeys[i] = new Timeline.Key(i);
this.unmappedTweenedKeys[i] = new Timeline.Key(i);
this.tweenedKeys[i].setObject(new Timeline.Key.Object(new Point(0,0)));
this.unmappedTweenedKeys[i].setObject(new Timeline.Key.Object(new Point(0,0)));
}
if(mainline.keys.length > 0) currentKey = mainline.getKey(0);
this.prepared = true;
}
}