diff --git a/src/main/java/com/brashmonkey/spriter/Animation.java b/src/main/java/com/brashmonkey/spriter/Animation.java index 8305d5a..85bbbeb 100644 --- a/src/main/java/com/brashmonkey/spriter/Animation.java +++ b/src/main/java/com/brashmonkey/spriter/Animation.java @@ -1,204 +1,224 @@ 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; + +import java.util.HashMap; + /** - * 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 + * 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); + private final Timeline[] timelines; + private final HashMap nameToTimeline; + Key currentKey; + Timeline.Key[] tweenedKeys, unmappedTweenedKeys; + private int timelinePointer = 0; + 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]; + 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); + public Timeline getTimeline(String name) { + return this.nameToTimeline.get(name); } - - void addTimeline(Timeline timeline){ - this.timelines[timelinePointer++] = timeline; - this.nameToTimeline.put(timeline.name, timeline); + + 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 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; + + 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. + * @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); + public void update(float 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, float 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 = (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 = (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 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); + + 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); } - - 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); + + Timeline getSimilarTimeline(Timeline t) { + Timeline found = getTimeline(t.name); + if (found == null && t.id < this.timelines()) found = this.getTimeline(t.id); + return found; } - - 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; - } + /*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(float, 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; + } } diff --git a/src/main/java/com/brashmonkey/spriter/Box.java b/src/main/java/com/brashmonkey/spriter/Box.java index 92fb677..af9a399 100644 --- a/src/main/java/com/brashmonkey/spriter/Box.java +++ b/src/main/java/com/brashmonkey/spriter/Box.java @@ -3,97 +3,119 @@ import com.brashmonkey.spriter.Entity.ObjectInfo; /** - * Represents a box, which consists of four points: top-left, top-right, bottom-left and bottom-right. - * A box is responsible for checking collisions and calculating a bounding box for a {@link Timeline.Key.Bone}. - * @author Trixt0r + * Represents a box, which consists of four points: top-left, top-right, bottom-left and + * bottom-right. A box is responsible for checking collisions and calculating a bounding box for a + * {@link Timeline.Key.Bone}. * + * @author Trixt0r */ public class Box { - public final Point[] points; - private Rectangle rect; - - /** - * Creates a new box with no witdh and height. - */ - public Box(){ - this.points = new Point[4]; - //this.temp = new Point[4]; - for(int i = 0; i < 4; i++){ - this.points[i] = new Point(0,0); - //this.temp[i] = new Point(0,0); - } - this.rect = new Rectangle(0,0,0,0); - } - - /** - * Calculates its four points for the given bone or object with the given info. - * @param boneOrObject the bone or object - * @param info the info - * @throws NullPointerException if info or boneOrObject is null - */ - public void calcFor(Timeline.Key.Bone boneOrObject, ObjectInfo info){ - float width = info.size.width*boneOrObject.scale.x; - float height = info.size.height*boneOrObject.scale.y; - - float pivotX = width*boneOrObject.pivot.x; - float pivotY = height*boneOrObject.pivot.y; - - this.points[0].set(-pivotX,-pivotY); - this.points[1].set(width-pivotX, -pivotY); - this.points[2].set(-pivotX,height-pivotY); - this.points[3].set(width-pivotX,height-pivotY); - - for(int i = 0; i < 4; i++) - this.points[i].rotate(boneOrObject.angle); - for(int i = 0; i < 4; i++) - this.points[i].translate(boneOrObject.position); - } - - /** - * Returns whether the given coordinates lie inside the box of the given bone or object. - * @param boneOrObject the bone or object - * @param info the object info of the given bone or object - * @param x the x coordinate - * @param y the y coordinate - * @return true if the given point lies in the box - * @throws NullPointerException if info or boneOrObject is null - */ - public boolean collides(Timeline.Key.Bone boneOrObject, ObjectInfo info, float x, float y){ - float width = info.size.width*boneOrObject.scale.x; - float height = info.size.height*boneOrObject.scale.y; - - float pivotX = width*boneOrObject.pivot.x; - float pivotY = height*boneOrObject.pivot.y; - - Point point = new Point(x-boneOrObject.position.x,y-boneOrObject.position.y); - point.rotate(-boneOrObject.angle); - - return point.x >= -pivotX && point.x <= width-pivotX && point.y >= -pivotY && point.y <= height-pivotY; - } - - /** - * Returns whether this box is inside the given rectangle. - * @param rect the rectangle - * @return true if one of the four points is inside the rectangle - */ - public boolean isInside(Rectangle rect){ - boolean inside = false; - for(Point p: points) - inside |= rect.isInside(p); - return inside; - } - - /** - * Returns a bounding box for this box. - * @return the bounding box - */ - public Rectangle getBoundingRect(){ - this.rect.set(points[0].x,points[0].y,points[0].x,points[0].y); - this.rect.left = Math.min(Math.min(Math.min(Math.min(points[0].x, points[1].x),points[2].x),points[3].x), this.rect.left); - this.rect.right = Math.max(Math.max(Math.max(Math.max(points[0].x, points[1].x),points[2].x),points[3].x), this.rect.right); - this.rect.top = Math.max(Math.max(Math.max(Math.max(points[0].y, points[1].y),points[2].y),points[3].y), this.rect.top); - this.rect.bottom = Math.min(Math.min(Math.min(Math.min(points[0].y, points[1].y),points[2].y),points[3].y), this.rect.bottom); - return this.rect; - } + public final Point[] points; + private Rectangle rect; + + /** Creates a new box with no witdh and height. */ + public Box() { + this.points = new Point[4]; + // this.temp = new Point[4]; + for (int i = 0; i < 4; i++) { + this.points[i] = new Point(0, 0); + // this.temp[i] = new Point(0,0); + } + this.rect = new Rectangle(0, 0, 0, 0); + } + + /** + * Calculates its four points for the given bone or object with the given info. + * + * @param boneOrObject the bone or object + * @param info the info + * @throws NullPointerException if info or boneOrObject is null + */ + public void calcFor(Timeline.Key.Bone boneOrObject, ObjectInfo info) { + float width = info.size.width * boneOrObject.scale.x; + float height = info.size.height * boneOrObject.scale.y; + + float pivotX = width * boneOrObject.pivot.x; + float pivotY = height * boneOrObject.pivot.y; + + this.points[0].set(-pivotX, -pivotY); + this.points[1].set(width - pivotX, -pivotY); + this.points[2].set(-pivotX, height - pivotY); + this.points[3].set(width - pivotX, height - pivotY); + + for (int i = 0; i < 4; i++) this.points[i].rotate(boneOrObject.angle); + for (int i = 0; i < 4; i++) this.points[i].translate(boneOrObject.position); + } + + /** + * Returns whether the given coordinates lie inside the box of the given bone or object. + * + * @param boneOrObject the bone or object + * @param info the object info of the given bone or object + * @param x the x coordinate + * @param y the y coordinate + * @return true if the given point lies in the box + * @throws NullPointerException if info or boneOrObject is null + */ + public boolean collides(Timeline.Key.Bone boneOrObject, ObjectInfo info, float x, float y) { + float width = info.size.width * boneOrObject.scale.x; + float height = info.size.height * boneOrObject.scale.y; + + float pivotX = width * boneOrObject.pivot.x; + float pivotY = height * boneOrObject.pivot.y; + + Point point = new Point(x - boneOrObject.position.x, y - boneOrObject.position.y); + point.rotate(-boneOrObject.angle); + + return point.x >= -pivotX + && point.x <= width - pivotX + && point.y >= -pivotY + && point.y <= height - pivotY; + } + + /** + * Returns whether this box is inside the given rectangle. + * + * @param rect the rectangle + * @return true if one of the four points is inside the rectangle + */ + public boolean isInside(Rectangle rect) { + boolean inside = false; + for (Point p : points) inside |= rect.isInside(p); + return inside; + } + /** + * Returns a bounding box for this box. + * + * @return the bounding box + */ + public Rectangle getBoundingRect() { + this.rect.set(points[0].x, points[0].y, points[0].x, points[0].y); + this.rect.left = + Math.min( + Math.min( + Math.min(Math.min(points[0].x, points[1].x), points[2].x), + points[3].x), + this.rect.left); + this.rect.right = + Math.max( + Math.max( + Math.max(Math.max(points[0].x, points[1].x), points[2].x), + points[3].x), + this.rect.right); + this.rect.top = + Math.max( + Math.max( + Math.max(Math.max(points[0].y, points[1].y), points[2].y), + points[3].y), + this.rect.top); + this.rect.bottom = + Math.min( + Math.min( + Math.min(Math.min(points[0].y, points[1].y), points[2].y), + points[3].y), + this.rect.bottom); + return this.rect; + } } diff --git a/src/main/java/com/brashmonkey/spriter/CCDResolver.java b/src/main/java/com/brashmonkey/spriter/CCDResolver.java index 5e59112..4210e42 100644 --- a/src/main/java/com/brashmonkey/spriter/CCDResolver.java +++ b/src/main/java/com/brashmonkey/spriter/CCDResolver.java @@ -4,60 +4,73 @@ import com.brashmonkey.spriter.Timeline.Key.Bone; /** - * An inverse kinematics resolver implementation. - * An instance of this class uses the CCD (Cyclic Coordinate Descent) algorithm to resolve the constraints. - * @see ccd-algorithm - * and cyclic-coordinate-descent-in-2d . - * @author Trixt0r + * An inverse kinematics resolver implementation. An instance of this class uses the CCD (Cyclic + * Coordinate Descent) algorithm to resolve the constraints. * + * @author Trixt0r + * @see ccd-algorithm and + * cyclic-coordinate-descent-in-2d + * . */ public class CCDResolver extends IKResolver { - - public CCDResolver(Player player) { - super(player); - } - @Override - public void resolve(float x, float y, int chainLength, BoneRef effectorRef) { - //player.unmapObjects(null); - Timeline timeline = player.animation.getTimeline(effectorRef.timeline); - Timeline.Key key = player.tweenedKeys[effectorRef.timeline]; - Timeline.Key unmappedKey = player.unmappedTweenedKeys[effectorRef.timeline]; - Bone effector = key.object(); - Bone unmappedffector = unmappedKey.object(); - float width = (timeline.objectInfo != null) ? timeline.objectInfo.size.width: 200; - width *= unmappedffector.scale.x; - float xx = unmappedffector.position.x+(float)Math.cos(Math.toRadians(unmappedffector.angle))*width, - yy = unmappedffector.position.y+(float)Math.sin(Math.toRadians(unmappedffector.angle))*width; - if(Calculator.distanceBetween(xx, yy, x, y) <= this.tolerance) - return; - - effector.angle = Calculator.angleBetween(unmappedffector.position.x, unmappedffector.position.y, x, y); - if(Math.signum(player.root.scale.x) == -1) effector.angle += 180f; - BoneRef parentRef = effectorRef.parent; - Bone parent = null, unmappedParent = null; - if(parentRef != null){ - parent = player.tweenedKeys[parentRef.timeline].object(); - unmappedParent = player.unmappedTweenedKeys[parentRef.timeline].object(); - effector.angle -= unmappedParent.angle; - } - player.unmapObjects(null); - for(int i = 0; i < chainLength && parentRef != null; i++){ - if(Calculator.distanceBetween(xx, yy, x, y) <= this.tolerance) - return; - parent.angle += Calculator.angleDifference(Calculator.angleBetween(unmappedParent.position.x, unmappedParent.position.y, x, y), - Calculator.angleBetween(unmappedParent.position.x, unmappedParent.position.y, xx, yy)); - parentRef = parentRef.parent; - if(parentRef != null && i < chainLength-1){ - parent = player.tweenedKeys[parentRef.timeline].object(); - unmappedParent = player.unmappedTweenedKeys[parentRef.timeline].object(); - parent.angle -= unmappedParent.angle; - } - else parent = null; - player.unmapObjects(null); - xx = unmappedffector.position.x+(float)Math.cos(Math.toRadians(unmappedffector.angle))*width; - yy = unmappedffector.position.y+(float)Math.sin(Math.toRadians(unmappedffector.angle))*width; - } - } + public CCDResolver(Player player) { + super(player); + } + + @Override + public void resolve(float x, float y, int chainLength, BoneRef effectorRef) { + // player.unmapObjects(null); + Timeline timeline = player.animation.getTimeline(effectorRef.timeline); + Timeline.Key key = player.tweenedKeys[effectorRef.timeline]; + Timeline.Key unmappedKey = player.unmappedTweenedKeys[effectorRef.timeline]; + Bone effector = key.object(); + Bone unmappedffector = unmappedKey.object(); + float width = (timeline.objectInfo != null) ? timeline.objectInfo.size.width : 200; + width *= unmappedffector.scale.x; + float + xx = + unmappedffector.position.x + + (float) Math.cos(Math.toRadians(unmappedffector.angle)) * width, + yy = + unmappedffector.position.y + + (float) Math.sin(Math.toRadians(unmappedffector.angle)) * width; + if (Calculator.distanceBetween(xx, yy, x, y) <= this.tolerance) return; + effector.angle = + Calculator.angleBetween( + unmappedffector.position.x, unmappedffector.position.y, x, y); + if (Math.signum(player.root.scale.x) == -1) effector.angle += 180f; + BoneRef parentRef = effectorRef.parent; + Bone parent = null, unmappedParent = null; + if (parentRef != null) { + parent = player.tweenedKeys[parentRef.timeline].object(); + unmappedParent = player.unmappedTweenedKeys[parentRef.timeline].object(); + effector.angle -= unmappedParent.angle; + } + player.unmapObjects(null); + for (int i = 0; i < chainLength && parentRef != null; i++) { + if (Calculator.distanceBetween(xx, yy, x, y) <= this.tolerance) return; + parent.angle += + Calculator.angleDifference( + Calculator.angleBetween( + unmappedParent.position.x, unmappedParent.position.y, x, y), + Calculator.angleBetween( + unmappedParent.position.x, unmappedParent.position.y, xx, yy)); + parentRef = parentRef.parent; + if (parentRef != null && i < chainLength - 1) { + parent = player.tweenedKeys[parentRef.timeline].object(); + unmappedParent = player.unmappedTweenedKeys[parentRef.timeline].object(); + parent.angle -= unmappedParent.angle; + } else parent = null; + player.unmapObjects(null); + xx = + unmappedffector.position.x + + (float) Math.cos(Math.toRadians(unmappedffector.angle)) * width; + yy = + unmappedffector.position.y + + (float) Math.sin(Math.toRadians(unmappedffector.angle)) * width; + } + } } diff --git a/src/main/java/com/brashmonkey/spriter/Calculator.java b/src/main/java/com/brashmonkey/spriter/Calculator.java index 4f71337..987aae1 100644 --- a/src/main/java/com/brashmonkey/spriter/Calculator.java +++ b/src/main/java/com/brashmonkey/spriter/Calculator.java @@ -3,62 +3,78 @@ import static java.lang.Math.*; /** - * A utility class which provides methods to calculate Spriter specific issues, - * like linear interpolation and rotation around a parent object. - * Other interpolation types are coming with the next releases of Spriter. + * A utility class which provides methods to calculate Spriter specific issues, like linear + * interpolation and rotation around a parent object. Other interpolation types are coming with the + * next releases of Spriter. * * @author Trixt0r - * */ - public class Calculator { - public final static float PI = (float)Math.PI; - public final static float NO_SOLUTION = -1; - - /** - * Calculates the smallest difference between angle a and b. - * @param a first angle (in degrees) - * @param b second angle (in degrees) - * @return Smallest difference between a and b (between 180� and -180�). - */ - public static float angleDifference(float a, float b){ - return ((((a - b) % 360) + 540) % 360) - 180; - } - - /** - * @param x1 x coordinate of first point. - * @param y1 y coordinate of first point. - * @param x2 x coordinate of second point. - * @param y2 y coordinate of second point. - * @return Angle between the two given points. - */ - public static float angleBetween(float x1, float y1, float x2, float y2){ - return (float)toDegrees(atan2(y2-y1,x2-x1)); - } - - /** - * @param x1 x coordinate of first point. - * @param y1 y coordinate of first point. - * @param x2 x coordinate of second point. - * @param y2 y coordinate of second point. - * @return Distance between the two given points. - */ - public static float distanceBetween(float x1, float y1, float x2, float y2){ - float xDiff = x2-x1; - float yDiff = y2-y1; - return (float)sqrt(xDiff*xDiff+yDiff*yDiff); - } - - /** - * Solves the equation a*x^3 + b*x^2 + c*x +d = 0. - * @param a - * @param b - * @param c - * @param d - * @return the solution of the cubic function if it belongs [0, 1], {@link #NO_SOLUTION} otherwise. - */ - public static float solveCubic(float a, float b, float c, float d) { + public static final float PI = (float) Math.PI; + public static final float NO_SOLUTION = -1; + /** multiply by this to convert from radians to degrees */ + public static final float radiansToDegrees = 180f / PI; + + public static final float radDeg = radiansToDegrees; + /** multiply by this to convert from degrees to radians */ + public static final float degreesToRadians = PI / 180; + + public static final float degRad = degreesToRadians; + private static final int SIN_BITS = 14; // 16KB. Adjust for accuracy. + private static final int SIN_MASK = ~(-1 << SIN_BITS); + private static final int SIN_COUNT = SIN_MASK + 1; + private static final float radFull = PI * 2; + private static final float degFull = 360; + private static final float radToIndex = SIN_COUNT / radFull; + private static final float degToIndex = SIN_COUNT / degFull; + + /** + * Calculates the smallest difference between angle a and b. + * + * @param a first angle (in degrees) + * @param b second angle (in degrees) + * @return Smallest difference between a and b (between 180� and -180�). + */ + public static float angleDifference(float a, float b) { + return ((((a - b) % 360) + 540) % 360) - 180; + } + + /** + * @param x1 x coordinate of first point. + * @param y1 y coordinate of first point. + * @param x2 x coordinate of second point. + * @param y2 y coordinate of second point. + * @return Angle between the two given points. + */ + public static float angleBetween(float x1, float y1, float x2, float y2) { + return (float) toDegrees(atan2(y2 - y1, x2 - x1)); + } + + /** + * @param x1 x coordinate of first point. + * @param y1 y coordinate of first point. + * @param x2 x coordinate of second point. + * @param y2 y coordinate of second point. + * @return Distance between the two given points. + */ + public static float distanceBetween(float x1, float y1, float x2, float y2) { + float xDiff = x2 - x1; + float yDiff = y2 - y1; + return sqrt(xDiff * xDiff + yDiff * yDiff); + } + + /** + * Solves the equation a*x^3 + b*x^2 + c*x +d = 0. + * + * @param a + * @param b + * @param c + * @param d + * @return the solution of the cubic function if it belongs [0, 1], {@link #NO_SOLUTION} + * otherwise. + */ + public static float solveCubic(float a, float b, float c, float d) { if (a == 0) return solveQuadratic(b, c, d); if (d == 0) return 0f; @@ -107,17 +123,19 @@ public static float solveCubic(float a, float b, float c, float d) { return NO_SOLUTION; } - /** - * Solves the equation a*x^2 + b*x + c = 0 - * @param a - * @param b - * @param c - * @return the solution for the quadratic function if it belongs [0, 1], {@link #NO_SOLUTION} otherwise. - */ - public static float solveQuadratic(float a, float b, float c) { - float squaredB = squared(b); - float twoA = 2 * a; - float fourAC = 4 * a * c; + /** + * Solves the equation a*x^2 + b*x + c = 0 + * + * @param a + * @param b + * @param c + * @return the solution for the quadratic function if it belongs [0, 1], {@link #NO_SOLUTION} + * otherwise. + */ + public static float solveQuadratic(float a, float b, float c) { + float squaredB = squared(b); + float twoA = 2 * a; + float fourAC = 4 * a * c; float sqrt = sqrt(squaredB - fourAC); float result = (-b + sqrt) / twoA; if (result >= 0 && result <= 1) return result; @@ -128,85 +146,84 @@ public static float solveQuadratic(float a, float b, float c) { return NO_SOLUTION; } - /** - * Returns the square of the given value. - * @param f the value - * @return the square of the value - */ - public static float squared(float f) { return f * f; } - - /** - * Returns the cubed value of the given one. - * @param f the value - * @return the cubed value - */ - public static float cubed(float f) { return f * f * f; } - - /** - * Returns the cubic root of the given value. - * @param f the value - * @return the cubic root - */ - public static float cubicRoot(float f) { return (float) pow(f, 1f / 3f); } - - /** - * Returns the square root of the given value. - * @param x the value - * @return the square root - */ - public static float sqrt(float x){ return (float)Math.sqrt(x); } - - /** - * Returns the arc cosine at the given value. - * @param x the value - * @return the arc cosine - */ - public static float acos(float x){ return (float)Math.acos(x); } - - static private final int SIN_BITS = 14; // 16KB. Adjust for accuracy. - static private final int SIN_MASK = ~(-1 << SIN_BITS); - static private final int SIN_COUNT = SIN_MASK + 1; - - static private final float radFull = PI * 2; - static private final float degFull = 360; - static private final float radToIndex = SIN_COUNT / radFull; - static private final float degToIndex = SIN_COUNT / degFull; - - /** multiply by this to convert from radians to degrees */ - static public final float radiansToDegrees = 180f / PI; - static public final float radDeg = radiansToDegrees; - /** multiply by this to convert from degrees to radians */ - static public final float degreesToRadians = PI / 180; - static public final float degRad = degreesToRadians; - - static private class Sin { - static final float[] table = new float[SIN_COUNT]; - static { - for (int i = 0; i < SIN_COUNT; i++) - table[i] = (float)Math.sin((i + 0.5f) / SIN_COUNT * radFull); - for (int i = 0; i < 360; i += 90) - table[(int)(i * degToIndex) & SIN_MASK] = (float)Math.sin(i * degreesToRadians); - } - } - - /** Returns the sine in radians from a lookup table. */ - static public final float sin (float radians) { - return Sin.table[(int)(radians * radToIndex) & SIN_MASK]; - } - - /** Returns the cosine in radians from a lookup table. */ - static public final float cos (float radians) { - return Sin.table[(int)((radians + PI / 2) * radToIndex) & SIN_MASK]; - } - - /** Returns the sine in radians from a lookup table. */ - static public final float sinDeg (float degrees) { - return Sin.table[(int)(degrees * degToIndex) & SIN_MASK]; - } - - /** Returns the cosine in radians from a lookup table. */ - static public final float cosDeg (float degrees) { - return Sin.table[(int)((degrees + 90) * degToIndex) & SIN_MASK]; - } + /** + * Returns the square of the given value. + * + * @param f the value + * @return the square of the value + */ + public static float squared(float f) { + return f * f; + } + + /** + * Returns the cubed value of the given one. + * + * @param f the value + * @return the cubed value + */ + public static float cubed(float f) { + return f * f * f; + } + /** + * Returns the cubic root of the given value. + * + * @param f the value + * @return the cubic root + */ + public static float cubicRoot(float f) { + return (float) pow(f, 1f / 3f); + } + + /** + * Returns the square root of the given value. + * + * @param x the value + * @return the square root + */ + public static float sqrt(float x) { + return (float) Math.sqrt(x); + } + + /** + * Returns the arc cosine at the given value. + * + * @param x the value + * @return the arc cosine + */ + public static float acos(float x) { + return (float) Math.acos(x); + } + + /** Returns the sine in radians from a lookup table. */ + public static final float sin(float radians) { + return Sin.table[(int) (radians * radToIndex) & SIN_MASK]; + } + + /** Returns the cosine in radians from a lookup table. */ + public static final float cos(float radians) { + return Sin.table[(int) ((radians + PI / 2) * radToIndex) & SIN_MASK]; + } + + /** Returns the sine in radians from a lookup table. */ + public static final float sinDeg(float degrees) { + return Sin.table[(int) (degrees * degToIndex) & SIN_MASK]; + } + + /** Returns the cosine in radians from a lookup table. */ + public static final float cosDeg(float degrees) { + return Sin.table[(int) ((degrees + 90) * degToIndex) & SIN_MASK]; + } + + private static class Sin { + static final float[] table = new float[SIN_COUNT]; + + static { + for (int i = 0; i < SIN_COUNT; i++) + table[i] = (float) Math.sin((i + 0.5f) / SIN_COUNT * radFull); + for (int i = 0; i < 360; i += 90) + table[(int) (i * degToIndex) & SIN_MASK] = (float) Math.sin(i * degreesToRadians); + } + } } diff --git a/src/main/java/com/brashmonkey/spriter/Curve.java b/src/main/java/com/brashmonkey/spriter/Curve.java index 8cc7913..5619142 100644 --- a/src/main/java/com/brashmonkey/spriter/Curve.java +++ b/src/main/java/com/brashmonkey/spriter/Curve.java @@ -1,207 +1,285 @@ package com.brashmonkey.spriter; -import static com.brashmonkey.spriter.Calculator.*; +import static com.brashmonkey.spriter.Calculator.NO_SOLUTION; +import static com.brashmonkey.spriter.Calculator.solveCubic; import static com.brashmonkey.spriter.Interpolator.*; /** - * Represents a curve in a Spriter SCML file. - * An instance of this class is responsible for tweening given data. - * The most important method of this class is {@link #tween(float, float, float)}. + * Represents a curve in a Spriter SCML file. An instance of this class is responsible for tweening + * given data. The most important method of this class is {@link #tween(float, float, float)}. * Curves can be changed with sub curves {@link Curve#subCurve}. - * @author Trixt0r * + * @author Trixt0r */ public class Curve { - - /** - * Represents a curve type in a Spriter SCML file. - * @author Trixt0r - * - */ - public static enum Type { - Instant, Linear, Quadratic, Cubic, Quartic, Quintic, Bezier; - } - - /** - * Returns a curve type based on the given curve name. - * @param name the name of the curve - * @return the curve type. {@link Type#Linear} is returned as a default type. - */ - public static Type getType(String name){ - if(name.equals("instant")) return Type.Instant; - else if(name.equals("quadratic")) return Type.Quadratic; - else if(name.equals("cubic")) return Type.Cubic; - else if(name.equals("quartic")) return Type.Quartic; - else if(name.equals("quintic")) return Type.Quintic; - else if(name.equals("bezier")) return Type.Bezier; - else return Type.Linear; - } - - private Type type; - /** - * The sub curve of this curve, which can be null. - */ - public Curve subCurve; - /** - * The constraints of a curve which will affect a curve of the types different from {@link Type#Linear} and {@link Type#Instant}. - */ - public final Constraints constraints = new Constraints(0, 0, 0, 0); - - /** - * Creates a new linear curve. - */ - public Curve(){ - this(Type.Linear); - } - - /** - * Creates a new curve with the given type. - * @param type the curve type - */ - public Curve(Type type){ - this(type, null); - } - - /** - * Creates a new curve with the given type and sub cuve. - * @param type the curve type - * @param subCurve the sub curve. Can be null - */ - public Curve(Type type, Curve subCurve){ - this.setType(type); - this.subCurve = subCurve; - } - - /** - * Sets the type of this curve. - * @param type the curve type. - * @throws SpriterException if the type is null - */ - public void setType(Type type){ - if(type == null) throw new SpriterException("The type of a curve cannot be null!"); - this.type = type; - } - - /** - * Returns the type of this curve. - * @return the curve type - */ - public Type getType(){ - return this.type; - } - - - private float lastCubicSolution = 0f; - /** - * Returns a new value based on the given values. - * Tweens the weight with the set sub curve. - * @param a the start value - * @param b the end value - * @param t the weight which lies between 0.0 and 1.0 - * @return tweened value - */ - public float tween(float a, float b, float t){ - t = tweenSub(0f,1f,t); - switch(type){ - case Instant: return a; - case Linear: return linear(a, b, t); - case Quadratic: return quadratic(a, linear(a, b, constraints.c1), b, t); - case Cubic: return cubic(a, linear(a, b, constraints.c1), linear(a, b, constraints.c2), b, t); - case Quartic: return quartic(a, linear(a, b, constraints.c1), linear(a, b, constraints.c2), linear(a, b, constraints.c3), b, t); - case Quintic: return quintic(a, linear(a, b, constraints.c1), linear(a, b, constraints.c2), linear(a, b, constraints.c3), linear(a, b, constraints.c4), b, t); - case Bezier: float cubicSolution = solveCubic(3f*(constraints.c1-constraints.c3) + 1f, 3f*(constraints.c3-2f*constraints.c1), 3f*constraints.c1, -t); - if(cubicSolution == NO_SOLUTION) cubicSolution = lastCubicSolution; - else lastCubicSolution = cubicSolution; - return linear(a, b, bezier(cubicSolution, 0f, constraints.c2, constraints.c4, 1f)); - default: return linear(a, b, t); - } - } - - /** - * Interpolates the given two points with the given weight and saves the result in the target point. - * @param a the start point - * @param b the end point - * @param t the weight which lies between 0.0 and 1.0 - * @param target the target point to save the result in - */ - public void tweenPoint(Point a, Point b, float t, Point target){ - target.set(this.tween(a.x, b.x, t), this.tween(a.y, b.y, t)); - } - - private float tweenSub(float a, float b, float t){ - if(this.subCurve != null) return subCurve.tween(a, b, t); - else return t; - } - - /** - * Returns a tweened angle based on the given angles, weight and the spin. - * @param a the start angle - * @param b the end angle - * @param t the weight which lies between 0.0 and 1.0 - * @param spin the spin, which is either 0, 1 or -1 - * @return tweened angle - */ - public float tweenAngle(float a, float b, float t, int spin){ - if(spin>0){ - if(b-a < 0) - b+=360; - } - else if(spin < 0){ - if(b-a > 0) - b-=360; - } - else return a; - - return tween(a, b, t); - } - - /** - * @see {@link #tween(float, float, float)} - */ - public float tweenAngle(float a, float b, float t){ - t = tweenSub(0f,1f,t); - switch(type){ - case Instant: return a; - case Linear: return linearAngle(a, b, t); - case Quadratic: return quadraticAngle(a, linearAngle(a, b, constraints.c1), b, t); - case Cubic: return cubicAngle(a, linearAngle(a, b, constraints.c1), linearAngle(a, b, constraints.c2), b, t); - case Quartic: return quarticAngle(a, linearAngle(a, b, constraints.c1), linearAngle(a, b, constraints.c2), linearAngle(a, b, constraints.c3), b, t); - case Quintic: return quinticAngle(a, linearAngle(a, b, constraints.c1), linearAngle(a, b, constraints.c2), linearAngle(a, b, constraints.c3), linearAngle(a, b, constraints.c4), b, t); - case Bezier: float cubicSolution = solveCubic(3f*(constraints.c1-constraints.c3) + 1f, 3f*(constraints.c3-2f*constraints.c1), 3f*constraints.c1, -t); - if(cubicSolution == NO_SOLUTION) cubicSolution = lastCubicSolution; - else lastCubicSolution = cubicSolution; - return linearAngle(a, b, bezier(cubicSolution, 0f, constraints.c2, constraints.c4, 1f)); - default: return linearAngle(a, b, t); - } - } - - public String toString(){ - return getClass().getSimpleName()+"|["+type+":"+constraints+", subCurve: "+subCurve+"]"; - } - - /** - * Represents constraints for a curve. - * Constraints are important for curves which have a order higher than 1. - * @author Trixt0r - * - */ - public static class Constraints{ - public float c1, c2, c3, c4; - - public Constraints(float c1, float c2, float c3, float c4){ - this.set(c1, c2, c3, c4); - } - - public void set(float c1, float c2, float c3, float c4){ - this.c1 = c1; - this.c2 = c2; - this.c3 = c3; - this.c4 = c4; - } - - public String toString(){ - return getClass().getSimpleName()+"| [c1:"+c1+", c2:"+c2+", c3:"+c3+", c4:"+c4+"]"; - } - } + /** + * The constraints of a curve which will affect a curve of the types different from {@link + * Type#Linear} and {@link Type#Instant}. + */ + public final Constraints constraints = new Constraints(0, 0, 0, 0); + /** The sub curve of this curve, which can be null. */ + public Curve subCurve; + + private Type type; + private float lastCubicSolution = 0f; + + /** Creates a new linear curve. */ + public Curve() { + this(Type.Linear); + } + + /** + * Creates a new curve with the given type. + * + * @param type the curve type + */ + public Curve(Type type) { + this(type, null); + } + + /** + * Creates a new curve with the given type and sub cuve. + * + * @param type the curve type + * @param subCurve the sub curve. Can be null + */ + public Curve(Type type, Curve subCurve) { + this.setType(type); + this.subCurve = subCurve; + } + + /** + * Returns a curve type based on the given curve name. + * + * @param name the name of the curve + * @return the curve type. {@link Type#Linear} is returned as a default type. + */ + public static Type getType(String name) { + if (name.equals("instant")) return Type.Instant; + else if (name.equals("quadratic")) return Type.Quadratic; + else if (name.equals("cubic")) return Type.Cubic; + else if (name.equals("quartic")) return Type.Quartic; + else if (name.equals("quintic")) return Type.Quintic; + else if (name.equals("bezier")) return Type.Bezier; + else return Type.Linear; + } + + /** + * Returns the type of this curve. + * + * @return the curve type + */ + public Type getType() { + return this.type; + } + + /** + * Sets the type of this curve. + * + * @param type the curve type. + * @throws SpriterException if the type is null + */ + public void setType(Type type) { + if (type == null) throw new SpriterException("The type of a curve cannot be null!"); + this.type = type; + } + + /** + * Returns a new value based on the given values. Tweens the weight with the set sub curve. + * + * @param a the start value + * @param b the end value + * @param t the weight which lies between 0.0 and 1.0 + * @return tweened value + */ + public float tween(float a, float b, float t) { + t = tweenSub(0f, 1f, t); + switch (type) { + case Instant: + return a; + case Linear: + return linear(a, b, t); + case Quadratic: + return quadratic(a, linear(a, b, constraints.c1), b, t); + case Cubic: + return cubic(a, linear(a, b, constraints.c1), linear(a, b, constraints.c2), b, t); + case Quartic: + return quartic( + a, + linear(a, b, constraints.c1), + linear(a, b, constraints.c2), + linear(a, b, constraints.c3), + b, + t); + case Quintic: + return quintic( + a, + linear(a, b, constraints.c1), + linear(a, b, constraints.c2), + linear(a, b, constraints.c3), + linear(a, b, constraints.c4), + b, + t); + case Bezier: + float cubicSolution = + solveCubic( + 3f * (constraints.c1 - constraints.c3) + 1f, + 3f * (constraints.c3 - 2f * constraints.c1), + 3f * constraints.c1, + -t); + if (cubicSolution == NO_SOLUTION) cubicSolution = lastCubicSolution; + else lastCubicSolution = cubicSolution; + return linear(a, b, bezier(cubicSolution, 0f, constraints.c2, constraints.c4, 1f)); + default: + return linear(a, b, t); + } + } + + /** + * Interpolates the given two points with the given weight and saves the result in the target + * point. + * + * @param a the start point + * @param b the end point + * @param t the weight which lies between 0.0 and 1.0 + * @param target the target point to save the result in + */ + public void tweenPoint(Point a, Point b, float t, Point target) { + target.set(this.tween(a.x, b.x, t), this.tween(a.y, b.y, t)); + } + + private float tweenSub(float a, float b, float t) { + if (this.subCurve != null) return subCurve.tween(a, b, t); + else return t; + } + + /** + * Returns a tweened angle based on the given angles, weight and the spin. + * + * @param a the start angle + * @param b the end angle + * @param t the weight which lies between 0.0 and 1.0 + * @param spin the spin, which is either 0, 1 or -1 + * @return tweened angle + */ + public float tweenAngle(float a, float b, float t, int spin) { + if (spin > 0) { + if (b - a < 0) b += 360; + } else if (spin < 0) { + if (b - a > 0) b -= 360; + } else return a; + + return tween(a, b, t); + } + + /** @see {@link #tween(float, float, float)} */ + public float tweenAngle(float a, float b, float t) { + t = tweenSub(0f, 1f, t); + switch (type) { + case Instant: + return a; + case Linear: + return linearAngle(a, b, t); + case Quadratic: + return quadraticAngle(a, linearAngle(a, b, constraints.c1), b, t); + case Cubic: + return cubicAngle( + a, + linearAngle(a, b, constraints.c1), + linearAngle(a, b, constraints.c2), + b, + t); + case Quartic: + return quarticAngle( + a, + linearAngle(a, b, constraints.c1), + linearAngle(a, b, constraints.c2), + linearAngle(a, b, constraints.c3), + b, + t); + case Quintic: + return quinticAngle( + a, + linearAngle(a, b, constraints.c1), + linearAngle(a, b, constraints.c2), + linearAngle(a, b, constraints.c3), + linearAngle(a, b, constraints.c4), + b, + t); + case Bezier: + float cubicSolution = + solveCubic( + 3f * (constraints.c1 - constraints.c3) + 1f, + 3f * (constraints.c3 - 2f * constraints.c1), + 3f * constraints.c1, + -t); + if (cubicSolution == NO_SOLUTION) cubicSolution = lastCubicSolution; + else lastCubicSolution = cubicSolution; + return linearAngle( + a, b, bezier(cubicSolution, 0f, constraints.c2, constraints.c4, 1f)); + default: + return linearAngle(a, b, t); + } + } + + public String toString() { + return getClass().getSimpleName() + + "|[" + + type + + ":" + + constraints + + ", subCurve: " + + subCurve + + "]"; + } + + /** + * Represents a curve type in a Spriter SCML file. + * + * @author Trixt0r + */ + public enum Type { + Instant, + Linear, + Quadratic, + Cubic, + Quartic, + Quintic, + Bezier + } + + /** + * Represents constraints for a curve. Constraints are important for curves which have a order + * higher than 1. + * + * @author Trixt0r + */ + public static class Constraints { + public float c1, c2, c3, c4; + + public Constraints(float c1, float c2, float c3, float c4) { + this.set(c1, c2, c3, c4); + } + + public void set(float c1, float c2, float c3, float c4) { + this.c1 = c1; + this.c2 = c2; + this.c3 = c3; + this.c4 = c4; + } + + public String toString() { + return getClass().getSimpleName() + + "| [c1:" + + c1 + + ", c2:" + + c2 + + ", c3:" + + c3 + + ", c4:" + + c4 + + "]"; + } + } } diff --git a/src/main/java/com/brashmonkey/spriter/Data.java b/src/main/java/com/brashmonkey/spriter/Data.java index 1fb492f..f87fb6e 100644 --- a/src/main/java/com/brashmonkey/spriter/Data.java +++ b/src/main/java/com/brashmonkey/spriter/Data.java @@ -1,173 +1,189 @@ package com.brashmonkey.spriter; - /** - * Represents all the data which necessary to animate a Spriter generated SCML file. - * An instance of this class holds {@link Folder}s and {@link Entity} instances. - * Specific {@link Folder} and {@link Entity} instances can be accessed via the corresponding methods, i.e. getEntity() - * and getFolder(). - * @author Trixt0r + * Represents all the data which necessary to animate a Spriter generated SCML file. An instance of + * this class holds {@link Folder}s and {@link Entity} instances. Specific {@link Folder} and {@link + * Entity} instances can be accessed via the corresponding methods, i.e. getEntity() and + * getFolder(). * + * @author Trixt0r */ public class Data { - /** - * Represents the rendering mode stored in the spriter data root. - */ - public enum PixelMode { - NONE, PIXEL_ART; - - /** - * @param mode - * @return The pixel mode for the given int value. Default is {@link NONE}. - */ - public static PixelMode get(int mode) { - switch (mode) { - case 1: return PIXEL_ART; - default: return NONE; - } - } - } - - final Folder[] folders; - final Entity[] entities; - private int folderPointer = 0, entityPointer = 0; public final String scmlVersion, generator, generatorVersion; public final PixelMode pixelMode; + final Folder[] folders; + final Entity[] entities; + private int folderPointer = 0, entityPointer = 0; - - Data(String scmlVersion, String generator, String generatorVersion, PixelMode pixelMode, int folders, int entities){ - this.scmlVersion = scmlVersion; - this.generator = generator; - this.generatorVersion = generatorVersion; - this.pixelMode = pixelMode; - this.folders = new Folder[folders]; - this.entities = new Entity[entities]; + Data( + String scmlVersion, + String generator, + String generatorVersion, + PixelMode pixelMode, + int folders, + int entities) { + this.scmlVersion = scmlVersion; + this.generator = generator; + this.generatorVersion = generatorVersion; + this.pixelMode = pixelMode; + this.folders = new Folder[folders]; + this.entities = new Entity[entities]; } - + /** * Adds a folder to this data. + * * @param folder the folder to add */ - void addFolder(Folder folder){ - this.folders[folderPointer++] = folder; + void addFolder(Folder folder) { + this.folders[folderPointer++] = folder; } - + /** * Adds an entity to this data. + * * @param entity the entity to add */ - void addEntity(Entity entity){ - this.entities[entityPointer++] = entity; + void addEntity(Entity entity) { + this.entities[entityPointer++] = entity; } - + /** * Returns a {@link Folder} instance with the given name. + * * @param name the name of the folder * @return the folder with the given name or null if no folder with the given name exists */ - public Folder getFolder(String name){ - int index = getFolderIndex(name); - if(index >= 0) return getFolder(index); - else return null; + public Folder getFolder(String name) { + int index = getFolderIndex(name); + if (index >= 0) return getFolder(index); + else return null; } - + /** * Returns a folder index with the given name. + * * @param name name of the folder - * @return the folder index of the Folder with the given name or -1 if no folder with the given name exists + * @return the folder index of the Folder with the given name or -1 if no folder with the given + * name exists */ - int getFolderIndex(String name){ - for(Folder folder: this.folders) - if(folder.name.equals(name)) return folder.id; - return -1; + int getFolderIndex(String name) { + for (Folder folder : this.folders) if (folder.name.equals(name)) return folder.id; + return -1; } - + /** * Returns a {@link Folder} instance at the given index. + * * @param index the index of the folder * @return the {@link Folder} instance at the given index */ - Folder getFolder(int index){ - return this.folders[index]; + Folder getFolder(int index) { + return this.folders[index]; } - + /** * Returns an {@link Entity} instance with the given index. + * * @param index index of the entity to return. * @return the entity with the given index - * @throws {@link IndexOutOfBoundsException} if the index is out of range + * @throws {@link IndexOutOfBoundsException} if the index is out of range */ - public Entity getEntity(int index){ - return this.entities[index]; + public Entity getEntity(int index) { + return this.entities[index]; } - + /** * Returns an {@link Entity} instance with the given name. + * * @param name the name of the entity * @return the entity with the given name or null if no entity with the given name exists */ - public Entity getEntity(String name){ - int index = getEntityIndex(name); - if(index >= 0) return getEntity(index); - else return null; + public Entity getEntity(String name) { + int index = getEntityIndex(name); + if (index >= 0) return getEntity(index); + else return null; } - + /** * Returns an entity index with the given name. + * * @param name name of the entity - * @return the entity index of the entity with the given name or -1 if no entity with the given name exists + * @return the entity index of the entity with the given name or -1 if no entity with the given + * name exists */ - int getEntityIndex(String name){ - for(Entity entity: this.entities) - if(entity.name.equals(name)) return entity.id; - return -1; + int getEntityIndex(String name) { + for (Entity entity : this.entities) if (entity.name.equals(name)) return entity.id; + return -1; } - + /** * Returns a {@link File} instance in the given {@link Folder} instance at the given file index. + * * @param folder {@link Folder} instance to search in. * @param file index of the file * @return the {@link File} instance in the given folder at the given file index */ - public File getFile(Folder folder, int file){ - return folder.getFile(file); + public File getFile(Folder folder, int file) { + return folder.getFile(file); } - + /** * Returns a {@link File} instance in the given folder at the given file index. + * * @param folder index of the folder * @param file index of the file * @return the {@link File} instance in the given folder at the given file index - * @throws {@link IndexOutOfBoundsException} if the folder or file index are out of range + * @throws {@link IndexOutOfBoundsException} if the folder or file index are out of range */ - public File getFile(int folder, int file){ - return getFile(this.getFolder(folder), file); + public File getFile(int folder, int file) { + return getFile(this.getFolder(folder), file); } - + /** * Returns a {@link File} instance for the given {@link FileReference} instance. + * * @param ref reference to the file * @return the {@link File} instance for the given reference */ - public File getFile(FileReference ref){ - return this.getFile(ref.folder, ref.file); + public File getFile(FileReference ref) { + return this.getFile(ref.folder, ref.file); } - /** - * @return The string representation of this spriter data - */ - public String toString(){ - String toReturn = getClass().getSimpleName() + - "|[Version: " + scmlVersion + - ", Generator: " + generator + - " (" + generatorVersion + ")]"; - for(Folder folder: folders) - toReturn += "\n"+folder; - for(Entity entity: entities) - toReturn += "\n"+entity; - toReturn+="]"; - return toReturn; + /** @return The string representation of this spriter data */ + public String toString() { + String toReturn = + getClass().getSimpleName() + + "|[Version: " + + scmlVersion + + ", Generator: " + + generator + + " (" + + generatorVersion + + ")]"; + for (Folder folder : folders) toReturn += "\n" + folder; + for (Entity entity : entities) toReturn += "\n" + entity; + toReturn += "]"; + return toReturn; } + /** Represents the rendering mode stored in the spriter data root. */ + public enum PixelMode { + NONE, + PIXEL_ART; + + /** + * @param mode + * @return The pixel mode for the given int value. Default is None. + */ + public static PixelMode get(int mode) { + switch (mode) { + case 1: + return PIXEL_ART; + default: + return NONE; + } + } + } } diff --git a/src/main/java/com/brashmonkey/spriter/Dimension.java b/src/main/java/com/brashmonkey/spriter/Dimension.java index f9a27d4..80137f8 100644 --- a/src/main/java/com/brashmonkey/spriter/Dimension.java +++ b/src/main/java/com/brashmonkey/spriter/Dimension.java @@ -1,52 +1,54 @@ package com.brashmonkey.spriter; /** - * Represents a dimension in a 2D space. - * A dimension has a width and a height. - * @author Trixt0r + * Represents a dimension in a 2D space. A dimension has a width and a height. * + * @author Trixt0r */ public class Dimension { - - public float width, height; - - /** - * Creates a new dimension with the given size. - * @param width the width of the dimension - * @param height the height of the dimension - */ - public Dimension(float width, float height){ - this.set(width, height); - } - - /** - * Creates a new dimension with the given size. - * @param size the size - */ - public Dimension(Dimension size){ - this.set(size); - } - - /** - * Sets the size of this dimension to the given size. - * @param width the width of the dimension - * @param height the height of the dimension - */ - public void set(float width, float height){ - this.width = width; - this.height = height; - } - - /** - * Sets the size of this dimension to the given size. - * @param size the size - */ - public void set(Dimension size){ - this.set(size.width, size.height); - } - - public String toString(){ - return "["+width+"x"+height+"]"; - } + public float width, height; + + /** + * Creates a new dimension with the given size. + * + * @param width the width of the dimension + * @param height the height of the dimension + */ + public Dimension(float width, float height) { + this.set(width, height); + } + + /** + * Creates a new dimension with the given size. + * + * @param size the size + */ + public Dimension(Dimension size) { + this.set(size); + } + + /** + * Sets the size of this dimension to the given size. + * + * @param width the width of the dimension + * @param height the height of the dimension + */ + public void set(float width, float height) { + this.width = width; + this.height = height; + } + + /** + * Sets the size of this dimension to the given size. + * + * @param size the size + */ + public void set(Dimension size) { + this.set(size.width, size.height); + } + + public String toString() { + return "[" + width + "x" + height + "]"; + } } diff --git a/src/main/java/com/brashmonkey/spriter/Drawer.java b/src/main/java/com/brashmonkey/spriter/Drawer.java index 149b974..d741163 100644 --- a/src/main/java/com/brashmonkey/spriter/Drawer.java +++ b/src/main/java/com/brashmonkey/spriter/Drawer.java @@ -1,265 +1,298 @@ package com.brashmonkey.spriter; -import java.util.Iterator; - import com.brashmonkey.spriter.Entity.CharacterMap; import com.brashmonkey.spriter.Entity.ObjectInfo; import com.brashmonkey.spriter.Entity.ObjectType; import com.brashmonkey.spriter.Timeline.Key.Bone; import com.brashmonkey.spriter.Timeline.Key.Object; +import java.util.Iterator; + /** - * A Drawer is responsible for drawing a {@link Player}. - * Since this library is meant to be as generic as possible this class has to be abstract, because it cannot be assumed how to draw a resource. - * Anyone who wants to draw a {@link Player} has to know how to draw a resource. A resource can be e.g. a sprite, a texture or a texture region. - * To draw a {@link Player} call {@link #draw(Player)}. This method relies on {@link #draw(Object)}, which has to be implemented with the chosen backend. - * To debug draw a {@link Player} call {@link #drawBones(Player)}, {@link #drawBoxes(Player)} and {@link #drawPoints(Player)}, - * which rely on {@link #rectangle(float, float, float, float)}, {@link #circle(float, float, float)}, {@link #line(float, float, float, float)} and {@link #setColor(float, float, float, float)}. - * @author Trixt0r + * A Drawer is responsible for drawing a {@link Player}. Since this library is meant to be as + * generic as possible this class has to be abstract, because it cannot be assumed how to draw a + * resource. Anyone who wants to draw a {@link Player} has to know how to draw a resource. A + * resource can be e.g. a sprite, a texture or a texture region. To draw a {@link Player} call + * {@link #draw(Player)}. This method relies on {@link #draw(Object)}, which has to be implemented + * with the chosen backend. To debug draw a {@link Player} call {@link #drawBones(Player)}, {@link + * #drawBoxes(Player)} and {@link #drawPoints(Player)}, which rely on {@link #rectangle(float, + * float, float, float)}, {@link #circle(float, float, float)}, {@link #line(float, float, float, + * float)} and {@link #setColor(float, float, float, float)}. * - * @param The backend specific resource. In general such a resource is called "sprite", "texture" or "image". + * @param The backend specific resource. In general such a resource is called "sprite", + * "texture" or "image". + * @author Trixt0r */ public abstract class Drawer { - - /** - * The radius of a point for debug drawing purposes. - */ - public float pointRadius = 5f; - protected Loader loader; - - /** - * Creates a new drawer based on the given loader. - * @param loader the loader containing resources - */ - public Drawer(Loader loader){ - this.loader = loader; - } - - /** - * Sets the loader of this drawer. - * @param loader the loader containing resources - * @throws SpriterException if the loader is null - */ - public void setLoader(Loader loader){ - if(loader == null) throw new SpriterException("The loader instance can not be null!"); - this.loader = loader; - } - - /** - * Draws the bones of the given player composed of lines. - * @param player the player to draw - */ - public void drawBones(Player player){ - this.setColor(1, 0, 0, 1); - Iterator it = player.boneIterator(); - while(it.hasNext()){ - Timeline.Key.Bone bone = it.next(); - Timeline.Key key = player.getKeyFor(bone); - if(!key.active) continue; - ObjectInfo info = player.getObjectInfoFor(bone); - Dimension size = info.size; - drawBone(bone, size); - } - /*for(Mainline.Key.BoneRef ref: player.getCurrentKey().boneRefs){ - Timeline.Key key = player.unmappedTweenedKeys[ref.timeline]; - Timeline.Key.Bone bone = key.object(); - if(player.animation.getTimeline(ref.timeline).objectInfo.type != ObjectType.Bone || !key.active) continue; - ObjectInfo info = player.animation.getTimeline(ref.timeline).objectInfo; - if(info == null) continue; - Dimension size = info.size; - drawBone(bone, size); - }*/ - } - - /** - * Draws the given bone composed of lines with the given size. - * @param bone the bone to draw - * @param size the size of the bone - */ - public void drawBone(Bone bone, Dimension size){ - float halfHeight = size.height/2; - float xx = bone.position.x+(float)Math.cos(Math.toRadians(bone.angle))*size.height; - float yy = bone.position.y+(float)Math.sin(Math.toRadians(bone.angle))*size.height; - float x2 = (float)Math.cos(Math.toRadians(bone.angle+90))*halfHeight*bone.scale.y; - float y2 = (float)Math.sin(Math.toRadians(bone.angle+90))*halfHeight*bone.scale.y; - - float targetX = bone.position.x+(float)Math.cos(Math.toRadians(bone.angle))*size.width*bone.scale.x, - targetY = bone.position.y+(float)Math.sin(Math.toRadians(bone.angle))*size.width*bone.scale.x; - float upperPointX = xx+x2, upperPointY = yy+y2; - this.line(bone.position.x, bone.position.y, upperPointX, upperPointY); - this.line(upperPointX, upperPointY, targetX, targetY); - float lowerPointX = xx-x2, lowerPointY = yy-y2; - this.line(bone.position.x, bone.position.y, lowerPointX, lowerPointY); - this.line(lowerPointX, lowerPointY, targetX, targetY); - this.line(bone.position.x, bone.position.y, targetX, targetY); - } - - /** - * Draws the boxes of the player. - * @param player the player to draw the boxes from - */ - public void drawBoxes(Player player){ - this.setColor(0f, 1f, 0f, 1f); - this.drawBoneBoxes(player); - this.drawObjectBoxes(player); - this.drawPoints(player); - } - - /** - * Draws the boxes of all bones of the given player. - * @param player the player to draw the bone boxes of - */ - public void drawBoneBoxes(Player player){ - drawBoneBoxes(player, player.boneIterator()); - } - - /** - * Draws the boxes of all bones of the given player based on the given iterator. - * @param player the player to draw the bone boxes of - * @param it the iterator iterating over the bones to draw - */ - public void drawBoneBoxes(Player player, Iterator it){ - while(it.hasNext()){ - Bone bone = it.next(); - this.drawBox(player.getBox(bone)); - } - } - - /** - * Draws the boxes of the player objects, i.e. sprites and objects. - * @param player the player to draw the object boxes of - */ - public void drawObjectBoxes(Player player){ - drawObjectBoxes(player, player.objectIterator()); - } - - /** - * Draws the boxes of sprites and boxes of the given player based on the given iterator. - * @param player player the player to draw the object boxes of - * @param it the iterator iterating over the object to draw - */ - public void drawObjectBoxes(Player player, Iterator it){ - while(it.hasNext()){ - Object bone = it.next(); - this.drawBox(player.getBox(bone)); - } - } - - /** - * Draws all points of the given player. - * @param player the player to draw the points of. - */ - public void drawPoints(Player player){ - drawPoints(player, player.objectIterator()); - } - - /** - * Draws the points of the given player based on the given iterator. - * @param player player the player to draw the points of - * @param it the iterator iterating over the points to draw - */ - public void drawPoints(Player player, Iterator it){ - while(it.hasNext()){ - Object point = it.next(); - if(player.getObjectInfoFor(point).type == ObjectType.Point){ - float x = point.position.x+(float)(Math.cos(Math.toRadians(point.angle))*pointRadius); - float y = point.position.y+(float)(Math.sin(Math.toRadians(point.angle))*pointRadius); - circle(point.position.x, point.position.y, pointRadius); - line(point.position.x, point.position.y, x,y); - } - } - } - - /** - * Draws the given player with its current character map. - * @param player the player to draw - */ - public void draw(Player player){ - this.draw(player, player.characterMaps); - } - - /** - * Draws the given player with the given character map. - * @param player the player to draw - * @param map the character map to draw - */ - public void draw(Player player, CharacterMap[] maps){ - this.draw(player.objectIterator(), maps); - } - - /** - * Draws the objects the given iterator is providing with the given character map. - * @param it the iterator iterating over the objects to draw - * @param map the character map to draw - */ - public void draw(Iterator it, CharacterMap[] maps){ - while(it.hasNext()){ - Timeline.Key.Object object = it.next(); - if(object.ref.hasFile()){ - if(maps != null){ - for(CharacterMap map: maps) - if(map != null) - object.ref.set(map.get(object.ref)); - } - this.draw(object); - } - } - } - - /** - * Draws the given box composed of lines. - * @param box the box to draw - */ - public void drawBox(Box box){ - this.line(box.points[0].x, box.points[0].y, box.points[1].x, box.points[1].y); - this.line(box.points[1].x, box.points[1].y, box.points[3].x, box.points[3].y); - this.line(box.points[3].x, box.points[3].y, box.points[2].x, box.points[2].y); - this.line(box.points[2].x, box.points[2].y, box.points[0].x, box.points[0].y); - } - - public void drawRectangle(Rectangle rect){ - this.rectangle(rect.left, rect.bottom, rect.size.width, rect.size.height); - } - - /** - * Sets the color for drawing lines, rectangles and circles. - * @param r the red value between 0.0 - 1.0 - * @param g the green value between 0.0 - 1.0 - * @param b the blue value between 0.0 - 1.0 - * @param a the alpha value between 0.0 - 1.0 - */ - public abstract void setColor(float r, float g, float b, float a); - - /** - * Draws a line from (x1, y1) to (x2, y2). - * @param x1 - * @param y1 - * @param x2 - * @param y2 - */ - public abstract void line(float x1, float y1, float x2, float y2); - - /** - * Draws a rectangle with origin at (x, y) and the given size. - * @param x the x coordinate - * @param y the y coordinate - * @param width the width of the size - * @param height the height of the size - */ - public abstract void rectangle(float x, float y, float width, float height); - - /** - * Draws a circle at (x, y) with the given radius. - * @param x the x coordinate - * @param y the y coordinate - * @param radius the radius of the circle - */ - public abstract void circle(float x, float y, float radius); - - /** - * Draws the given object with its current resource. - * @param object the object to draw. - */ - public abstract void draw(Timeline.Key.Object object); + /** The radius of a point for debug drawing purposes. */ + public float pointRadius = 5f; + + protected Loader loader; + + /** + * Creates a new drawer based on the given loader. + * + * @param loader the loader containing resources + */ + public Drawer(Loader loader) { + this.loader = loader; + } + + /** + * Sets the loader of this drawer. + * + * @param loader the loader containing resources + * @throws SpriterException if the loader is null + */ + public void setLoader(Loader loader) { + if (loader == null) throw new SpriterException("The loader instance can not be null!"); + this.loader = loader; + } + + /** + * Draws the bones of the given player composed of lines. + * + * @param player the player to draw + */ + public void drawBones(Player player) { + this.setColor(1, 0, 0, 1); + Iterator it = player.boneIterator(); + while (it.hasNext()) { + Timeline.Key.Bone bone = it.next(); + Timeline.Key key = player.getKeyFor(bone); + if (!key.active) continue; + ObjectInfo info = player.getObjectInfoFor(bone); + Dimension size = info.size; + drawBone(bone, size); + } + /*for(Mainline.Key.BoneRef ref: player.getCurrentKey().boneRefs){ + Timeline.Key key = player.unmappedTweenedKeys[ref.timeline]; + Timeline.Key.Bone bone = key.object(); + if(player.animation.getTimeline(ref.timeline).objectInfo.type != ObjectType.Bone || !key.active) continue; + ObjectInfo info = player.animation.getTimeline(ref.timeline).objectInfo; + if(info == null) continue; + Dimension size = info.size; + drawBone(bone, size); + }*/ + } + + /** + * Draws the given bone composed of lines with the given size. + * + * @param bone the bone to draw + * @param size the size of the bone + */ + public void drawBone(Bone bone, Dimension size) { + float halfHeight = size.height / 2; + float xx = bone.position.x + (float) Math.cos(Math.toRadians(bone.angle)) * size.height; + float yy = bone.position.y + (float) Math.sin(Math.toRadians(bone.angle)) * size.height; + float x2 = (float) Math.cos(Math.toRadians(bone.angle + 90)) * halfHeight * bone.scale.y; + float y2 = (float) Math.sin(Math.toRadians(bone.angle + 90)) * halfHeight * bone.scale.y; + + float + targetX = + bone.position.x + + (float) Math.cos(Math.toRadians(bone.angle)) + * size.width + * bone.scale.x, + targetY = + bone.position.y + + (float) Math.sin(Math.toRadians(bone.angle)) + * size.width + * bone.scale.x; + float upperPointX = xx + x2, upperPointY = yy + y2; + this.line(bone.position.x, bone.position.y, upperPointX, upperPointY); + this.line(upperPointX, upperPointY, targetX, targetY); + + float lowerPointX = xx - x2, lowerPointY = yy - y2; + this.line(bone.position.x, bone.position.y, lowerPointX, lowerPointY); + this.line(lowerPointX, lowerPointY, targetX, targetY); + this.line(bone.position.x, bone.position.y, targetX, targetY); + } + + /** + * Draws the boxes of the player. + * + * @param player the player to draw the boxes from + */ + public void drawBoxes(Player player) { + this.setColor(0f, 1f, 0f, 1f); + this.drawBoneBoxes(player); + this.drawObjectBoxes(player); + this.drawPoints(player); + } + + /** + * Draws the boxes of all bones of the given player. + * + * @param player the player to draw the bone boxes of + */ + public void drawBoneBoxes(Player player) { + drawBoneBoxes(player, player.boneIterator()); + } + + /** + * Draws the boxes of all bones of the given player based on the given iterator. + * + * @param player the player to draw the bone boxes of + * @param it the iterator iterating over the bones to draw + */ + public void drawBoneBoxes(Player player, Iterator it) { + while (it.hasNext()) { + Bone bone = it.next(); + this.drawBox(player.getBox(bone)); + } + } + + /** + * Draws the boxes of the player objects, i.e. sprites and objects. + * + * @param player the player to draw the object boxes of + */ + public void drawObjectBoxes(Player player) { + drawObjectBoxes(player, player.objectIterator()); + } + + /** + * Draws the boxes of sprites and boxes of the given player based on the given iterator. + * + * @param player player the player to draw the object boxes of + * @param it the iterator iterating over the object to draw + */ + public void drawObjectBoxes(Player player, Iterator it) { + while (it.hasNext()) { + Object bone = it.next(); + this.drawBox(player.getBox(bone)); + } + } + + /** + * Draws all points of the given player. + * + * @param player the player to draw the points of. + */ + public void drawPoints(Player player) { + drawPoints(player, player.objectIterator()); + } + + /** + * Draws the points of the given player based on the given iterator. + * + * @param player player the player to draw the points of + * @param it the iterator iterating over the points to draw + */ + public void drawPoints(Player player, Iterator it) { + while (it.hasNext()) { + Object point = it.next(); + if (player.getObjectInfoFor(point).type == ObjectType.Point) { + float x = + point.position.x + + (float) (Math.cos(Math.toRadians(point.angle)) * pointRadius); + float y = + point.position.y + + (float) (Math.sin(Math.toRadians(point.angle)) * pointRadius); + circle(point.position.x, point.position.y, pointRadius); + line(point.position.x, point.position.y, x, y); + } + } + } + + /** + * Draws the given player with its current character map. + * + * @param player the player to draw + */ + public void draw(Player player) { + this.draw(player, player.characterMaps); + } + + /** + * Draws the given player with the given character map. + * + * @param player the player to draw param map the character map to draw + */ + public void draw(Player player, CharacterMap[] maps) { + this.draw(player.objectIterator(), maps); + } + + /** + * Draws the objects the given iterator is providing with the given character map. + * + * @param it the iterator iterating over the objects to draw param map the character map to draw + */ + public void draw(Iterator it, CharacterMap[] maps) { + while (it.hasNext()) { + Timeline.Key.Object object = it.next(); + if (object.ref.hasFile()) { + if (maps != null) { + for (CharacterMap map : maps) + if (map != null) object.ref.set(map.get(object.ref)); + } + this.draw(object); + } + } + } + + /** + * Draws the given box composed of lines. + * + * @param box the box to draw + */ + public void drawBox(Box box) { + this.line(box.points[0].x, box.points[0].y, box.points[1].x, box.points[1].y); + this.line(box.points[1].x, box.points[1].y, box.points[3].x, box.points[3].y); + this.line(box.points[3].x, box.points[3].y, box.points[2].x, box.points[2].y); + this.line(box.points[2].x, box.points[2].y, box.points[0].x, box.points[0].y); + } + + public void drawRectangle(Rectangle rect) { + this.rectangle(rect.left, rect.bottom, rect.size.width, rect.size.height); + } + + /** + * Sets the color for drawing lines, rectangles and circles. + * + * @param r the red value between 0.0 - 1.0 + * @param g the green value between 0.0 - 1.0 + * @param b the blue value between 0.0 - 1.0 + * @param a the alpha value between 0.0 - 1.0 + */ + public abstract void setColor(float r, float g, float b, float a); + + /** + * Draws a line from (x1, y1) to (x2, y2). + * + * @param x1 + * @param y1 + * @param x2 + * @param y2 + */ + public abstract void line(float x1, float y1, float x2, float y2); + + /** + * Draws a rectangle with origin at (x, y) and the given size. + * + * @param x the x coordinate + * @param y the y coordinate + * @param width the width of the size + * @param height the height of the size + */ + public abstract void rectangle(float x, float y, float width, float height); + + /** + * Draws a circle at (x, y) with the given radius. + * + * @param x the x coordinate + * @param y the y coordinate + * @param radius the radius of the circle + */ + public abstract void circle(float x, float y, float radius); + + /** + * Draws the given object with its current resource. + * + * @param object the object to draw. + */ + public abstract void draw(Timeline.Key.Object object); } diff --git a/src/main/java/com/brashmonkey/spriter/Entity.java b/src/main/java/com/brashmonkey/spriter/Entity.java index a630ebb..c262e3a 100644 --- a/src/main/java/com/brashmonkey/spriter/Entity.java +++ b/src/main/java/com/brashmonkey/spriter/Entity.java @@ -5,237 +5,244 @@ import java.util.List; /** - * Represents an entity of a Spriter SCML file. - * An entity holds {@link Animation}s, an {@link #id}, a {@link #name}. - * {@link #characterMaps} and {@link #objectInfos} may be empty. - * @author Trixt0r + * Represents an entity of a Spriter SCML file. An entity holds {@link Animation}s, an {@link #id}, + * a {@link #name}. {@link #characterMaps} and {@link #objectInfos} may be empty. * + * @author Trixt0r */ public class Entity { public final int id; public final String name; private final Animation[] animations; - private int animationPointer = 0; private final HashMap namedAnimations; private final CharacterMap[] characterMaps; - private int charMapPointer = 0; private final ObjectInfo[] objectInfos; + private int animationPointer = 0; + private int charMapPointer = 0; private int objInfoPointer = 0; - - Entity(int id, String name, int animations, int characterMaps, int objectInfos){ - this.id = id; - this.name = name; - this.animations = new Animation[animations]; - this.characterMaps = new CharacterMap[characterMaps]; - this.objectInfos = new ObjectInfo[objectInfos]; - this.namedAnimations = new HashMap(); - } - - void addAnimation(Animation anim){ - this.animations[animationPointer++] = anim; - this.namedAnimations.put(anim.name, anim); - } - - /** - * Returns an {@link Animation} with the given index. - * @param index the index of the animation - * @return the animation with the given index - * @throws IndexOutOfBoundsException if the index is out of range - */ - public Animation getAnimation(int index){ - return this.animations[index]; - } - - /** - * Returns an {@link Animation} with the given name. - * @param name the name of the animation - * @return the animation with the given name or null if no animation exists with the given name - */ - public Animation getAnimation(String name){ - return this.namedAnimations.get(name); - } - - /** - * Returns the number of animations this entity holds. - * @return the number of animations - */ - public int animations(){ - return this.animations.length; - } - - /** - * Returns whether this entity contains the given animation. - * @param anim the animation to check - * @return true if the given animation is in this entity, false otherwise. - */ - public boolean containsAnimation(Animation anim){ - for(Animation a: this.animations) - if(a == anim) return true; - return false; - } - - /** - * Returns the animation with the most number of time lines in this entity. - * @return animation with the maximum amount of time lines. - */ - public Animation getAnimationWithMostTimelines(){ - Animation maxAnim = getAnimation(0); - for(Animation anim: this.animations){ - if(maxAnim.timelines() < anim.timelines()) maxAnim = anim; - } - return maxAnim; - } - + + Entity(int id, String name, int animations, int characterMaps, int objectInfos) { + this.id = id; + this.name = name; + this.animations = new Animation[animations]; + this.characterMaps = new CharacterMap[characterMaps]; + this.objectInfos = new ObjectInfo[objectInfos]; + this.namedAnimations = new HashMap(); + } + + void addAnimation(Animation anim) { + this.animations[animationPointer++] = anim; + this.namedAnimations.put(anim.name, anim); + } + + /** + * Returns an {@link Animation} with the given index. + * + * @param index the index of the animation + * @return the animation with the given index + * @throws IndexOutOfBoundsException if the index is out of range + */ + public Animation getAnimation(int index) { + return this.animations[index]; + } + + /** + * Returns an {@link Animation} with the given name. + * + * @param name the name of the animation + * @return the animation with the given name or null if no animation exists with the given name + */ + public Animation getAnimation(String name) { + return this.namedAnimations.get(name); + } + + /** + * Returns the number of animations this entity holds. + * + * @return the number of animations + */ + public int animations() { + return this.animations.length; + } + + /** + * Returns whether this entity contains the given animation. + * + * @param anim the animation to check + * @return true if the given animation is in this entity, false otherwise. + */ + public boolean containsAnimation(Animation anim) { + for (Animation a : this.animations) if (a == anim) return true; + return false; + } + + /** + * Returns the animation with the most number of time lines in this entity. + * + * @return animation with the maximum amount of time lines. + */ + public Animation getAnimationWithMostTimelines() { + Animation maxAnim = getAnimation(0); + for (Animation anim : this.animations) { + if (maxAnim.timelines() < anim.timelines()) maxAnim = anim; + } + return maxAnim; + } + /** * Returns a {@link CharacterMap} with the given name. + * * @param name name of the character map * @return the character map or null if no character map exists with the given name */ - public CharacterMap getCharacterMap(String name){ - for(CharacterMap map: this.characterMaps) - if(map.name.equals(name)) return map; - return null; + public CharacterMap getCharacterMap(String name) { + for (CharacterMap map : this.characterMaps) if (map.name.equals(name)) return map; + return null; } - - void addCharacterMap(CharacterMap map){ - this.characterMaps[charMapPointer++] = map; + + void addCharacterMap(CharacterMap map) { + this.characterMaps[charMapPointer++] = map; } - - void addInfo(ObjectInfo info){ - this.objectInfos[objInfoPointer++] = info; + + void addInfo(ObjectInfo info) { + this.objectInfos[objInfoPointer++] = info; } - + /** * Returns an {@link ObjectInfo} with the given index. + * * @param index the index of the object info * @return the object info * @throws IndexOutOfBoundsException if index is out of range */ - public ObjectInfo getInfo(int index){ - return this.objectInfos[index]; + public ObjectInfo getInfo(int index) { + return this.objectInfos[index]; } - + /** * Returns an {@link ObjectInfo} with the given name. + * * @param name name of the object info * @return object info or null if no object info exists with the given name */ - public ObjectInfo getInfo(String name){ - for(ObjectInfo info: this.objectInfos) - if(info.name.equals(name)) return info; - return null; + public ObjectInfo getInfo(String name) { + for (ObjectInfo info : this.objectInfos) if (info.name.equals(name)) return info; + return null; } - + /** * Returns an {@link ObjectInfo} with the given name and the given {@link ObjectType} type. + * * @param name the name of the object info * @param type the type if the object info * @return the object info or null if no object info exists with the given name and type */ - public ObjectInfo getInfo(String name, ObjectType type){ - ObjectInfo info = this.getInfo(name); - if(info != null && info.type == type) return info; - else return null; + public ObjectInfo getInfo(String name, ObjectType type) { + ObjectInfo info = this.getInfo(name); + if (info != null && info.type == type) return info; + else return null; + } + + public String toString() { + String toReturn = getClass().getSimpleName() + "|[id: " + id + ", name: " + name + "]"; + toReturn += "Object infos:\n"; + for (ObjectInfo info : this.objectInfos) toReturn += "\n" + info; + toReturn += "Character maps:\n"; + for (CharacterMap map : this.characterMaps) toReturn += "\n" + map; + toReturn += "Animations:\n"; + for (Animation animaton : this.animations) toReturn += "\n" + animaton; + toReturn += "]"; + return toReturn; } - + /** * Represents the object types Spriter supports. - * @author Trixt0r * + * @author Trixt0r */ - public static enum ObjectType{ - Sprite, Bone, Box, Point, Skin; - - /** - * Returns the object type for the given name - * @param name the name of the type - * @return the object type, Sprite is the default value - */ - public static ObjectType getObjectInfoFor(String name){ - if(name.equals("bone")) return Bone; - else if(name.equals("skin")) return Skin; - else if(name.equals("box")) return Box; - else if(name.equals("point")) return Point; - else return Sprite; - } - } - + public enum ObjectType { + Sprite, + Bone, + Box, + Point, + Skin; + + /** + * Returns the object type for the given name + * + * @param name the name of the type + * @return the object type, Sprite is the default value + */ + public static ObjectType getObjectInfoFor(String name) { + if (name.equals("bone")) return Bone; + else if (name.equals("skin")) return Skin; + else if (name.equals("box")) return Box; + else if (name.equals("point")) return Point; + else return Sprite; + } + } + /** - * Represents the object info in a Spriter SCML file. - * An object info holds a {@link #type} and a {@link #name}. - * If the type is a Sprite it holds a list of frames. Otherwise it has a {@link #size} for debug drawing purposes. - * @author Trixt0r + * Represents the object info in a Spriter SCML file. An object info holds a {@link #type} and a + * {@link #name}. If the type is a Sprite it holds a list of frames. Otherwise it has a {@link + * #size} for debug drawing purposes. * + * @author Trixt0r */ - public static class ObjectInfo{ - public final ObjectType type; - public final List frames; - public final String name; - public final Dimension size; - - ObjectInfo(String name, ObjectType type, Dimension size, List frames){ - this.type = type; - this.frames = frames; - this.name = name; - this.size = size; - } - - ObjectInfo(String name, ObjectType type, Dimension size){ - this(name, type, size, new ArrayList()); - } - - ObjectInfo(String name, ObjectType type, List frames){ - this(name, type, new Dimension(0,0), frames); - } - - public String toString(){ - return name + ": "+ type + ", size: "+size+"|frames:\n"+frames; - } - } - + public static class ObjectInfo { + public final ObjectType type; + public final List frames; + public final String name; + public final Dimension size; + + ObjectInfo(String name, ObjectType type, Dimension size, List frames) { + this.type = type; + this.frames = frames; + this.name = name; + this.size = size; + } + + ObjectInfo(String name, ObjectType type, Dimension size) { + this(name, type, size, new ArrayList()); + } + + ObjectInfo(String name, ObjectType type, List frames) { + this(name, type, new Dimension(0, 0), frames); + } + + public String toString() { + return name + ": " + type + ", size: " + size + "|frames:\n" + frames; + } + } + /** - * Represents a Spriter SCML character map. - * A character map maps {@link FileReference}s to {@link FileReference}s. - * It holds an {@link CharacterMap#id} and a {@link CharacterMap#name}. - * @author Trixt0r + * Represents a Spriter SCML character map. A character map maps {@link FileReference}s to + * {@link FileReference}s. It holds an {@link CharacterMap#id} and a {@link CharacterMap#name}. * + * @author Trixt0r */ - public static class CharacterMap extends HashMap{ - private static final long serialVersionUID = 6062776450159802283L; - - public final int id; - public final String name; - - public CharacterMap(int id, String name){ - this.id = id; - this.name = name; - } - - /** - * Returns the mapped reference for the given key. - * @param key the key of the reference - * @return The mapped reference if the key is in this map, otherwise the given key itself is returned. - */ - public FileReference get(FileReference key){ - if(!super.containsKey(key)) return key; - else return super.get(key); - } - } - - public String toString(){ - String toReturn = getClass().getSimpleName()+"|[id: "+id+", name: "+name+"]"; - toReturn +="Object infos:\n"; - for(ObjectInfo info: this.objectInfos) - toReturn += "\n"+info; - toReturn +="Character maps:\n"; - for(CharacterMap map: this.characterMaps) - toReturn += "\n"+map; - toReturn +="Animations:\n"; - for(Animation animaton: this.animations) - toReturn += "\n"+animaton; - toReturn+="]"; - return toReturn; - } + public static class CharacterMap extends HashMap { + private static final long serialVersionUID = 6062776450159802283L; + + public final int id; + public final String name; + public CharacterMap(int id, String name) { + this.id = id; + this.name = name; + } + + /** + * Returns the mapped reference for the given key. + * + * @param key the key of the reference + * @return The mapped reference if the key is in this map, otherwise the given key itself is + * returned. + */ + public FileReference get(FileReference key) { + if (!super.containsKey(key)) return key; + else return super.get(key); + } + } } diff --git a/src/main/java/com/brashmonkey/spriter/File.java b/src/main/java/com/brashmonkey/spriter/File.java index 0dabec2..d6cab5b 100644 --- a/src/main/java/com/brashmonkey/spriter/File.java +++ b/src/main/java/com/brashmonkey/spriter/File.java @@ -1,11 +1,11 @@ package com.brashmonkey.spriter; /** - * Represents a file in a Spriter SCML file. - * A file has an {@link #id}, a {@link #name}. - * A {@link #size} and a {@link #pivot} point, i.e. origin of an image do not have to be set since a file can be a sound file. - * @author Trixt0r + * Represents a file in a Spriter SCML file. A file has an {@link #id}, a {@link #name}. A {@link + * #size} and a {@link #pivot} point, i.e. origin of an image do not have to be set since a file can + * be a sound file. * + * @author Trixt0r */ public class File { @@ -13,24 +13,32 @@ public class File { public final String name; public final Dimension size; public final Point pivot; - - File(int id, String name, Dimension size, Point pivot){ - this.id = id; - this.name = name; - this.size = size; - this.pivot = pivot; + + File(int id, String name, Dimension size, Point pivot) { + this.id = id; + this.name = name; + this.size = size; + this.pivot = pivot; } - + /** - * Returns whether this file is a sprite, i.e. an image which is going to be animated, or not. + * Returns whether this file is a sprite, i.e. an image which is going to be animated, or not. + * * @return whether this file is a sprite or not. */ - public boolean isSprite(){ - return pivot != null && size != null; - } - - public String toString(){ - return getClass().getSimpleName()+"|[id: "+id+", name: "+name+", size: "+size+", pivot: "+pivot; + public boolean isSprite() { + return pivot != null && size != null; } + public String toString() { + return getClass().getSimpleName() + + "|[id: " + + id + + ", name: " + + name + + ", size: " + + size + + ", pivot: " + + pivot; + } } diff --git a/src/main/java/com/brashmonkey/spriter/FileReference.java b/src/main/java/com/brashmonkey/spriter/FileReference.java index 2015d8d..be67836 100644 --- a/src/main/java/com/brashmonkey/spriter/FileReference.java +++ b/src/main/java/com/brashmonkey/spriter/FileReference.java @@ -1,50 +1,49 @@ package com.brashmonkey.spriter; /** - * Represents a reference to a specific file. - * A file reference consists of a folder and file index. - * @author Trixt0r + * Represents a reference to a specific file. A file reference consists of a folder and file index. * + * @author Trixt0r */ public class FileReference { - - public int folder, file; - - public FileReference(int folder, int file){ - this.set(folder, file); - } - - @Override - public int hashCode(){ - return folder*10000+file;//We can have 10000 files per folder - } - - @Override - public boolean equals(Object ref){ - if(ref instanceof FileReference){ - return this.file == ((FileReference)ref).file && this.folder == ((FileReference)ref).folder; - } else return false; - } - - public void set(int folder, int file){ - this.folder = folder; - this.file = file; - } - - public void set(FileReference ref){ - this.set(ref.folder, ref.file); - } - - public boolean hasFile(){ - return this.file != -1; - } - - public boolean hasFolder(){ - return this.folder != -1; - } - - public String toString(){ - return "[folder: "+folder+", file: "+file+"]"; - } + public int folder, file; + + public FileReference(int folder, int file) { + this.set(folder, file); + } + + @Override + public int hashCode() { + return folder * 10000 + file; // We can have 10000 files per folder + } + + @Override + public boolean equals(Object ref) { + if (ref instanceof FileReference) { + return this.file == ((FileReference) ref).file + && this.folder == ((FileReference) ref).folder; + } else return false; + } + + public void set(int folder, int file) { + this.folder = folder; + this.file = file; + } + + public void set(FileReference ref) { + this.set(ref.folder, ref.file); + } + + public boolean hasFile() { + return this.file != -1; + } + + public boolean hasFolder() { + return this.folder != -1; + } + + public String toString() { + return "[folder: " + folder + ", file: " + file + "]"; + } } diff --git a/src/main/java/com/brashmonkey/spriter/Folder.java b/src/main/java/com/brashmonkey/spriter/Folder.java index 3225720..776fc21 100644 --- a/src/main/java/com/brashmonkey/spriter/Folder.java +++ b/src/main/java/com/brashmonkey/spriter/Folder.java @@ -1,70 +1,72 @@ package com.brashmonkey.spriter; /** - * Represents a folder in a Spriter SCML file. - * A folder has at least an {@link #id}, {@link #name} and {@link #files} may be empty. - * An instance of this class holds an array of {@link File} instances. - * Specific {@link File} instances can be accessed via the corresponding methods, i.e getFile(). - * @author Trixt0r + * Represents a folder in a Spriter SCML file. A folder has at least an {@link #id}, {@link #name} + * and {@link #files} may be empty. An instance of this class holds an array of {@link File} + * instances. Specific {@link File} instances can be accessed via the corresponding methods, i.e + * getFile(). * + * @author Trixt0r */ public class Folder { - final File[] files; - private int filePointer = 0; public final int id; public final String name; - - Folder(int id, String name, int files){ - this.id = id; - this.name = name; - this.files = new File[files]; + final File[] files; + private int filePointer = 0; + + Folder(int id, String name, int files) { + this.id = id; + this.name = name; + this.files = new File[files]; } - + /** * Adds a {@link File} instance to this folder. + * * @param file the file to add */ - void addFile(File file){ - this.files[filePointer++] = file; + void addFile(File file) { + this.files[filePointer++] = file; } - + /** * Returns a {@link File} instance with the given index. + * * @param index the index of the file * @return the file with the given name */ - public File getFile(int index){ - return files[index]; + public File getFile(int index) { + return files[index]; } - + /** * Returns a {@link File} instance with the given name. + * * @param name the name of the file * @return the file with the given name or null if no file with the given name exists */ - public File getFile(String name){ - int index = getFileIndex(name); - if(index >= 0) return getFile(index); - else return null; + public File getFile(String name) { + int index = getFileIndex(name); + if (index >= 0) return getFile(index); + else return null; } - + /** * Returns a file index with the given name. + * * @param name the name of the file * @return the file index with the given name or -1 if no file with the given name exists */ - int getFileIndex(String name){ - for(File file: this.files) - if(file.name.equals(name)) return file.id; - return -1; + int getFileIndex(String name) { + for (File file : this.files) if (file.name.equals(name)) return file.id; + return -1; } - - public String toString(){ - String toReturn = getClass().getSimpleName()+"|[id: "+id+", name: "+name; - for(File file: files) - toReturn += "\n"+file; - toReturn += "]"; - return toReturn; + + public String toString() { + String toReturn = getClass().getSimpleName() + "|[id: " + id + ", name: " + name; + for (File file : files) toReturn += "\n" + file; + toReturn += "]"; + return toReturn; } } diff --git a/src/main/java/com/brashmonkey/spriter/IKObject.java b/src/main/java/com/brashmonkey/spriter/IKObject.java index 8d90803..ebb21e6 100644 --- a/src/main/java/com/brashmonkey/spriter/IKObject.java +++ b/src/main/java/com/brashmonkey/spriter/IKObject.java @@ -2,67 +2,71 @@ /** * An inverse kinematics objects which defines a constraint for a {@link IKResolver}. - * - * @author Trixt0r * + * @author Trixt0r */ public class IKObject extends Point { - - int chainLength, iterations; - /** - * Creates a new IKObject with the given constraints. - * @param x x coordinate constraint - * @param y y coordinate constraint - * @param length the chain length constraint. - * @param iterations the number of iterations. - */ - public IKObject(float x, float y, int length, int iterations) { - super(x, y); - this.setLength(length); - this.setIterations(iterations); - } - - /** - * Sets the chain length of this ik object. - * The chain length indicates how many parent bones should get affected, when a {@link IKResolver} resolves the constraints. - * @param chainLength the chain length - * @return this ik object for chained operations - * @throws SpriterException if the chain length is smaller than 0 - */ - public IKObject setLength(int chainLength){ - if(chainLength < 0) throw new SpriterException("The chain has to be at least 0!"); - this.chainLength = chainLength; - return this; - } - - /** - * Sets the number of iterations. - * The more iterations a {@link IKResolver} is asked to do, the more precise the result will be. - * @param iterations number of iterations - * @return this ik object for chained operations - * @throws SpriterException if the number of iterations is smaller than 0 - */ - public IKObject setIterations(int iterations){ - if(iterations < 0) throw new SpriterException("The number of iterations has to be at least 1!"); - this.iterations = iterations; - return this; - } - - /** - * Returns the current set chain length. - * @return the chain length - */ - public int getChainLength(){ - return this.chainLength; - } - - /** - * Returns the current set number of iterations. - * @return the number of iterations - */ - public int getIterations(){ - return this.iterations; - } + int chainLength, iterations; + + /** + * Creates a new IKObject with the given constraints. + * + * @param x x coordinate constraint + * @param y y coordinate constraint + * @param length the chain length constraint. + * @param iterations the number of iterations. + */ + public IKObject(float x, float y, int length, int iterations) { + super(x, y); + this.setLength(length); + this.setIterations(iterations); + } + + /** + * Sets the chain length of this ik object. The chain length indicates how many parent bones + * should get affected, when a {@link IKResolver} resolves the constraints. + * + * @param chainLength the chain length + * @return this ik object for chained operations + * @throws SpriterException if the chain length is smaller than 0 + */ + public IKObject setLength(int chainLength) { + if (chainLength < 0) throw new SpriterException("The chain has to be at least 0!"); + this.chainLength = chainLength; + return this; + } + + /** + * Returns the current set chain length. + * + * @return the chain length + */ + public int getChainLength() { + return this.chainLength; + } + + /** + * Returns the current set number of iterations. + * + * @return the number of iterations + */ + public int getIterations() { + return this.iterations; + } + /** + * Sets the number of iterations. The more iterations a {@link IKResolver} is asked to do, the + * more precise the result will be. + * + * @param iterations number of iterations + * @return this ik object for chained operations + * @throws SpriterException if the number of iterations is smaller than 0 + */ + public IKObject setIterations(int iterations) { + if (iterations < 0) + throw new SpriterException("The number of iterations has to be at least 1!"); + this.iterations = iterations; + return this; + } } diff --git a/src/main/java/com/brashmonkey/spriter/IKResolver.java b/src/main/java/com/brashmonkey/spriter/IKResolver.java index 58d75fe..ea4c0ea 100644 --- a/src/main/java/com/brashmonkey/spriter/IKResolver.java +++ b/src/main/java/com/brashmonkey/spriter/IKResolver.java @@ -1,113 +1,124 @@ package com.brashmonkey.spriter; -import java.util.HashMap; -import java.util.Map.Entry; - import com.brashmonkey.spriter.Mainline.Key.BoneRef; import com.brashmonkey.spriter.Timeline.Key.Bone; +import java.util.HashMap; +import java.util.Map.Entry; + /** * A IKResolver is responsible for resolving previously set constraints. - * @see Inverse kinematics - * @author Trixt0r * + * @author Trixt0r + * @see Inverse kinematics */ public abstract class IKResolver { - - /** - * Resolves the inverse kinematics constraint with a specific algtorithm - * @param x the target x value - * @param y the target y value - * @param chainLength number of parents which are affected - * @param effector the actual effector where the resolved information has to be stored in. - */ - protected abstract void resolve(float x, float y, int chainLength, BoneRef effector); - - protected HashMap ikMap; - protected float tolerance; - protected Player player; - /** - * Creates a resolver with a default tolerance of 5f. - */ - public IKResolver(Player player) { - this.tolerance = 5f; - this.ikMap = new HashMap(); - this.setPlayer(player); - } - - /** - * Sets the player for this resolver. - * @param player the player which gets affected. - * @throws SpriterException if player is null - */ - public void setPlayer(Player player){ - if(player == null) throw new SpriterException("player cannot be null!"); - this.player = player; - } - - /** - * Returns the current set player. - * @return the current player. - */ - public Player getPlayer(){ - return this.player; - } - - /** - * Resolves the inverse kinematics constraints with the implemented algorithm in {@link #resolve(float, float, int, SpriterAbstractObject, SpriterAbstractPlayer)}. - * @param player player to apply the resolving. - */ - public void resolve(){ - for(Entry entry: this.ikMap.entrySet()){ - for(int j = 0; j < entry.getKey().iterations; j++) - this.resolve(entry.getKey().x, entry.getKey().y, entry.getKey().chainLength, entry.getValue()); - } - } - - /** - * Adds the given object to the internal IKObject - Bone map. - * This means, the values of the given ik object affect the mapped bone. - * @param ikObject the ik object - * @param boneRef the bone reference which gets affected - */ - public void mapIKObject(IKObject ikObject, BoneRef boneRef){ - this.ikMap.put(ikObject, boneRef); - } - - /** - * Adds the given object to the internal IKObject - Bone map. - * This means, the values of the given ik object affect the mapped bone. - * @param ikObject the ik object - * @param bone the bone which gets affected - */ - public void mapIKObject(IKObject ikObject, Bone bone){ - this.ikMap.put(ikObject, player.getBoneRef(bone)); - } - - /** - * Removes the given object from the internal map. - * @param ikObject the ik object to remove - */ - public void unmapIKObject(IKObject ikObject){ - this.ikMap.remove(ikObject); - } - - /** - * Returns the tolerance of this resolver. - * @return the tolerance - */ - public float getTolerance() { - return tolerance; - } + protected HashMap ikMap; + protected float tolerance; + protected Player player; + + /** Creates a resolver with a default tolerance of 5f. */ + public IKResolver(Player player) { + this.tolerance = 5f; + this.ikMap = new HashMap(); + this.setPlayer(player); + } + + /** + * Resolves the inverse kinematics constraint with a specific algtorithm + * + * @param x the target x value + * @param y the target y value + * @param chainLength number of parents which are affected + * @param effector the actual effector where the resolved information has to be stored in. + */ + protected abstract void resolve(float x, float y, int chainLength, BoneRef effector); + + /** + * Returns the current set player. + * + * @return the current player. + */ + public Player getPlayer() { + return this.player; + } + + /** + * Sets the player for this resolver. + * + * @param player the player which gets affected. + * @throws SpriterException if player is null + */ + public void setPlayer(Player player) { + if (player == null) throw new SpriterException("player cannot be null!"); + this.player = player; + } + + /** + * Resolves the inverse kinematics constraints with the implemented algorithm in {link + * #resolve(float, float, int, SpriterAbstractObject, SpriterAbstractPlayer)}. + * + *

param player player to apply the resolving. + */ + public void resolve() { + for (Entry entry : this.ikMap.entrySet()) { + for (int j = 0; j < entry.getKey().iterations; j++) + this.resolve( + entry.getKey().x, + entry.getKey().y, + entry.getKey().chainLength, + entry.getValue()); + } + } + + /** + * Adds the given object to the internal IKObject - Bone map. This means, the values of the + * given ik object affect the mapped bone. + * + * @param ikObject the ik object + * @param boneRef the bone reference which gets affected + */ + public void mapIKObject(IKObject ikObject, BoneRef boneRef) { + this.ikMap.put(ikObject, boneRef); + } + + /** + * Adds the given object to the internal IKObject - Bone map. This means, the values of the + * given ik object affect the mapped bone. + * + * @param ikObject the ik object + * @param bone the bone which gets affected + */ + public void mapIKObject(IKObject ikObject, Bone bone) { + this.ikMap.put(ikObject, player.getBoneRef(bone)); + } + + /** + * Removes the given object from the internal map. + * + * @param ikObject the ik object to remove + */ + public void unmapIKObject(IKObject ikObject) { + this.ikMap.remove(ikObject); + } - /** - * Sets the tolerance distance of this resolver. - * The resolver should stop the algorithm if the distance to the set ik object is less than the tolerance. - * @param tolerance the tolerance - */ - public void setTolerance(float tolerance) { - this.tolerance = tolerance; - } + /** + * Returns the tolerance of this resolver. + * + * @return the tolerance + */ + public float getTolerance() { + return tolerance; + } + /** + * Sets the tolerance distance of this resolver. The resolver should stop the algorithm if the + * distance to the set ik object is less than the tolerance. + * + * @param tolerance the tolerance + */ + public void setTolerance(float tolerance) { + this.tolerance = tolerance; + } } diff --git a/src/main/java/com/brashmonkey/spriter/Interpolator.java b/src/main/java/com/brashmonkey/spriter/Interpolator.java index 6fdc654..1cc1074 100644 --- a/src/main/java/com/brashmonkey/spriter/Interpolator.java +++ b/src/main/java/com/brashmonkey/spriter/Interpolator.java @@ -1,75 +1,73 @@ package com.brashmonkey.spriter; - - /** * Utility class for various interpolation techniques, Spriter is using. - * @author Trixt0r * + * @author Trixt0r */ public class Interpolator { - - public static float linear(float a, float b, float t){ - return a+(b-a)*t; - } - - public static float linearAngle(float a, float b, float t){ - return a + Calculator.angleDifference(b, a)*t; - } - - public static float quadratic(float a, float b, float c, float t){ - return linear(linear(a, b, t), linear(b, c, t), t); - } - - public static float quadraticAngle(float a, float b, float c, float t){ - return linearAngle(linearAngle(a, b, t), linearAngle(b, c, t), t); - } - - public static float cubic(float a, float b, float c, float d, float t){ - return linear(quadratic(a, b, c, t), quadratic(b, c, d, t), t); - } - - public static float cubicAngle(float a, float b, float c, float d, float t){ - return linearAngle(quadraticAngle(a, b, c, t), quadraticAngle(b, c, d, t), t); - } - - public static float quartic(float a, float b, float c, float d, float e, float t){ - return linear(cubic(a, b, c, d, t), cubic(b, c, d, e, t), t); - } - - public static float quarticAngle(float a, float b, float c, float d, float e, float t){ - return linearAngle(cubicAngle(a, b, c, d, t), cubicAngle(b, c, d, e, t), t); - } - - public static float quintic(float a, float b, float c, float d, float e, float f, float t){ - return linear(quartic(a, b, c, d, e, t), quartic(b, c, d, e, f, t), t); - } - - public static float quinticAngle(float a, float b, float c, float d, float e, float f, float t){ - return linearAngle(quarticAngle(a, b, c, d, e, t), quarticAngle(b, c, d, e, f, t), t); - } - - public static float bezier(float t, float x1, float x2, float x3,float x4){ - return bezier0(t)*x1 + bezier1(t)*x2 + bezier2(t)*x3 + bezier3(t)*x4; - } - - private static float bezier0(float t){ - float temp = t*t; - return -temp*t + 3*temp - 3*t + 1; - } - - private static float bezier1(float t){ - float temp = t*t; - return 3*t*temp - 6*temp + 3*t; - } - - private static float bezier2(float t){ - float temp = t*t; - return -3*temp*t+3*temp; - } - - private static float bezier3(float t){ - return t*t*t; - } + public static float linear(float a, float b, float t) { + return a + (b - a) * t; + } + + public static float linearAngle(float a, float b, float t) { + return a + Calculator.angleDifference(b, a) * t; + } + + public static float quadratic(float a, float b, float c, float t) { + return linear(linear(a, b, t), linear(b, c, t), t); + } + + public static float quadraticAngle(float a, float b, float c, float t) { + return linearAngle(linearAngle(a, b, t), linearAngle(b, c, t), t); + } + + public static float cubic(float a, float b, float c, float d, float t) { + return linear(quadratic(a, b, c, t), quadratic(b, c, d, t), t); + } + + public static float cubicAngle(float a, float b, float c, float d, float t) { + return linearAngle(quadraticAngle(a, b, c, t), quadraticAngle(b, c, d, t), t); + } + + public static float quartic(float a, float b, float c, float d, float e, float t) { + return linear(cubic(a, b, c, d, t), cubic(b, c, d, e, t), t); + } + + public static float quarticAngle(float a, float b, float c, float d, float e, float t) { + return linearAngle(cubicAngle(a, b, c, d, t), cubicAngle(b, c, d, e, t), t); + } + + public static float quintic(float a, float b, float c, float d, float e, float f, float t) { + return linear(quartic(a, b, c, d, e, t), quartic(b, c, d, e, f, t), t); + } + + public static float quinticAngle( + float a, float b, float c, float d, float e, float f, float t) { + return linearAngle(quarticAngle(a, b, c, d, e, t), quarticAngle(b, c, d, e, f, t), t); + } + + public static float bezier(float t, float x1, float x2, float x3, float x4) { + return bezier0(t) * x1 + bezier1(t) * x2 + bezier2(t) * x3 + bezier3(t) * x4; + } + + private static float bezier0(float t) { + float temp = t * t; + return -temp * t + 3 * temp - 3 * t + 1; + } + + private static float bezier1(float t) { + float temp = t * t; + return 3 * t * temp - 6 * temp + 3 * t; + } + + private static float bezier2(float t) { + float temp = t * t; + return -3 * temp * t + 3 * temp; + } + + private static float bezier3(float t) { + return t * t * t; + } } diff --git a/src/main/java/com/brashmonkey/spriter/JsonReader.java b/src/main/java/com/brashmonkey/spriter/JsonReader.java index 89e1b83..228859d 100644 --- a/src/main/java/com/brashmonkey/spriter/JsonReader.java +++ b/src/main/java/com/brashmonkey/spriter/JsonReader.java @@ -1,20 +1,23 @@ package com.brashmonkey.spriter; -import org.json.*; +import org.json.JSONObject; import java.io.*; -/** Lightweight JSON parser. Uses org.json library. - * @author mrdlink */ +/** + * Lightweight JSON parser. Uses org.json library. + * + * @author mrdlink + */ public class JsonReader { - private JsonReader(){} + private JsonReader() {} - public static JSONObject parse (String json) { + public static JSONObject parse(String json) { return new JSONObject(json); } - public static JSONObject parse (InputStream input) throws IOException{ + public static JSONObject parse(InputStream input) throws IOException { StringBuilder textBuilder = new StringBuilder(); Reader reader = new BufferedReader(new InputStreamReader(input)); int c = 0; diff --git a/src/main/java/com/brashmonkey/spriter/Loader.java b/src/main/java/com/brashmonkey/spriter/Loader.java index 5ad5963..e9c022a 100644 --- a/src/main/java/com/brashmonkey/spriter/Loader.java +++ b/src/main/java/com/brashmonkey/spriter/Loader.java @@ -3,115 +3,111 @@ import java.util.HashMap; /** - * A loader is responsible for loading all resources. - * Since this library is meant to be as generic as possible, it cannot be assumed how to load a resource. Because of this this class has to be abstract. - * This class takes care of loading all resources a {@link Data} instance contains. - * To load all resources an instance relies on {@link #loadResource(FileReference)} which has to implemented with the backend specific methods. - * - * @author Trixt0r + * A loader is responsible for loading all resources. Since this library is meant to be as generic + * as possible, it cannot be assumed how to load a resource. Because of this this class has to be + * abstract. This class takes care of loading all resources a {@link Data} instance contains. To + * load all resources an instance relies on {@link #loadResource(FileReference)} which has to + * implemented with the backend specific methods. * - * @param The backend specific resource. In general such a resource is called "sprite", "texture" or "image". + * @param The backend specific resource. In general such a resource is called "sprite", + * "texture" or "image". + * @author Trixt0r */ public abstract class Loader { - - /** - * Contains all loaded resources if not {@link #isDisposed()}. - */ - protected final HashMap resources; - - /** - * The current set data containing {@link Folder}s and {@link File}s. - */ - protected Data data; - - /** - * The root path to the previous loaded Spriter SCML file. - */ - protected String root = ""; - - private boolean disposed; - - /** - * Creates a loader with the given Spriter data. - * @param data the generated Spriter data - */ - public Loader(Data data){ - this.data = data; - this.resources = new HashMap(100); - } - - /** - * Loads a resource. - * The path to the file can be resolved with {@link #root} and {@link #data}. - * I recommend using {@link Data#getFile(FileReference)}. Then the path to the resource is {@link File#name} relative to {@link #root}. - * @param ref the reference to load - * @return the loaded resource - */ - protected abstract R loadResource(FileReference ref); - - /** - * Called when all resources from {@link #data} have been loaded. - */ - protected void finishLoading(){} - - /** - * Called before all resources get loaded. - */ - protected void beginLoading(){} - - /** - * Loads all resources indicated by {@link #data}. - * @param root the root folder of the previously loaded Spriter SCML file - */ - public void load(String root){ - this.root = root; - this.beginLoading(); - for(Folder folder: data.folders){ - for(File file: folder.files){ - //if(new java.io.File(root+"/"+file.name).exists()){ - FileReference ref = new FileReference(folder.id, file.id); - this.resources.put(ref, this.loadResource(ref)); - //} - } - } - this.disposed = false; - this.finishLoading(); - } - - /** - * Loads all resources indicated by {@link #data}. - * @param file the previously loaded Spriter SCML file - */ - public void load(java.io.File file){ - this.load(file.getParent()); - } - - /** - * Returns a resource the given reference is pointing to. - * @param ref the reference pointing to a resource - * @return the resource or null if the resource is not loaded yet. - */ - public R get(FileReference ref){ - return this.resources.get(ref); - } - - /** - * Removes all loaded resources from the internal reference-resource map. - * Override this method and dispose all your resources. After that call {@link #dispose()} of the super class. - */ - public void dispose(){ - resources.clear(); - data = null; - root = ""; - disposed = true; - } - - /** - * Returns whether this loader has been disposed or not. - * @return true if this loader is disposed - */ - public boolean isDisposed(){ - return disposed; - } + /** Contains all loaded resources if not {@link #isDisposed()}. */ + protected final HashMap resources; + + /** The current set data containing {@link Folder}s and {@link File}s. */ + protected Data data; + + /** The root path to the previous loaded Spriter SCML file. */ + protected String root = ""; + + private boolean disposed; + + /** + * Creates a loader with the given Spriter data. + * + * @param data the generated Spriter data + */ + public Loader(Data data) { + this.data = data; + this.resources = new HashMap(100); + } + + /** + * Loads a resource. The path to the file can be resolved with {@link #root} and {@link #data}. + * I recommend using {@link Data#getFile(FileReference)}. Then the path to the resource is + * {@link File#name} relative to {@link #root}. + * + * @param ref the reference to load + * @return the loaded resource + */ + protected abstract R loadResource(FileReference ref); + + /** Called when all resources from {@link #data} have been loaded. */ + protected void finishLoading() {} + + /** Called before all resources get loaded. */ + protected void beginLoading() {} + + /** + * Loads all resources indicated by {@link #data}. + * + * @param root the root folder of the previously loaded Spriter SCML file + */ + public void load(String root) { + this.root = root; + this.beginLoading(); + for (Folder folder : data.folders) { + for (File file : folder.files) { + // if(new java.io.File(root+"/"+file.name).exists()){ + FileReference ref = new FileReference(folder.id, file.id); + this.resources.put(ref, this.loadResource(ref)); + // } + } + } + this.disposed = false; + this.finishLoading(); + } + + /** + * Loads all resources indicated by {@link #data}. + * + * @param file the previously loaded Spriter SCML file + */ + public void load(java.io.File file) { + this.load(file.getParent()); + } + + /** + * Returns a resource the given reference is pointing to. + * + * @param ref the reference pointing to a resource + * @return the resource or null if the resource is not loaded yet. + */ + public R get(FileReference ref) { + return this.resources.get(ref); + } + + /** + * Removes all loaded resources from the internal reference-resource map. Override this method + * and dispose all your resources. After that call {@link #dispose()} of the super class. + */ + public void dispose() { + resources.clear(); + data = null; + root = ""; + disposed = true; + } + + /** + * Returns whether this loader has been disposed or not. + * + * @return true if this loader is disposed + */ + public boolean isDisposed() { + return disposed; + } } diff --git a/src/main/java/com/brashmonkey/spriter/Mainline.java b/src/main/java/com/brashmonkey/spriter/Mainline.java index 39d46e2..a653d61 100644 --- a/src/main/java/com/brashmonkey/spriter/Mainline.java +++ b/src/main/java/com/brashmonkey/spriter/Mainline.java @@ -1,215 +1,236 @@ package com.brashmonkey.spriter; /** - * Represents a mainline in a Spriter SCML file. - * A mainline holds only keys and occurs only once in an animation. - * The mainline is responsible for telling which draw order the sprites have - * and how the objects are related to each other, i.e. which bone is the root and which objects are the children. - * @author Trixt0r + * Represents a mainline in a Spriter SCML file. A mainline holds only keys and occurs only once in + * an animation. The mainline is responsible for telling which draw order the sprites have and how + * the objects are related to each other, i.e. which bone is the root and which objects are the + * children. * + * @author Trixt0r */ public class Mainline { final Key[] keys; private int keyPointer = 0; - - public Mainline(int keys){ - this.keys = new Key[keys]; + + public Mainline(int keys) { + this.keys = new Key[keys]; } - - public String toString(){ - String toReturn = getClass().getSimpleName()+"|"; - for(Key key: keys) - toReturn += "\n"+key; - toReturn+="]"; - return toReturn; + + public String toString() { + String toReturn = getClass().getSimpleName() + "|"; + for (Key key : keys) toReturn += "\n" + key; + toReturn += "]"; + return toReturn; } - - public void addKey(Key key){ - this.keys[keyPointer++] = key; + + public void addKey(Key key) { + this.keys[keyPointer++] = key; } - + /** * Returns a {@link Key} at the given index. + * * @param index the index of the key * @return the key with the given index * @throws IndexOutOfBoundsException if index is out of range */ - public Key getKey(int index){ - return this.keys[index]; + public Key getKey(int index) { + return this.keys[index]; } - + /** * Returns a {@link Key} before the given time. + * * @param time the time a key has to be before - * @return a key which has a time value before the given one. - * The first key is returned if no key was found. + * @return a key which has a time value before the given one. The first key is returned if no + * key was found. */ - public Key getKeyBeforeTime(int time){ - Key found = this.keys[0]; - for(Key key: this.keys){ - if(key.time <= time) found = key; - else break; - } - return found; + public Key getKeyBeforeTime(float time) { + Key found = this.keys[0]; + for (Key key : this.keys) { + if (key.time <= time) found = key; + else break; + } + return found; } - + /** - * Represents a mainline key in a Spriter SCML file. - * A mainline key holds an {@link #id}, a {@link #time}, a {@link #curve} - * and lists of bone and object references which build a tree hierarchy. - * @author Trixt0r + * Represents a mainline key in a Spriter SCML file. A mainline key holds an {@link #id}, a + * {@link #time}, a {@link #curve} and lists of bone and object references which build a tree + * hierarchy. * + * @author Trixt0r */ - public static class Key{ - - public final int id, time; - final BoneRef[] boneRefs; - final ObjectRef[] objectRefs; - private int bonePointer = 0, objectPointer = 0; - public final Curve curve; - - public Key(int id, int time, Curve curve, int boneRefs, int objectRefs){ - this.id = id; - this.time = time; - this.curve = curve; - this.boneRefs = new BoneRef[boneRefs]; - this.objectRefs = new ObjectRef[objectRefs]; - } - - /** - * Adds a bone reference to this key. - * @param ref the reference to add - */ - public void addBoneRef(BoneRef ref){ - this.boneRefs[bonePointer++] = ref; - } - - /** - * Adds a object reference to this key. - * @param ref the reference to add - */ - public void addObjectRef(ObjectRef ref){ - this.objectRefs[objectPointer++] = ref; - } - - /** - * Returns a {@link BoneRef} with the given index. - * @param index the index of the bone reference - * @return the bone reference or null if no reference exists with the given index - */ - public BoneRef getBoneRef(int index){ - if(index < 0 || index >= this.boneRefs.length) return null; - else return this.boneRefs[index]; - } - - /** - * Returns a {@link ObjectRef} with the given index. - * @param index the index of the object reference - * @return the object reference or null if no reference exists with the given index - */ - public ObjectRef getObjectRef(int index){ - if(index < 0 || index >= this.objectRefs.length) return null; - else return this.objectRefs[index]; - } - - /** - * Returns a {@link BoneRef} for the given reference. - * @param ref the reference to the reference in this key - * @return a bone reference with the same time line as the given one - */ - public BoneRef getBoneRef(BoneRef ref){ - return getBoneRefTimeline(ref.timeline); + public static class Key { + + public final int id, time; + public final Curve curve; + final BoneRef[] boneRefs; + final ObjectRef[] objectRefs; + private int bonePointer = 0, objectPointer = 0; + + public Key(int id, int time, Curve curve, int boneRefs, int objectRefs) { + this.id = id; + this.time = time; + this.curve = curve; + this.boneRefs = new BoneRef[boneRefs]; + this.objectRefs = new ObjectRef[objectRefs]; + } + + /** + * Adds a bone reference to this key. + * + * @param ref the reference to add + */ + public void addBoneRef(BoneRef ref) { + this.boneRefs[bonePointer++] = ref; + } + + /** + * Adds a object reference to this key. + * + * @param ref the reference to add + */ + public void addObjectRef(ObjectRef ref) { + this.objectRefs[objectPointer++] = ref; + } + + /** + * Returns a {@link BoneRef} with the given index. + * + * @param index the index of the bone reference + * @return the bone reference or null if no reference exists with the given index + */ + public BoneRef getBoneRef(int index) { + if (index < 0 || index >= this.boneRefs.length) return null; + else return this.boneRefs[index]; } - + + /** + * Returns a {@link ObjectRef} with the given index. + * + * @param index the index of the object reference + * @return the object reference or null if no reference exists with the given index + */ + public ObjectRef getObjectRef(int index) { + if (index < 0 || index >= this.objectRefs.length) return null; + else return this.objectRefs[index]; + } + + /** + * Returns a {@link BoneRef} for the given reference. + * + * @param ref the reference to the reference in this key + * @return a bone reference with the same time line as the given one + */ + public BoneRef getBoneRef(BoneRef ref) { + return getBoneRefTimeline(ref.timeline); + } + /** - * Returns a {@link BoneRef} with the given time line index. + * Returns a {@link BoneRef} with the given time line index. + * * @param timeline the time line index - * @return the bone reference with the given time line index or null if no reference exists with the given time line index + * @return the bone reference with the given time line index or null if no reference exists + * with the given time line index */ - public BoneRef getBoneRefTimeline(int timeline){ - for(BoneRef boneRef: this.boneRefs) - if(boneRef.timeline == timeline) return boneRef; - return null; + public BoneRef getBoneRefTimeline(int timeline) { + for (BoneRef boneRef : this.boneRefs) if (boneRef.timeline == timeline) return boneRef; + return null; } - + /** - * Returns an {@link ObjectRef} for the given reference. - * @param ref the reference to the reference in this key - * @return an object reference with the same time line as the given one - */ - public ObjectRef getObjectRef(ObjectRef ref){ - return getObjectRefTimeline(ref.timeline); + * Returns an {@link ObjectRef} for the given reference. + * + * @param ref the reference to the reference in this key + * @return an object reference with the same time line as the given one + */ + public ObjectRef getObjectRef(ObjectRef ref) { + return getObjectRefTimeline(ref.timeline); } - + /** - * Returns a {@link ObjectRef} with the given time line index. + * Returns a {@link ObjectRef} with the given time line index. + * * @param timeline the time line index - * @return the object reference with the given time line index or null if no reference exists with the given time line index + * @return the object reference with the given time line index or null if no reference + * exists with the given time line index */ - public ObjectRef getObjectRefTimeline(int timeline){ - for(ObjectRef objRef: this.objectRefs) - if(objRef.timeline == timeline) return objRef; - return null; + public ObjectRef getObjectRefTimeline(int timeline) { + for (ObjectRef objRef : this.objectRefs) if (objRef.timeline == timeline) return objRef; + return null; } - - public String toString(){ - String toReturn = getClass().getSimpleName()+"|[id:"+id+", time: "+time+", curve: ["+curve+"]"; - for(BoneRef ref: boneRefs) - toReturn += "\n"+ref; - for(ObjectRef ref: objectRefs) - toReturn += "\n"+ref; - toReturn+="]"; - return toReturn; + + public String toString() { + String toReturn = + getClass().getSimpleName() + + "|[id:" + + id + + ", time: " + + time + + ", curve: [" + + curve + + "]"; + for (BoneRef ref : boneRefs) toReturn += "\n" + ref; + for (ObjectRef ref : objectRefs) toReturn += "\n" + ref; + toReturn += "]"; + return toReturn; } - - /** - * Represents a bone reference in a Spriter SCML file. - * A bone reference holds an {@link #id}, a {@link #timeline} and a {@link #key}. - * A bone reference may have a parent reference. - * @author Trixt0r - * - */ - public static class BoneRef{ - public final int id, key, timeline; - public final BoneRef parent; - - public BoneRef(int id, int timeline, int key, BoneRef parent){ - this.id = id; - this.timeline = timeline; - this.key = key; - this.parent = parent; - } - - public String toString(){ - int parentId = (parent != null) ? parent.id:-1; - return getClass().getSimpleName()+"|id: "+id+", parent:"+parentId+", timeline: "+timeline+", key: "+key; - } - } - - /** - * Represents an object reference in a Spriter SCML file. - * An object reference extends a {@link BoneRef} with a {@link #zIndex}, - * which indicates when the object has to be drawn. - * @author Trixt0r - * - */ - public static class ObjectRef extends BoneRef implements Comparable{ - public final int zIndex; - - public ObjectRef(int id, int timeline, int key, BoneRef parent, int zIndex){ - super(id, timeline, key, parent); - this.zIndex = zIndex; - } - - public String toString(){ - return super.toString()+", z_index: "+zIndex; - } - - public int compareTo(ObjectRef o) { - return (int)Math.signum(zIndex-o.zIndex); - } - } - } + /** + * Represents a bone reference in a Spriter SCML file. A bone reference holds an {@link + * #id}, a {@link #timeline} and a {@link #key}. A bone reference may have a parent + * reference. + * + * @author Trixt0r + */ + public static class BoneRef { + public final int id, key, timeline; + public final BoneRef parent; + + public BoneRef(int id, int timeline, int key, BoneRef parent) { + this.id = id; + this.timeline = timeline; + this.key = key; + this.parent = parent; + } + + public String toString() { + int parentId = (parent != null) ? parent.id : -1; + return getClass().getSimpleName() + + "|id: " + + id + + ", parent:" + + parentId + + ", timeline: " + + timeline + + ", key: " + + key; + } + } + + /** + * Represents an object reference in a Spriter SCML file. An object reference extends a + * {@link BoneRef} with a {@link #zIndex}, which indicates when the object has to be drawn. + * + * @author Trixt0r + */ + public static class ObjectRef extends BoneRef implements Comparable { + public final int zIndex; + + public ObjectRef(int id, int timeline, int key, BoneRef parent, int zIndex) { + super(id, timeline, key, parent); + this.zIndex = zIndex; + } + + public String toString() { + return super.toString() + ", z_index: " + zIndex; + } + + public int compareTo(ObjectRef o) { + return (int) Math.signum(zIndex - o.zIndex); + } + } + } } diff --git a/src/main/java/com/brashmonkey/spriter/Meta.java b/src/main/java/com/brashmonkey/spriter/Meta.java index b375256..1a57d37 100644 --- a/src/main/java/com/brashmonkey/spriter/Meta.java +++ b/src/main/java/com/brashmonkey/spriter/Meta.java @@ -1,81 +1,77 @@ package com.brashmonkey.spriter; public class Meta { - - Var[] vars; - - static class Var { - int id; - String name; - Value def; - Key[] keys; - - static class Key { - int id; - long time; - Value value; - - public Value getValue() { - return value; - } - - public Class getType() { - return this.value.getClass(); - } - - public int getId() { - return this.id; - } - - public long getTime() { - return this.time; - } - } - - static class Value { - Object value; - - public int getInt() { - return (Integer)value; - } - - public long getLong() { - return (Long)value; - } - - public String getString() { - return (String)value; - } - } - - public Key get(long time) { - for (Key key: this.keys) - if (key.time == time) - return key; - return null; - } - - public boolean has(long time) { - return this.get(time) != null; - } - - public String getName() { - return this.name; - } - - public int getId() { - return this.id; - } - - public Value getDefault() { - return this.def; - } - } - - public Var getVar(long time) { - for (Var var: this.vars) - if (var.get(time) != null) - return var; - return null; - } + + Var[] vars; + + public Var getVar(long time) { + for (Var var : this.vars) if (var.get(time) != null) return var; + return null; + } + + static class Var { + int id; + String name; + Value def; + Key[] keys; + + public Key get(long time) { + for (Key key : this.keys) if (key.time == time) return key; + return null; + } + + public boolean has(long time) { + return this.get(time) != null; + } + + public String getName() { + return this.name; + } + + public int getId() { + return this.id; + } + + public Value getDefault() { + return this.def; + } + + static class Key { + int id; + long time; + Value value; + + public Value getValue() { + return value; + } + + public Class getType() { + return this.value.getClass(); + } + + public int getId() { + return this.id; + } + + public long getTime() { + return this.time; + } + } + + static class Value { + Object value; + + public int getInt() { + return (Integer) value; + } + + public long getLong() { + return (Long) value; + } + + public String getString() { + return (String) value; + } + } + } } diff --git a/src/main/java/com/brashmonkey/spriter/Player.java b/src/main/java/com/brashmonkey/spriter/Player.java index 1b54899..ac9c03d 100644 --- a/src/main/java/com/brashmonkey/spriter/Player.java +++ b/src/main/java/com/brashmonkey/spriter/Player.java @@ -1,10 +1,5 @@ package com.brashmonkey.spriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; - import com.brashmonkey.spriter.Entity.CharacterMap; import com.brashmonkey.spriter.Entity.ObjectInfo; import com.brashmonkey.spriter.Mainline.Key.BoneRef; @@ -12,1126 +7,1293 @@ import com.brashmonkey.spriter.Timeline.Key.Bone; import com.brashmonkey.spriter.Timeline.Key.Object; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + /** - * A Player instance is responsible for updating an {@link Animation} properly. - * With the {@link #update()} method an instance of this class will increase its current time - * and update the current set animation ({@link #setAnimation(Animation)}). - * A Player can be positioned with {@link #setPivot(float, float)}, scaled with {@link #setScale(float)}, - * flipped with {@link #flip(boolean, boolean)} and rotated {@link #setAngle(float)}. - * A Player has various methods for runtime object manipulation such as {@link #setBone(String, Bone)} or {@link #setObject(String, Bone)}. - * Events like the ending of an animation can be observed with the {@link PlayerListener} interface. - * Character maps can be changed on the fly, just by assigning a character maps to {@link #characterMaps}, setting it to null will remove the current character map. - * - * @author Trixt0r + * A player instance is responsible for updating an {@link Animation} properly. With the {@link + * #update(float)} method an instance of this class will increase its current time and update the + * current set animation ({@link #setAnimation(Animation)}). A player can be positioned with {@link + * #setPivot(float, float)}, scaled with {@link #setScale(float)}, flipped with {@link + * #flip(boolean, boolean)} and rotated {@link #setAngle(float)}. A player has various methods for + * runtime object manipulation such as {@link #setBone(String, Bone)} or {link #setObject(String, + * Bone)}. Events like the ending of an animation can be observed with the {@link PlayerListener} + * interface. Character maps can be changed on the fly, just by assigning a character maps to {@link + * #characterMaps}, setting it to null will remove the current character map. * + * @author Trixt0r */ public class Player { - - protected Entity entity; - Animation animation; - int time; - public int speed; - Timeline.Key[] tweenedKeys, unmappedTweenedKeys; - private Timeline.Key[] tempTweenedKeys, tempUnmappedTweenedKeys; - private List listeners; - public final List attachments = new ArrayList(); - Timeline.Key.Bone root = new Timeline.Key.Bone(new Point(0,0)); - private final Point position = new Point(0,0), pivot = new Point(0,0); - private final HashMap objToTimeline = new HashMap(); - private float angle; - private boolean dirty = true; - public CharacterMap[] characterMaps; - private Rectangle rect; - public final Box prevBBox; - private BoneIterator boneIterator; - private ObjectIterator objectIterator; - private Mainline.Key currentKey, prevKey; - public boolean copyObjects = true; - - /** - * Creates a {@link Player} instance with the given entity. - * @param entity the entity this player will animate - */ - public Player(Entity entity){ - this.boneIterator = new BoneIterator(); - this.objectIterator = new ObjectIterator(); - this.speed = 15; - this.rect = new Rectangle(0,0,0,0); - this.prevBBox = new Box(); - this.listeners = new ArrayList(); - this.setEntity(entity); - } - - /** - * Updates this player. - * This means the current time gets increased by {@link #speed} and is applied to the current animation. - */ - public void update(){ - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).preProcess(this); - } - if(dirty) this.updateRoot(); - this.animation.update(time, root); - this.currentKey = this.animation.currentKey; - if(prevKey != currentKey){ - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).mainlineKeyChanged(prevKey, currentKey); - } - prevKey = currentKey; - } - if(copyObjects){ - tweenedKeys = tempTweenedKeys; - unmappedTweenedKeys = tempUnmappedTweenedKeys; - this.copyObjects(); - } - else{ - tweenedKeys = animation.tweenedKeys; - unmappedTweenedKeys = animation.unmappedTweenedKeys; - } - - for (int i = 0; i < attachments.size(); i++) { - attachments.get(i).update(); - } - - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).postProcess(this); - } - this.increaseTime(); - } - - private void copyObjects(){ - for(int i = 0; i < animation.tweenedKeys.length; i++){ - this.tweenedKeys[i].active = animation.tweenedKeys[i].active; - this.unmappedTweenedKeys[i].active = animation.unmappedTweenedKeys[i].active; - this.tweenedKeys[i].object().set(animation.tweenedKeys[i].object()); - this.unmappedTweenedKeys[i].object().set(animation.unmappedTweenedKeys[i].object()); - } - } - - private void increaseTime(){ - time += speed; - if(time > animation.length){ - time = time-animation.length; - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).animationFinished(animation); - } - } - if(time < 0){ - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).animationFinished(animation); - } - time += animation.length; - } - } - - private void updateRoot(){ - this.root.angle = angle; - this.root.position.set(pivot); - this.root.position.rotate(angle); - this.root.position.translate(position); - dirty = false; - } - - /** - * Returns a time line bone at the given index. - * @param index the index of the bone - * @return the bone with the given index. - */ - public Bone getBone(int index){ - return this.unmappedTweenedKeys[getCurrentKey().getBoneRef(index).timeline].object(); - } - - /** - * Returns a time line object at the given index. - * @param index the index of the object - * @return the object with the given index. - */ - public Object getObject(int index){ - return (Object) this.unmappedTweenedKeys[getCurrentKey().getObjectRef(index).timeline].object(); - } - - /** - * Returns the index of a time line bone with the given name. - * @param name the name of the bone - * @return the index of the bone or -1 if no bone exists with the given name - */ - public int getBoneIndex(String name){ - for(BoneRef ref: getCurrentKey().boneRefs) - if(animation.getTimeline(ref.timeline).name.equals(name)) - return ref.id; - return -1; - } - - /** - * Returns a time line bone with the given name. - * @param name the name of the bone - * @return the bone with the given name - * @throws ArrayIndexOutOfBoundsException if no bone exists with the given name - * @throws NullPointerException if no bone exists with the given name - */ - public Bone getBone(String name){ - return this.unmappedTweenedKeys[animation.getTimeline(name).id].object(); - } - - /** - * Returns a bone reference for the given time line bone. - * @param bone the time line bone - * @return the bone reference for the given bone - * @throws NullPointerException if no reference for the given bone was found - */ - public BoneRef getBoneRef(Bone bone){ - return this.getCurrentKey().getBoneRefTimeline(this.objToTimeline.get(bone).id); - } - - /** - * Returns the index of a time line object with the given name. - * @param name the name of the object - * @return the index of the object or -1 if no object exists with the given name - */ - public int getObjectIndex(String name){ - for(ObjectRef ref: getCurrentKey().objectRefs) - if(animation.getTimeline(ref.timeline).name.equals(name)) - return ref.id; - return -1; - } - - /** - * Returns a time line object with the given name. - * @param name the name of the object - * @return the object with the given name - * @throws ArrayIndexOutOfBoundsException if no object exists with the given name - * @throws NullPointerException if no object exists with the given name - */ - public Object getObject(String name){ - return (Object)this.unmappedTweenedKeys[animation.getTimeline(name).id].object(); - } - - /** - * Returns a object reference for the given time line bone. - * @param object the time line object - * @return the object reference for the given bone - * @throws NullPointerException if no reference for the given object was found - */ - public ObjectRef getObjectRef(Object object){ - return this.getCurrentKey().getObjectRefTimeline(this.objToTimeline.get(object).id); - } - - /** - * Returns the name for the given bone or object. - * @param boneOrObject the bone or object - * @return the name of the bone or object - * @throws NullPointerException if no name for the given bone or bject was found - */ - public String getNameFor(Bone boneOrObject){ - return this.animation.getTimeline(objToTimeline.get(boneOrObject).id).name; - } - - /** - * Returns the object info for the given bone or object. - * @param boneOrObject the bone or object - * @return the object info of the bone or object - * @throws NullPointerException if no object info for the given bone or bject was found - */ - public ObjectInfo getObjectInfoFor(Bone boneOrObject){ - return this.animation.getTimeline(objToTimeline.get(boneOrObject).id).objectInfo; - } - - /** - * Returns the time line key for the given bone or object - * @param boneOrObject the bone or object - * @return the time line key of the bone or object, or null if no time line key was found - */ - public Timeline.Key getKeyFor(Bone boneOrObject){ - return objToTimeline.get(boneOrObject); - } - - /** - * Calculates and returns a {@link Box} for the given bone or object. - * @param boneOrObject the bone or object to calculate the bounding box for - * @return the box for the given bone or object - * @throws NullPointerException if no object info for the given bone or object exists - */ - public Box getBox(Bone boneOrObject){ - ObjectInfo info = getObjectInfoFor(boneOrObject); - this.prevBBox.calcFor(boneOrObject, info); - return this.prevBBox; - } - - /** - * Returns whether the given point at x,y lies inside the box of the given bone or object. - * @param boneOrObject the bone or object - * @param x the x value of the point - * @param y the y value of the point - * @return true if x,y lies inside the box of the given bone or object - * @throws NullPointerException if no object info for the given bone or object exists - */ - public boolean collidesFor(Bone boneOrObject, float x, float y){ - ObjectInfo info = getObjectInfoFor(boneOrObject); - this.prevBBox.calcFor(boneOrObject, info); - return this.prevBBox.collides(boneOrObject, info, x, y); - } - - /** - * Returns whether the given point lies inside the box of the given bone or object. - * @param bone the bone or object - * @param point the point - * @return true if the point lies inside the box of the given bone or object - * @throws NullPointerException if no object info for the given bone or object exists - */ - public boolean collidesFor(Bone boneOrObject, Point point){ - return this.collidesFor(boneOrObject, point.x, point.y); - } - - /** - * Returns whether the given area collides with the box of the given bone or object. - * @param boneOrObject the bone or object - * @param area the rectangular area - * @return true if the area collides with the bone or object - */ - public boolean collidesFor(Bone boneOrObject, Rectangle area){ - ObjectInfo info = getObjectInfoFor(boneOrObject); - this.prevBBox.calcFor(boneOrObject, info); - return this.prevBBox.isInside(area); - } - - /** - * Sets the given values of the bone with the given name. - * @param name the name of the bone - * @param x the new x value of the bone - * @param y the new y value of the bone - * @param angle the new angle of the bone - * @param scaleX the new scale in x direction of the bone - * @param scaleY the new scale in y direction of the bone - * @throws SpriterException if no bone exists of the given name - */ - public void setBone(String name, float x, float y, float angle, float scaleX, float scaleY){ - int index = getBoneIndex(name); - if(index == -1) throw new SpriterException("No bone found of name \""+name+"\""); - BoneRef ref = getCurrentKey().getBoneRef(index); - Bone bone = getBone(index); - bone.set(x, y, angle, scaleX, scaleY, 0f, 0f); - unmapObjects(ref); - } - - /** - * Sets the given values of the bone with the given name. - * @param name the name of the bone - * @param position the new position of the bone - * @param angle the new angle of the bone - * @param scale the new scale of the bone - * @throws SpriterException if no bone exists of the given name - */ - public void setBone(String name, Point position, float angle, Point scale){ - this.setBone(name, position.x, position.y, angle, scale.x, scale.y); - } - - /** - * Sets the given values of the bone with the given name. - * @param name the name of the bone - * @param x the new x value of the bone - * @param y the new y value of the bone - * @param angle the new angle of the bone - * @throws SpriterException if no bone exists of the given name - */ - public void setBone(String name, float x, float y, float angle){ - Bone b = getBone(name); - setBone(name, x, y, angle, b.scale.x, b.scale.y); - } - - /** - * Sets the given values of the bone with the given name. - * @param name the name of the bone - * @param position the new position of the bone - * @param angle the new angle of the bone - * @throws SpriterException if no bone exists of the given name - */ - public void setBone(String name, Point position, float angle){ - Bone b = getBone(name); - setBone(name, position.x, position.y, angle, b.scale.x, b.scale.y); - } - - /** - * Sets the position of the bone with the given name. - * @param name the name of the bone - * @param x the new x value of the bone - * @param y the new y value of the bone - * @throws SpriterException if no bone exists of the given name - */ - public void setBone(String name, float x, float y){ - Bone b = getBone(name); - setBone(name, x, y, b.angle); - } - - /** - * Sets the position of the bone with the given name. - * @param name the name of the bone - * @param position the new position of the bone - * @throws SpriterException if no bone exists of the given name - */ - public void setBone(String name, Point position){ - setBone(name, position.x, position.y); - } - - /** - * Sets the angle of the bone with the given name - * @param name the name of the bone - * @param angle the new angle of the bone - * @throws SpriterException if no bone exists of the given name - */ - public void setBone(String name, float angle){ - Bone b = getBone(name); - setBone(name, b.position.x, b.position.y, angle); - } - - /** - * Sets the values of the bone with the given name to the values of the given bone - * @param name the name of the bone - * @param bone the bone with the new values - * @throws SpriterException if no bone exists of the given name - */ - public void setBone(String name, Bone bone){ - setBone(name, bone.position, bone.angle, bone.scale); - } - - /** - * Sets the given values of the object with the given name. - * @param name the name of the object - * @param x the new position in x direction of the object - * @param y the new position in y direction of the object - * @param angle the new angle of the object - * @param scaleX the new scale in x direction of the object - * @param scaleY the new scale in y direction of the object - * @param pivotX the new pivot in x direction of the object - * @param pivotY the new pivot in y direction of the object - * @param alpha the new alpha value of the object - * @param folder the new folder index of the object - * @param file the new file index of the object - * @throws SpriterException if no object exists of the given name - */ - public void setObject(String name, float x, float y, float angle, float scaleX, float scaleY, float pivotX, float pivotY, float alpha, int folder, int file){ - int index = getObjectIndex(name); - if(index == -1) throw new SpriterException("No object found for name \""+name+"\""); - ObjectRef ref = getCurrentKey().getObjectRef(index); - Object object = getObject(index); - object.set(x, y, angle, scaleX, scaleY, pivotX, pivotY, alpha, folder, file); - unmapObjects(ref); - } - - /** - * Sets the given values of the object with the given name. - * @param name the name of the object - * @param position the new position of the object - * @param angle the new angle of the object - * @param scale the new scale of the object - * @param pivot the new pivot of the object - * @param alpha the new alpha value of the object - * @param ref the new file reference of the object - * @throws SpriterException if no object exists of the given name - */ - public void setObject(String name, Point position, float angle, Point scale, Point pivot, float alpha, FileReference ref){ - this.setObject(name, position.x, position.y, angle, scale.x, scale.y, pivot.x, pivot.y, alpha, ref.folder, ref.file); - } - - /** - * Sets the given values of the object with the given name. - * @param name the name of the object - * @param x the new position in x direction of the object - * @param y the new position in y direction of the object - * @param angle the new angle of the object - * @param scaleX the new scale in x direction of the object - * @param scaleY the new scale in y direction of the object - * @throws SpriterException if no object exists of the given name - */ - public void setObject(String name, float x, float y, float angle, float scaleX, float scaleY){ - Object b = getObject(name); - setObject(name, x, y, angle, scaleX, scaleY, b.pivot.x, b.pivot.y, b.alpha, b.ref.folder, b.ref.file); - } - - /** - * Sets the given values of the object with the given name. - * @param name the name of the object - * @param x the new position in x direction of the object - * @param y the new position in y direction of the object - * @param angle the new angle of the object - * @throws SpriterException if no object exists of the given name - */ - public void setObject(String name, float x, float y, float angle){ - Object b = getObject(name); - setObject(name, x, y, angle, b.scale.x, b.scale.y); - } - - /** - * Sets the given values of the object with the given name. - * @param name the name of the object - * @param position the new position of the object - * @param angle the new angle of the object - * @throws SpriterException if no object exists of the given name - */ - public void setObject(String name, Point position, float angle){ - Object b = getObject(name); - setObject(name, position.x, position.y, angle, b.scale.x, b.scale.y); - } - - /** - * Sets the position of the object with the given name. - * @param name the name of the object - * @param x the new position in x direction of the object - * @param y the new position in y direction of the object - * @throws SpriterException if no object exists of the given name - */ - public void setObject(String name, float x, float y){ - Object b = getObject(name); - setObject(name, x, y, b.angle); - } - - /** - * Sets the position of the object with the given name. - * @param name the name of the object - * @param position the new position of the object - * @throws SpriterException if no object exists of the given name - */ - public void setObject(String name, Point position){ - setObject(name, position.x, position.y); - } - - /** - * Sets the position of the object with the given name. - * @param name the name of the object - * @param angle the new angle of the object - * @throws SpriterException if no object exists of the given name - */ - public void setObject(String name, float angle){ - Object b = getObject(name); - setObject(name, b.position.x, b.position.y, angle); - } - - /** - * Sets the position of the object with the given name. - * @param name the name of the object - * @param alpha the new alpha value of the object - * @param folder the new folder index of the object - * @param file the new file index of the object - * @throws SpriterException if no object exists of the given name - */ - public void setObject(String name, float alpha, int folder, int file){ - Object b = getObject(name); - setObject(name, b.position.x, b.position.y, b.angle, b.scale.x, b.scale.y, b.pivot.x, b.pivot.y, alpha, folder, file); - } - - /** - * Sets the values of the object with the given name to the values of the given object. - * @param name the name of the object - * @param object the object with the new values - * @throws SpriterException if no object exists of the given name - */ - public void setObject(String name, Object object){ - setObject(name, object.position, object.angle, object.scale, object.pivot, object.alpha, object.ref); - } - - /** - * Maps all object from the parent's coordinate system to the global coordinate system. - * @param base the root bone to start at. Set it to null to traverse the whole bone hierarchy. - */ - public void unmapObjects(BoneRef base){ - int start = base == null ? -1 : base.id-1; - for(int i = start+1; i < getCurrentKey().boneRefs.length; i++){ - BoneRef ref = getCurrentKey().getBoneRef(i); - if(ref.parent != base && base != null) continue; - Bone parent = ref.parent == null ? this.root : this.unmappedTweenedKeys[ref.parent.timeline].object(); - unmappedTweenedKeys[ref.timeline].object().set(tweenedKeys[ref.timeline].object()); - unmappedTweenedKeys[ref.timeline].object().unmap(parent); - unmapObjects(ref); - } - for(ObjectRef ref: getCurrentKey().objectRefs){ - if(ref.parent != base && base != null) continue; - Bone parent = ref.parent == null ? this.root : this.unmappedTweenedKeys[ref.parent.timeline].object(); - unmappedTweenedKeys[ref.timeline].object().set(tweenedKeys[ref.timeline].object()); - unmappedTweenedKeys[ref.timeline].object().unmap(parent); - } - } - - /** - * Sets the entity for this player instance. - * The animation will be switched to the first one of the new entity. - * @param entity the new entity - * @throws SpriterException if the entity is null - */ - public void setEntity(Entity entity){ - if(entity == null) throw new SpriterException("entity can not be null!"); - this.entity = entity; - int maxAnims = entity.getAnimationWithMostTimelines().timelines(); - tweenedKeys = new Timeline.Key[maxAnims]; - unmappedTweenedKeys = new Timeline.Key[maxAnims]; - for(int i = 0; i < maxAnims; i++){ - Timeline.Key key = new Timeline.Key(i); - Timeline.Key keyU = new Timeline.Key(i); - key.setObject(new Timeline.Key.Object(new Point(0,0))); - keyU.setObject(new Timeline.Key.Object(new Point(0,0))); - tweenedKeys[i] = key; - unmappedTweenedKeys[i] = keyU; - this.objToTimeline.put(keyU.object(), keyU); - } - this.tempTweenedKeys = tweenedKeys; - this.tempUnmappedTweenedKeys = unmappedTweenedKeys; - this.setAnimation(entity.getAnimation(0)); - } - - /** - * Returns the current set entity. - * @return the current entity - */ - public Entity getEntity(){ - return this.entity; - } - - /** - * Sets the animation of this player. - * @param animation the new animation - * @throws SpriterException if the animation is null or the current animation is not a member of the current set entity - */ - public void setAnimation(Animation animation){ - Animation prevAnim = this.animation; - if(animation == this.animation) return; - if(animation == null) throw new SpriterException("animation can not be null!"); - if(!this.entity.containsAnimation(animation) && animation.id != -1) throw new SpriterException("animation has to be in the same entity as the current set one!"); - if(animation != this.animation) time = 0; - this.animation = animation; - int tempTime = this.time; - this.time = 0; - this.update(); - this.time = tempTime; - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).animationChanged(prevAnim, animation); - } - } - - /** - * Sets the animation of this player to the one with the given name. - * @param name the name of the animation - * @throws SpriterException if no animation exists with the given name - */ - public void setAnimation(String name){ - this.setAnimation(entity.getAnimation(name)); - } - - /** - * Sets the animation of this player to the one with the given index. - * @param index the index of the animation - * @throws IndexOutOfBoundsException if the index is out of range - */ - public void setAnimation(int index){ - this.setAnimation(entity.getAnimation(index)); - } - - /** - * Returns the current set animation. - * @return the current animation - */ - public Animation getAnimation(){ - return this.animation; - } - - /** - * Returns a bounding box for this player. - * The bounding box is calculated for all bones and object starting from the given root. - * @param root the starting root. Set it to null to calculate the bounding box for the whole player - * @return the bounding box - */ - public Rectangle getBoundingRectangle(BoneRef root){ - Bone boneRoot = root == null ? this.root : this.unmappedTweenedKeys[root.timeline].object(); - this.rect.set(boneRoot.position.x, boneRoot.position.y, boneRoot.position.x, boneRoot.position.y); - this.calcBoundingRectangle(root); - this.rect.calculateSize(); - return this.rect; - } - - /** - * Returns a bounding box for this player. - * The bounding box is calculated for all bones and object starting from the given root. - * @param root the starting root. Set it to null to calculate the bounding box for the whole player - * @return the bounding box - */ - public Rectangle getBoudingRectangle(Bone root){ - return this.getBoundingRectangle(root == null ? null: getBoneRef(root)); - } - - private void calcBoundingRectangle(BoneRef root){ - for(BoneRef ref: getCurrentKey().boneRefs){ - if(ref.parent != root && root != null) continue; - Bone bone = this.unmappedTweenedKeys[ref.timeline].object(); - this.prevBBox.calcFor(bone, animation.getTimeline(ref.timeline).objectInfo); - Rectangle.setBiggerRectangle(rect, this.prevBBox.getBoundingRect(), rect); - this.calcBoundingRectangle(ref); - } - for(ObjectRef ref: getCurrentKey().objectRefs){ - if(ref.parent != root) continue; - Bone bone = this.unmappedTweenedKeys[ref.timeline].object(); - this.prevBBox.calcFor(bone, animation.getTimeline(ref.timeline).objectInfo); - Rectangle.setBiggerRectangle(rect, this.prevBBox.getBoundingRect(), rect); - } - } - - /** - * Returns the current main line key based on the current {@link #time}. - * @return the current main line key - */ - public Mainline.Key getCurrentKey(){ - return this.currentKey; - } - - /** - * Returns the current time. - * The player will make sure that the current time is always between 0 and {@link Animation#length}. - * @return the current time - */ - public int getTime() { - return time; - } - - /** - * Sets the time for the current time. - * The player will make sure that the new time will not exceed the time bounds of the current animation. - * @param time the new time - * @return this player to enable chained operations - */ - public Player setTime(int time){ - this.time = time; - int prevSpeed = this.speed; - this.speed = 0; - this.increaseTime(); - this.speed = prevSpeed; - return this; - } - - /** - * Sets the scale of this player to the given one. - * Only uniform scaling is supported. - * @param scale the new scale. 1f means 100% scale. - * @return this player to enable chained operations - */ - public Player setScale(float scale){ - this.root.scale.set(scale*flippedX(), scale*flippedY()); - return this; - } - - /** - * Scales this player based on the current set scale. - * @param scale the scaling factor. 1f means no scale. - * @return this player to enable chained operations - */ - public Player scale(float scale){ - this.root.scale.scale(scale, scale); - return this; - } - - /** - * Returns the current scale. - * @return the current scale - */ - public float getScale(){ - return root.scale.x; - } - - /** - * Flips this player around the x and y axis. - * @param x whether to flip the player around the x axis - * @param y whether to flip the player around the y axis - * @return this player to enable chained operations - */ - public Player flip(boolean x, boolean y){ - if(x) this.flipX(); - if(y) this.flipY(); - return this; - } - - /** - * Flips the player around the x axis. - * @return this player to enable chained operations - */ - public Player flipX(){ - this.root.scale.x *= -1; - return this; - } - - /** - * Flips the player around the y axis. - * @return this player to enable chained operations - */ - public Player flipY(){ - this.root.scale.y *= -1; - return this; - } - - /** - * Returns whether this player is flipped around the x axis. - * @return 1 if this player is not flipped, -1 if it is flipped - */ - public int flippedX(){ - return (int) Math.signum(root.scale.x); - } - - /** - * Returns whether this player is flipped around the y axis. - * @return 1 if this player is not flipped, -1 if it is flipped - */ - public int flippedY(){ - return (int) Math.signum(root.scale.y); - } - - /** - * Sets the position of this player to the given coordinates. - * @param x the new position in x direction - * @param y the new position in y direction - * @return this player to enable chained operations - */ - public Player setPosition(float x, float y){ - this.dirty = true; - this.position.set(x,y); - return this; - } - - /** - * Sets the position of the player to the given one. - * @param position the new position - * @return this player to enable chained operations - */ - public Player setPosition(Point position){ - return this.setPosition(position.x, position.y); - } - - /** - * Adds the given coordinates to the current position of this player. - * @param x the amount in x direction - * @param y the amount in y direction - * @return this player to enable chained operations - */ - public Player translatePosition(float x, float y){ - return this.setPosition(position.x+x, position.y+y); - } - - /** - * Adds the given amount to the current position of this player. - * @param amount the amount to add - * @return this player to enable chained operations - */ - public Player translate(Point amount){ - return this.translatePosition(amount.x, amount.y); - } - - /** - * Returns the current position in x direction. - * @return the current position in x direction - */ - public float getX(){ - return position.x; - } - - /** - * Returns the current position in y direction. - * @return the current position in y direction - */ - public float getY(){ - return position.y; - } - - /** - * Sets the angle of this player to the given angle. - * @param angle the angle in degrees - * @return this player to enable chained operations - */ - public Player setAngle(float angle){ - this.dirty = true; - this.angle = angle; - return this; - } - - /** - * Rotates this player by the given angle. - * @param angle the angle in degrees - * @return this player to enable chained operations - */ - public Player rotate(float angle){ - return this.setAngle(angle+this.angle); - } - - /** - * Returns the current set angle. - * @return the current angle - */ - public float getAngle(){ - return this.angle; - } - - /** - * Sets the pivot, i.e. origin, of this player. - * A pivot at (0,0) means that the origin of the played animation will have the same one as in Spriter. - * @param x the new pivot in x direction - * @param y the new pivot in y direction - * @return this player to enable chained operations - */ - public Player setPivot(float x, float y){ - this.dirty = true; - this.pivot.set(x, y); - return this; - } - - /** - * Sets the pivot, i.e. origin, of this player. - * A pivot at (0,0) means that the origin of the played animation will have the same one as in Spriter. - * @param pivot the new pivot - * @return this player to enable chained operations - */ - public Player setPivot(Point pivot){ - return this.setPivot(pivot.x, pivot.y); - } - - /** - * Translates the current set pivot position by the given amount. - * @param x the amount in x direction - * @param y the amount in y direction - * @return this player to enable chained operations - */ - public Player translatePivot(float x, float y){ - return this.setPivot(pivot.x+x, pivot.y+y); - } - - /** - * Adds the given amount to the current set pivot position. - * @param amount the amount to add - * @return this player to enable chained operations - */ - public Player translatePivot(Point amount){ - return this.translatePivot(amount.x, amount.y); - } - - /** - * Returns the current set pivot in x direction. - * @return the pivot in x direction - */ - public float getPivotX(){ - return pivot.x; - } - - /** - * Returns the current set pivot in y direction. - * @return the pivot in y direction - */ - public float getPivotY(){ - return pivot.y; - } - - /** - * Appends a listener to the listeners list of this player. - * @param listener the listener to add - */ - public void addListener(PlayerListener listener){ - this.listeners.add(listener); - } - - /** - * Removes a listener from the listeners list of this player. - * @param listener the listener to remove - */ - public void removeListener(PlayerListener listener){ - this.listeners.remove(listener); - } - - /** - * Returns an iterator to iterate over all time line bones in the current animation. - * @return the bone iterator - */ - public Iterator boneIterator(){ - return this.boneIterator(this.getCurrentKey().boneRefs[0]); - } - - /** - * Returns an iterator to iterate over all time line bones in the current animation starting at a given root. - * @param start the bone reference to start at - * @return the bone iterator - */ - public Iterator boneIterator(BoneRef start){ - this.boneIterator.index = start.id; - return this.boneIterator; - } - - /** - * Returns an iterator to iterate over all time line objects in the current animation. - * @return the object iterator - */ - public Iterator objectIterator(){ - return this.objectIterator(this.getCurrentKey().objectRefs[0]); - } - - /** - * Returns an iterator to iterate over all time line objects in the current animation starting at a given root. - * @param start the object reference to start at - * @return the object iterator - */ - public Iterator objectIterator(ObjectRef start){ - this.objectIterator.index = start.id; - return this.objectIterator; - } - - /** - * An iterator to iterate over all time line objects in the current animation. - * @author Trixt0r - * - */ - class ObjectIterator implements Iterator{ - int index = 0; - - public boolean hasNext() { - return index < getCurrentKey().objectRefs.length; - } - - - public Object next() { - return unmappedTweenedKeys[getCurrentKey().objectRefs[index++].timeline].object(); - } - - - public void remove() { - throw new SpriterException("remove() is not supported by this iterator!"); - } - - } - - /** - * An iterator to iterate over all time line bones in the current animation. - * @author Trixt0r - * - */ - class BoneIterator implements Iterator{ - int index = 0; - - public boolean hasNext() { - return index < getCurrentKey().boneRefs.length; - } - - public Bone next() { - return unmappedTweenedKeys[getCurrentKey().boneRefs[index++].timeline].object(); - } - - public void remove() { - throw new SpriterException("remove() is not supported by this iterator!"); - } - } - - /** - * A listener to listen for specific events which can occur during the runtime of a {@link Player} instance. - * @author Trixt0r - * - */ - public static interface PlayerListener{ - - /** - * Gets called if the current animation has reached it's end or it's beginning (depends on the current set {@link Player#speed}). - * @param animation the animation which finished. - */ - public void animationFinished(Animation animation); - - /** - * Gets called if the animation of the player gets changed. - * If {@link Player#setAnimation(Animation)} gets called and the new animation is the same as the previous one, this method will not be called. - * @param oldAnim the old animation - * @param newAnim the new animation - */ - public void animationChanged(Animation oldAnim, Animation newAnim); - - /** - * Gets called before a player updates the current animation. - * @param player the player which is calling this method. - */ - public void preProcess(Player player); - - /** - * Gets called after a player updated the current animation. - * @param player the player which is calling this method. - */ - public void postProcess(Player player); - - /** - * Gets called if the mainline key gets changed. - * If {@link Player#speed} is big enough it can happen that mainline keys between the previous and the new mainline key will be ignored. - * @param prevKey the previous mainline key - * @param newKey the new mainline key - */ - public void mainlineKeyChanged(Mainline.Key prevKey, Mainline.Key newKey); - } - - /** - * An attachment is an abstract object which can be attached to a {@link Player} object. - * An attachment extends a {@link Bone} which means that {@link Bone#position}, {@link Bone#scale} and {@link Bone#angle} can be set to change the relative position to its {@link Attachment#parent} - * The {@link Player} object will make sure that the attachment will be transformed relative to its {@link Attachment#parent}. - * @author Trixt0r - * - */ - public static abstract class Attachment extends Timeline.Key.Bone{ - - private Bone parent; - private final Point positionTemp, scaleTemp; - private float angleTemp; - - /** - * Creates a new attachment - * @param parent the parent of this attachment - */ - public Attachment(Bone parent){ - this.positionTemp = new Point(); - this.scaleTemp = new Point(); - this.setParent(parent); - } - - /** - * Sets the parent of this attachment. - * @param parent the parent - * @throws SpriterException if parent is null - */ - public void setParent(Bone parent){ - if(parent == null) throw new SpriterException("The parent cannot be null!"); - this.parent = parent; - } - - /** - * Returns the current set parent. - * @return the parent - */ - public Bone getParent(){ - return this.parent; - } - - final void update(){ - //Save relative positions - this.positionTemp.set(super.position); - this.scaleTemp.set(super.scale); - this.angleTemp = super.angle; - - super.unmap(parent); - this.setPosition(super.position.x, super.position.y); - this.setScale(super.scale.x, super.scale.y); - this.setAngle(super.angle); - - //Load realtive positions - super.position.set(this.positionTemp); - super.scale.set(this.scaleTemp); - super.angle = this.angleTemp; - } - /** - * Sets the position to the given coordinates. - * @param x the x coordinate - * @param y the y coordinate - */ - protected abstract void setPosition(float x, float y); - /** - * Sets the scale to the given scale. - * @param xscale the scale in x direction - * @param yscale the scale in y direction - */ - protected abstract void setScale(float xscale, float yscale); - /** - * Sets the angle to the given one. - * @param angle the angle in degrees - */ - protected abstract void setAngle(float angle); - } + + public final List attachments = new ArrayList(); + public final Box prevBBox; + private final Point position = new Point(0, 0), pivot = new Point(0, 0); + private final HashMap objToTimeline = new HashMap(); + public int speed; + public CharacterMap[] characterMaps; + public boolean copyObjects = true; + protected Entity entity; + Animation animation; + float time; + Timeline.Key[] tweenedKeys, unmappedTweenedKeys; + Timeline.Key.Bone root = new Timeline.Key.Bone(new Point(0, 0)); + private Timeline.Key[] tempTweenedKeys, tempUnmappedTweenedKeys; + private List listeners; + private float angle; + private boolean dirty = true; + private Rectangle rect; + private BoneIterator boneIterator; + private ObjectIterator objectIterator; + private Mainline.Key currentKey, prevKey; + + /** + * Creates a {@link Player} instance with the given entity. + * + * @param entity the entity this player will animate + */ + public Player(Entity entity) { + this.boneIterator = new BoneIterator(); + this.objectIterator = new ObjectIterator(); + this.speed = 15; + this.rect = new Rectangle(0, 0, 0, 0); + this.prevBBox = new Box(); + this.listeners = new ArrayList(); + this.setEntity(entity); + } + + /** + * Updates this player. This means the current time gets increased by {@link #speed} and is + * applied to the current animation. + */ + public void update(float dt) { + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).preProcess(this); + } + if (dirty) this.updateRoot(); + this.animation.update(time, root); + this.currentKey = this.animation.currentKey; + if (prevKey != currentKey) { + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).mainlineKeyChanged(prevKey, currentKey); + } + prevKey = currentKey; + } + if (copyObjects) { + tweenedKeys = tempTweenedKeys; + unmappedTweenedKeys = tempUnmappedTweenedKeys; + this.copyObjects(); + } else { + tweenedKeys = animation.tweenedKeys; + unmappedTweenedKeys = animation.unmappedTweenedKeys; + } + + for (int i = 0; i < attachments.size(); i++) { + attachments.get(i).update(); + } + + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).postProcess(this); + } + this.increaseTime(dt); + } + + private void copyObjects() { + for (int i = 0; i < animation.tweenedKeys.length; i++) { + this.tweenedKeys[i].active = animation.tweenedKeys[i].active; + this.unmappedTweenedKeys[i].active = animation.unmappedTweenedKeys[i].active; + this.tweenedKeys[i].object().set(animation.tweenedKeys[i].object()); + this.unmappedTweenedKeys[i].object().set(animation.unmappedTweenedKeys[i].object()); + } + } + + private void increaseTime(float dt) { + time += dt * speed; + if (time > animation.length) { + time = time - animation.length; + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).animationFinished(animation); + } + } + if (time < 0) { + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).animationFinished(animation); + } + time += animation.length; + } + } + + private void updateRoot() { + this.root.angle = angle; + this.root.position.set(pivot); + this.root.position.rotate(angle); + this.root.position.translate(position); + dirty = false; + } + + /** + * Returns a time line bone at the given index. + * + * @param index the index of the bone + * @return the bone with the given index. + */ + public Bone getBone(int index) { + return this.unmappedTweenedKeys[getCurrentKey().getBoneRef(index).timeline].object(); + } + + /** + * Returns a time line object at the given index. + * + * @param index the index of the object + * @return the object with the given index. + */ + public Object getObject(int index) { + return this.unmappedTweenedKeys[getCurrentKey().getObjectRef(index).timeline].object(); + } + + /** + * Returns the index of a time line bone with the given name. + * + * @param name the name of the bone + * @return the index of the bone or -1 if no bone exists with the given name + */ + public int getBoneIndex(String name) { + for (BoneRef ref : getCurrentKey().boneRefs) + if (animation.getTimeline(ref.timeline).name.equals(name)) return ref.id; + return -1; + } + + /** + * Returns a time line bone with the given name. + * + * @param name the name of the bone + * @return the bone with the given name + * @throws ArrayIndexOutOfBoundsException if no bone exists with the given name + * @throws NullPointerException if no bone exists with the given name + */ + public Bone getBone(String name) { + return this.unmappedTweenedKeys[animation.getTimeline(name).id].object(); + } + + /** + * Returns a bone reference for the given time line bone. + * + * @param bone the time line bone + * @return the bone reference for the given bone + * @throws NullPointerException if no reference for the given bone was found + */ + public BoneRef getBoneRef(Bone bone) { + return this.getCurrentKey().getBoneRefTimeline(this.objToTimeline.get(bone).id); + } + + /** + * Returns the index of a time line object with the given name. + * + * @param name the name of the object + * @return the index of the object or -1 if no object exists with the given name + */ + public int getObjectIndex(String name) { + for (ObjectRef ref : getCurrentKey().objectRefs) + if (animation.getTimeline(ref.timeline).name.equals(name)) return ref.id; + return -1; + } + + /** + * Returns a time line object with the given name. + * + * @param name the name of the object + * @return the object with the given name + * @throws ArrayIndexOutOfBoundsException if no object exists with the given name + * @throws NullPointerException if no object exists with the given name + */ + public Object getObject(String name) { + return this.unmappedTweenedKeys[animation.getTimeline(name).id].object(); + } + + /** + * Returns a object reference for the given time line bone. + * + * @param object the time line object + * @return the object reference for the given bone + * @throws NullPointerException if no reference for the given object was found + */ + public ObjectRef getObjectRef(Object object) { + return this.getCurrentKey().getObjectRefTimeline(this.objToTimeline.get(object).id); + } + + /** + * Returns the name for the given bone or object. + * + * @param boneOrObject the bone or object + * @return the name of the bone or object + * @throws NullPointerException if no name for the given bone or bject was found + */ + public String getNameFor(Bone boneOrObject) { + return this.animation.getTimeline(objToTimeline.get(boneOrObject).id).name; + } + + /** + * Returns the object info for the given bone or object. + * + * @param boneOrObject the bone or object + * @return the object info of the bone or object + * @throws NullPointerException if no object info for the given bone or bject was found + */ + public ObjectInfo getObjectInfoFor(Bone boneOrObject) { + return this.animation.getTimeline(objToTimeline.get(boneOrObject).id).objectInfo; + } + + /** + * Returns the time line key for the given bone or object + * + * @param boneOrObject the bone or object + * @return the time line key of the bone or object, or null if no time line key was found + */ + public Timeline.Key getKeyFor(Bone boneOrObject) { + return objToTimeline.get(boneOrObject); + } + + /** + * Calculates and returns a {@link Box} for the given bone or object. + * + * @param boneOrObject the bone or object to calculate the bounding box for + * @return the box for the given bone or object + * @throws NullPointerException if no object info for the given bone or object exists + */ + public Box getBox(Bone boneOrObject) { + ObjectInfo info = getObjectInfoFor(boneOrObject); + this.prevBBox.calcFor(boneOrObject, info); + return this.prevBBox; + } + + /** + * Returns whether the given point at x,y lies inside the box of the given bone or object. + * + * @param boneOrObject the bone or object + * @param x the x value of the point + * @param y the y value of the point + * @return true if x,y lies inside the box of the given bone or object + * @throws NullPointerException if no object info for the given bone or object exists + */ + public boolean collidesFor(Bone boneOrObject, float x, float y) { + ObjectInfo info = getObjectInfoFor(boneOrObject); + this.prevBBox.calcFor(boneOrObject, info); + return this.prevBBox.collides(boneOrObject, info, x, y); + } + + /** + * Returns whether the given point lies inside the box of the given bone or object. + * + *

param bone the bone or object + * + * @param point the point + * @return true if the point lies inside the box of the given bone or object + * @throws NullPointerException if no object info for the given bone or object exists + */ + public boolean collidesFor(Bone boneOrObject, Point point) { + return this.collidesFor(boneOrObject, point.x, point.y); + } + + /** + * Returns whether the given area collides with the box of the given bone or object. + * + * @param boneOrObject the bone or object + * @param area the rectangular area + * @return true if the area collides with the bone or object + */ + public boolean collidesFor(Bone boneOrObject, Rectangle area) { + ObjectInfo info = getObjectInfoFor(boneOrObject); + this.prevBBox.calcFor(boneOrObject, info); + return this.prevBBox.isInside(area); + } + + /** + * Sets the given values of the bone with the given name. + * + * @param name the name of the bone + * @param x the new x value of the bone + * @param y the new y value of the bone + * @param angle the new angle of the bone + * @param scaleX the new scale in x direction of the bone + * @param scaleY the new scale in y direction of the bone + * @throws SpriterException if no bone exists of the given name + */ + public void setBone(String name, float x, float y, float angle, float scaleX, float scaleY) { + int index = getBoneIndex(name); + if (index == -1) throw new SpriterException("No bone found of name \"" + name + "\""); + BoneRef ref = getCurrentKey().getBoneRef(index); + Bone bone = getBone(index); + bone.set(x, y, angle, scaleX, scaleY, 0f, 0f); + unmapObjects(ref); + } + + /** + * Sets the given values of the bone with the given name. + * + * @param name the name of the bone + * @param position the new position of the bone + * @param angle the new angle of the bone + * @param scale the new scale of the bone + * @throws SpriterException if no bone exists of the given name + */ + public void setBone(String name, Point position, float angle, Point scale) { + this.setBone(name, position.x, position.y, angle, scale.x, scale.y); + } + + /** + * Sets the given values of the bone with the given name. + * + * @param name the name of the bone + * @param x the new x value of the bone + * @param y the new y value of the bone + * @param angle the new angle of the bone + * @throws SpriterException if no bone exists of the given name + */ + public void setBone(String name, float x, float y, float angle) { + Bone b = getBone(name); + setBone(name, x, y, angle, b.scale.x, b.scale.y); + } + + /** + * Sets the given values of the bone with the given name. + * + * @param name the name of the bone + * @param position the new position of the bone + * @param angle the new angle of the bone + * @throws SpriterException if no bone exists of the given name + */ + public void setBone(String name, Point position, float angle) { + Bone b = getBone(name); + setBone(name, position.x, position.y, angle, b.scale.x, b.scale.y); + } + + /** + * Sets the position of the bone with the given name. + * + * @param name the name of the bone + * @param x the new x value of the bone + * @param y the new y value of the bone + * @throws SpriterException if no bone exists of the given name + */ + public void setBone(String name, float x, float y) { + Bone b = getBone(name); + setBone(name, x, y, b.angle); + } + + /** + * Sets the position of the bone with the given name. + * + * @param name the name of the bone + * @param position the new position of the bone + * @throws SpriterException if no bone exists of the given name + */ + public void setBone(String name, Point position) { + setBone(name, position.x, position.y); + } + + /** + * Sets the angle of the bone with the given name + * + * @param name the name of the bone + * @param angle the new angle of the bone + * @throws SpriterException if no bone exists of the given name + */ + public void setBone(String name, float angle) { + Bone b = getBone(name); + setBone(name, b.position.x, b.position.y, angle); + } + + /** + * Sets the values of the bone with the given name to the values of the given bone + * + * @param name the name of the bone + * @param bone the bone with the new values + * @throws SpriterException if no bone exists of the given name + */ + public void setBone(String name, Bone bone) { + setBone(name, bone.position, bone.angle, bone.scale); + } + + /** + * Sets the given values of the object with the given name. + * + * @param name the name of the object + * @param x the new position in x direction of the object + * @param y the new position in y direction of the object + * @param angle the new angle of the object + * @param scaleX the new scale in x direction of the object + * @param scaleY the new scale in y direction of the object + * @param pivotX the new pivot in x direction of the object + * @param pivotY the new pivot in y direction of the object + * @param alpha the new alpha value of the object + * @param folder the new folder index of the object + * @param file the new file index of the object + * @throws SpriterException if no object exists of the given name + */ + public void setObject( + String name, + float x, + float y, + float angle, + float scaleX, + float scaleY, + float pivotX, + float pivotY, + float alpha, + int folder, + int file) { + int index = getObjectIndex(name); + if (index == -1) throw new SpriterException("No object found for name \"" + name + "\""); + ObjectRef ref = getCurrentKey().getObjectRef(index); + Object object = getObject(index); + object.set(x, y, angle, scaleX, scaleY, pivotX, pivotY, alpha, folder, file); + unmapObjects(ref); + } + + /** + * Sets the given values of the object with the given name. + * + * @param name the name of the object + * @param position the new position of the object + * @param angle the new angle of the object + * @param scale the new scale of the object + * @param pivot the new pivot of the object + * @param alpha the new alpha value of the object + * @param ref the new file reference of the object + * @throws SpriterException if no object exists of the given name + */ + public void setObject( + String name, + Point position, + float angle, + Point scale, + Point pivot, + float alpha, + FileReference ref) { + this.setObject( + name, + position.x, + position.y, + angle, + scale.x, + scale.y, + pivot.x, + pivot.y, + alpha, + ref.folder, + ref.file); + } + + /** + * Sets the given values of the object with the given name. + * + * @param name the name of the object + * @param x the new position in x direction of the object + * @param y the new position in y direction of the object + * @param angle the new angle of the object + * @param scaleX the new scale in x direction of the object + * @param scaleY the new scale in y direction of the object + * @throws SpriterException if no object exists of the given name + */ + public void setObject(String name, float x, float y, float angle, float scaleX, float scaleY) { + Object b = getObject(name); + setObject( + name, + x, + y, + angle, + scaleX, + scaleY, + b.pivot.x, + b.pivot.y, + b.alpha, + b.ref.folder, + b.ref.file); + } + + /** + * Sets the given values of the object with the given name. + * + * @param name the name of the object + * @param x the new position in x direction of the object + * @param y the new position in y direction of the object + * @param angle the new angle of the object + * @throws SpriterException if no object exists of the given name + */ + public void setObject(String name, float x, float y, float angle) { + Object b = getObject(name); + setObject(name, x, y, angle, b.scale.x, b.scale.y); + } + + /** + * Sets the given values of the object with the given name. + * + * @param name the name of the object + * @param position the new position of the object + * @param angle the new angle of the object + * @throws SpriterException if no object exists of the given name + */ + public void setObject(String name, Point position, float angle) { + Object b = getObject(name); + setObject(name, position.x, position.y, angle, b.scale.x, b.scale.y); + } + + /** + * Sets the position of the object with the given name. + * + * @param name the name of the object + * @param x the new position in x direction of the object + * @param y the new position in y direction of the object + * @throws SpriterException if no object exists of the given name + */ + public void setObject(String name, float x, float y) { + Object b = getObject(name); + setObject(name, x, y, b.angle); + } + + /** + * Sets the position of the object with the given name. + * + * @param name the name of the object + * @param position the new position of the object + * @throws SpriterException if no object exists of the given name + */ + public void setObject(String name, Point position) { + setObject(name, position.x, position.y); + } + + /** + * Sets the position of the object with the given name. + * + * @param name the name of the object + * @param angle the new angle of the object + * @throws SpriterException if no object exists of the given name + */ + public void setObject(String name, float angle) { + Object b = getObject(name); + setObject(name, b.position.x, b.position.y, angle); + } + + /** + * Sets the position of the object with the given name. + * + * @param name the name of the object + * @param alpha the new alpha value of the object + * @param folder the new folder index of the object + * @param file the new file index of the object + * @throws SpriterException if no object exists of the given name + */ + public void setObject(String name, float alpha, int folder, int file) { + Object b = getObject(name); + setObject( + name, + b.position.x, + b.position.y, + b.angle, + b.scale.x, + b.scale.y, + b.pivot.x, + b.pivot.y, + alpha, + folder, + file); + } + + /** + * Sets the values of the object with the given name to the values of the given object. + * + * @param name the name of the object + * @param object the object with the new values + * @throws SpriterException if no object exists of the given name + */ + public void setObject(String name, Object object) { + setObject( + name, + object.position, + object.angle, + object.scale, + object.pivot, + object.alpha, + object.ref); + } + + /** + * Maps all object from the parent's coordinate system to the global coordinate system. + * + * @param base the root bone to start at. Set it to null to traverse the whole bone + * hierarchy. + */ + public void unmapObjects(BoneRef base) { + int start = base == null ? -1 : base.id - 1; + for (int i = start + 1; i < getCurrentKey().boneRefs.length; i++) { + BoneRef ref = getCurrentKey().getBoneRef(i); + if (ref.parent != base && base != null) continue; + Bone parent = + ref.parent == null + ? this.root + : this.unmappedTweenedKeys[ref.parent.timeline].object(); + unmappedTweenedKeys[ref.timeline].object().set(tweenedKeys[ref.timeline].object()); + unmappedTweenedKeys[ref.timeline].object().unmap(parent); + unmapObjects(ref); + } + for (ObjectRef ref : getCurrentKey().objectRefs) { + if (ref.parent != base && base != null) continue; + Bone parent = + ref.parent == null + ? this.root + : this.unmappedTweenedKeys[ref.parent.timeline].object(); + unmappedTweenedKeys[ref.timeline].object().set(tweenedKeys[ref.timeline].object()); + unmappedTweenedKeys[ref.timeline].object().unmap(parent); + } + } + + /** + * Returns the current set entity. + * + * @return the current entity + */ + public Entity getEntity() { + return this.entity; + } + + /** + * Sets the entity for this player instance. The animation will be switched to the first one of + * the new entity. + * + * @param entity the new entity + * @throws SpriterException if the entity is null + */ + public void setEntity(Entity entity) { + if (entity == null) throw new SpriterException("entity can not be null!"); + this.entity = entity; + int maxAnims = entity.getAnimationWithMostTimelines().timelines(); + tweenedKeys = new Timeline.Key[maxAnims]; + unmappedTweenedKeys = new Timeline.Key[maxAnims]; + for (int i = 0; i < maxAnims; i++) { + Timeline.Key key = new Timeline.Key(i); + Timeline.Key keyU = new Timeline.Key(i); + key.setObject(new Timeline.Key.Object(new Point(0, 0))); + keyU.setObject(new Timeline.Key.Object(new Point(0, 0))); + tweenedKeys[i] = key; + unmappedTweenedKeys[i] = keyU; + this.objToTimeline.put(keyU.object(), keyU); + } + this.tempTweenedKeys = tweenedKeys; + this.tempUnmappedTweenedKeys = unmappedTweenedKeys; + this.setAnimation(entity.getAnimation(0)); + } + + /** + * Returns the current set animation. + * + * @return the current animation + */ + public Animation getAnimation() { + return this.animation; + } + + /** + * Sets the animation of this player. + * + * @param animation the new animation + * @throws SpriterException if the animation is null or the current animation is + * not a member of the current set entity + */ + public void setAnimation(Animation animation) { + Animation prevAnim = this.animation; + if (animation == this.animation) return; + if (animation == null) throw new SpriterException("animation can not be null!"); + if (!this.entity.containsAnimation(animation) && animation.id != -1) + throw new SpriterException( + "animation has to be in the same entity as the current set one!"); + if (animation != this.animation) time = 0; + this.animation = animation; + float tempTime = this.time; + this.time = 0; + this.update(0); + this.time = tempTime; + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).animationChanged(prevAnim, animation); + } + } + + /** + * Sets the animation of this player to the one with the given name. + * + * @param name the name of the animation + * @throws SpriterException if no animation exists with the given name + */ + public void setAnimation(String name) { + this.setAnimation(entity.getAnimation(name)); + } + + /** + * Sets the animation of this player to the one with the given index. + * + * @param index the index of the animation + * @throws IndexOutOfBoundsException if the index is out of range + */ + public void setAnimation(int index) { + this.setAnimation(entity.getAnimation(index)); + } + + /** + * Returns a bounding box for this player. The bounding box is calculated for all bones and + * object starting from the given root. + * + * @param root the starting root. Set it to null to calculate the bounding box for the whole + * player + * @return the bounding box + */ + public Rectangle getBoundingRectangle(BoneRef root) { + Bone boneRoot = root == null ? this.root : this.unmappedTweenedKeys[root.timeline].object(); + this.rect.set( + boneRoot.position.x, boneRoot.position.y, boneRoot.position.x, boneRoot.position.y); + this.calcBoundingRectangle(root); + this.rect.calculateSize(); + return this.rect; + } + + /** + * Returns a bounding box for this player. The bounding box is calculated for all bones and + * object starting from the given root. + * + * @param root the starting root. Set it to null to calculate the bounding box for the whole + * player + * @return the bounding box + */ + public Rectangle getBoudingRectangle(Bone root) { + return this.getBoundingRectangle(root == null ? null : getBoneRef(root)); + } + + private void calcBoundingRectangle(BoneRef root) { + for (BoneRef ref : getCurrentKey().boneRefs) { + if (ref.parent != root && root != null) continue; + Bone bone = this.unmappedTweenedKeys[ref.timeline].object(); + this.prevBBox.calcFor(bone, animation.getTimeline(ref.timeline).objectInfo); + Rectangle.setBiggerRectangle(rect, this.prevBBox.getBoundingRect(), rect); + this.calcBoundingRectangle(ref); + } + for (ObjectRef ref : getCurrentKey().objectRefs) { + if (ref.parent != root) continue; + Bone bone = this.unmappedTweenedKeys[ref.timeline].object(); + this.prevBBox.calcFor(bone, animation.getTimeline(ref.timeline).objectInfo); + Rectangle.setBiggerRectangle(rect, this.prevBBox.getBoundingRect(), rect); + } + } + + /** + * Returns the current main line key based on the current {@link #time}. + * + * @return the current main line key + */ + public Mainline.Key getCurrentKey() { + return this.currentKey; + } + + /** + * Returns the current time. The player will make sure that the current time is always between 0 + * and {@link Animation#length}. + * + * @return the current time + */ + public float getTime() { + return time; + } + + /** + * Sets the time for the current time. The player will make sure that the new time will not + * exceed the time bounds of the current animation. + * + * @param time the new time + * @return this player to enable chained operations + */ + public Player setTime(float time) { + this.time = time; + int prevSpeed = this.speed; + this.speed = 0; + this.increaseTime(0); + this.speed = prevSpeed; + return this; + } + + /** + * Scales this player based on the current set scale. + * + * @param scale the scaling factor. 1f means no scale. + * @return this player to enable chained operations + */ + public Player scale(float scale) { + this.root.scale.scale(scale, scale); + return this; + } + + /** + * Returns the current scale. + * + * @return the current scale + */ + public float getScale() { + return root.scale.x; + } + + /** + * Sets the scale of this player to the given one. Only uniform scaling is supported. + * + * @param scale the new scale. 1f means 100% scale. + * @return this player to enable chained operations + */ + public Player setScale(float scale) { + this.root.scale.set(scale * flippedX(), scale * flippedY()); + return this; + } + + /** + * Flips this player around the x and y axis. + * + * @param x whether to flip the player around the x axis + * @param y whether to flip the player around the y axis + * @return this player to enable chained operations + */ + public Player flip(boolean x, boolean y) { + if (x) this.flipX(); + if (y) this.flipY(); + return this; + } + + /** + * Flips the player around the x axis. + * + * @return this player to enable chained operations + */ + public Player flipX() { + this.root.scale.x *= -1; + return this; + } + + /** + * Flips the player around the y axis. + * + * @return this player to enable chained operations + */ + public Player flipY() { + this.root.scale.y *= -1; + return this; + } + + /** + * Returns whether this player is flipped around the x axis. + * + * @return 1 if this player is not flipped, -1 if it is flipped + */ + public int flippedX() { + return (int) Math.signum(root.scale.x); + } + + /** + * Returns whether this player is flipped around the y axis. + * + * @return 1 if this player is not flipped, -1 if it is flipped + */ + public int flippedY() { + return (int) Math.signum(root.scale.y); + } + + /** + * Sets the position of this player to the given coordinates. + * + * @param x the new position in x direction + * @param y the new position in y direction + * @return this player to enable chained operations + */ + public Player setPosition(float x, float y) { + this.dirty = true; + this.position.set(x, y); + return this; + } + + /** + * Sets the position of the player to the given one. + * + * @param position the new position + * @return this player to enable chained operations + */ + public Player setPosition(Point position) { + return this.setPosition(position.x, position.y); + } + + /** + * Adds the given coordinates to the current position of this player. + * + * @param x the amount in x direction + * @param y the amount in y direction + * @return this player to enable chained operations + */ + public Player translatePosition(float x, float y) { + return this.setPosition(position.x + x, position.y + y); + } + + /** + * Adds the given amount to the current position of this player. + * + * @param amount the amount to add + * @return this player to enable chained operations + */ + public Player translate(Point amount) { + return this.translatePosition(amount.x, amount.y); + } + + /** + * Returns the current position in x direction. + * + * @return the current position in x direction + */ + public float getX() { + return position.x; + } + + /** + * Returns the current position in y direction. + * + * @return the current position in y direction + */ + public float getY() { + return position.y; + } + + /** + * Rotates this player by the given angle. + * + * @param angle the angle in degrees + * @return this player to enable chained operations + */ + public Player rotate(float angle) { + return this.setAngle(angle + this.angle); + } + + /** + * Returns the current set angle. + * + * @return the current angle + */ + public float getAngle() { + return this.angle; + } + + /** + * Sets the angle of this player to the given angle. + * + * @param angle the angle in degrees + * @return this player to enable chained operations + */ + public Player setAngle(float angle) { + this.dirty = true; + this.angle = angle; + return this; + } + + /** + * Sets the pivot, i.e. origin, of this player. A pivot at (0,0) means that the origin of the + * played animation will have the same one as in Spriter. + * + * @param x the new pivot in x direction + * @param y the new pivot in y direction + * @return this player to enable chained operations + */ + public Player setPivot(float x, float y) { + this.dirty = true; + this.pivot.set(x, y); + return this; + } + + /** + * Sets the pivot, i.e. origin, of this player. A pivot at (0,0) means that the origin of the + * played animation will have the same one as in Spriter. + * + * @param pivot the new pivot + * @return this player to enable chained operations + */ + public Player setPivot(Point pivot) { + return this.setPivot(pivot.x, pivot.y); + } + + /** + * Translates the current set pivot position by the given amount. + * + * @param x the amount in x direction + * @param y the amount in y direction + * @return this player to enable chained operations + */ + public Player translatePivot(float x, float y) { + return this.setPivot(pivot.x + x, pivot.y + y); + } + + /** + * Adds the given amount to the current set pivot position. + * + * @param amount the amount to add + * @return this player to enable chained operations + */ + public Player translatePivot(Point amount) { + return this.translatePivot(amount.x, amount.y); + } + + /** + * Returns the current set pivot in x direction. + * + * @return the pivot in x direction + */ + public float getPivotX() { + return pivot.x; + } + + /** + * Returns the current set pivot in y direction. + * + * @return the pivot in y direction + */ + public float getPivotY() { + return pivot.y; + } + + /** + * Appends a listener to the listeners list of this player. + * + * @param listener the listener to add + */ + public void addListener(PlayerListener listener) { + this.listeners.add(listener); + } + + /** + * Removes a listener from the listeners list of this player. + * + * @param listener the listener to remove + */ + public void removeListener(PlayerListener listener) { + this.listeners.remove(listener); + } + + /** + * Returns an iterator to iterate over all time line bones in the current animation. + * + * @return the bone iterator + */ + public Iterator boneIterator() { + return this.boneIterator(this.getCurrentKey().boneRefs[0]); + } + + /** + * Returns an iterator to iterate over all time line bones in the current animation starting at + * a given root. + * + * @param start the bone reference to start at + * @return the bone iterator + */ + public Iterator boneIterator(BoneRef start) { + this.boneIterator.index = start.id; + return this.boneIterator; + } + + /** + * Returns an iterator to iterate over all time line objects in the current animation. + * + * @return the object iterator + */ + public Iterator objectIterator() { + return this.objectIterator(this.getCurrentKey().objectRefs[0]); + } + + /** + * Returns an iterator to iterate over all time line objects in the current animation starting + * at a given root. + * + * @param start the object reference to start at + * @return the object iterator + */ + public Iterator objectIterator(ObjectRef start) { + this.objectIterator.index = start.id; + return this.objectIterator; + } + + /** + * A listener to listen for specific events which can occur during the runtime of a {@link + * Player} instance. + * + * @author Trixt0r + */ + public interface PlayerListener { + + /** + * Gets called if the current animation has reached it's end or it's beginning (depends on + * the current set {@link Player#speed}). + * + * @param animation the animation which finished. + */ + void animationFinished(Animation animation); + + /** + * Gets called if the animation of the player gets changed. If {@link + * Player#setAnimation(Animation)} gets called and the new animation is the same as the + * previous one, this method will not be called. + * + * @param oldAnim the old animation + * @param newAnim the new animation + */ + void animationChanged(Animation oldAnim, Animation newAnim); + + /** + * Gets called before a player updates the current animation. + * + * @param player the player which is calling this method. + */ + void preProcess(Player player); + + /** + * Gets called after a player updated the current animation. + * + * @param player the player which is calling this method. + */ + void postProcess(Player player); + + /** + * Gets called if the mainline key gets changed. If {@link Player#speed} is big enough it + * can happen that mainline keys between the previous and the new mainline key will be + * ignored. + * + * @param prevKey the previous mainline key + * @param newKey the new mainline key + */ + void mainlineKeyChanged(Mainline.Key prevKey, Mainline.Key newKey); + } + + /** + * An attachment is an abstract object which can be attached to a {@link Player} object. An + * attachment extends a {@link Bone} which means that {@link Bone#position}, {@link Bone#scale} + * and {@link Bone#angle} can be set to change the relative position to its {@link + * Attachment#parent} The {@link Player} object will make sure that the attachment will be + * transformed relative to its {@link Attachment#parent}. + * + * @author Trixt0r + */ + public abstract static class Attachment extends Timeline.Key.Bone { + + private final Point positionTemp, scaleTemp; + private Bone parent; + private float angleTemp; + + /** + * Creates a new attachment + * + * @param parent the parent of this attachment + */ + public Attachment(Bone parent) { + this.positionTemp = new Point(); + this.scaleTemp = new Point(); + this.setParent(parent); + } + + /** + * Returns the current set parent. + * + * @return the parent + */ + public Bone getParent() { + return this.parent; + } + + /** + * Sets the parent of this attachment. + * + * @param parent the parent + * @throws SpriterException if parent is null + */ + public void setParent(Bone parent) { + if (parent == null) throw new SpriterException("The parent cannot be null!"); + this.parent = parent; + } + + final void update() { + // Save relative positions + this.positionTemp.set(super.position); + this.scaleTemp.set(super.scale); + this.angleTemp = super.angle; + + super.unmap(parent); + this.setPosition(super.position.x, super.position.y); + this.setScale(super.scale.x, super.scale.y); + this.setAngle(super.angle); + + // Load realtive positions + super.position.set(this.positionTemp); + super.scale.set(this.scaleTemp); + super.angle = this.angleTemp; + } + + /** + * Sets the position to the given coordinates. + * + * @param x the x coordinate + * @param y the y coordinate + */ + protected abstract void setPosition(float x, float y); + + /** + * Sets the scale to the given scale. + * + * @param xscale the scale in x direction + * @param yscale the scale in y direction + */ + protected abstract void setScale(float xscale, float yscale); + + /** + * Sets the angle to the given one. + * + * @param angle the angle in degrees + */ + protected abstract void setAngle(float angle); + } + + /** + * An iterator to iterate over all time line objects in the current animation. + * + * @author Trixt0r + */ + class ObjectIterator implements Iterator { + int index = 0; + + public boolean hasNext() { + return index < getCurrentKey().objectRefs.length; + } + + public Object next() { + return unmappedTweenedKeys[getCurrentKey().objectRefs[index++].timeline].object(); + } + + public void remove() { + throw new SpriterException("remove() is not supported by this iterator!"); + } + } + + /** + * An iterator to iterate over all time line bones in the current animation. + * + * @author Trixt0r + */ + class BoneIterator implements Iterator { + int index = 0; + + public boolean hasNext() { + return index < getCurrentKey().boneRefs.length; + } + + public Bone next() { + return unmappedTweenedKeys[getCurrentKey().boneRefs[index++].timeline].object(); + } + + public void remove() { + throw new SpriterException("remove() is not supported by this iterator!"); + } + } } diff --git a/src/main/java/com/brashmonkey/spriter/PlayerTweener.java b/src/main/java/com/brashmonkey/spriter/PlayerTweener.java index 8e76840..8c173e4 100644 --- a/src/main/java/com/brashmonkey/spriter/PlayerTweener.java +++ b/src/main/java/com/brashmonkey/spriter/PlayerTweener.java @@ -1,162 +1,172 @@ package com.brashmonkey.spriter; /** - * A player tweener is responsible for tweening to {@link Player} instances. - * Such a - * @author Trixt0r + * A player tweener is responsible for tweening to {@link Player} instances. Such a * + * @author Trixt0r */ -public class PlayerTweener extends Player{ - - private TweenedAnimation anim; - private Player player1, player2; - /** - * Indicates whether to update the {@link Player} instances this instance is holding. - * If this variable is set to false, you will have to call {@link Player#update()} on your own. - */ - public boolean updatePlayers = true; - - /** - * The name of root bone to start the tweening at. - * Set it to null to tween the whole hierarchy. - */ - public String baseBoneName = null; - - /** - * Creates a player tweener which will tween the given two players. - * @param player1 the first player - * @param player2 the second player - */ - public PlayerTweener(Player player1, Player player2){ - super(player1.getEntity()); - this.setPlayers(player1, player2); - } - - /** - * Creates a player tweener based on the entity. - * The players to tween will be created by this instance. - * @param entity the entity the players will animate - */ - public PlayerTweener(Entity entity){ - this(new Player(entity), new Player(entity)); - } - - /** - * Tweens the current set players. - * This method will update the set players if {@link #updatePlayers} is true. - * @throws SpriterException if no bone with {@link #baseBoneName} exists - */ - @Override - public void update(){ - if(updatePlayers){ - player1.update(); - player2.update(); - } - anim.setAnimations(player1.animation, player2.animation); - super.update(); - if(baseBoneName != null){ - int index = anim.onFirstMainLine()? player1.getBoneIndex(baseBoneName) : player2.getBoneIndex(baseBoneName); - if(index == -1) throw new SpriterException("A bone with name \""+baseBoneName+"\" does no exist!"); - anim.base = anim.getCurrentKey().getBoneRef(index); - super.update(); - } - } - - /** - * Sets the players for this tweener. - * Both players have to hold the same {@link Entity} - * @param player1 the first player - * @param player2 the second player - */ - public void setPlayers(Player player1, Player player2){ - if(player1.entity != player2.entity) - throw new SpriterException("player1 and player2 have to hold the same entity!"); - this.player1 = player1; - this.player2 = player2; - if(player1.entity == entity) return; - this.anim = new TweenedAnimation(player1.getEntity()); - anim.setAnimations(player1.animation, player2.animation); - super.setEntity(player1.getEntity()); - super.setAnimation(anim); - } - - /** - * Returns the first set player. - * @return the first player - */ - public Player getFirstPlayer(){ - return this.player1; - } - - /** - * Returns the second set player. - * @return the second player - */ - public Player getSecondPlayer(){ - return this.player2; - } - - /** - * Sets the interpolation weight of this tweener. - * @param weight the interpolation weight between 0.0f and 1.0f - */ - public void setWeight(float weight){ - this.anim.weight = weight; - } - - /** - * Returns the interpolation weight. - * @return the interpolation weight between 0.0f and 1.0f - */ - public float getWeight(){ - return this.anim.weight; - } - - - /** - * Sets the base animation of this tweener. - * Has only an effect if {@link #baseBoneName} is not null. - * @param anim the base animation - */ - public void setBaseAnimation(Animation anim){ - this.anim.baseAnimation = anim; - } - - /** - * Sets the base animation of this tweener by the given animation index. - * Has only an effect if {@link #baseBoneName} is not null. - * @param index the index of the base animation - */ - public void setBaseAnimation(int index){ - this.setBaseAnimation(entity.getAnimation(index)); - } - - /** - * Sets the base animation of this tweener by the given name. - * Has only an effect if {@link #baseBoneName} is not null. - * @param name the name of the base animation - */ - public void setBaseAnimation(String name){ - this.setBaseAnimation(entity.getAnimation(name)); - } - - /** - * Returns the base animation if this tweener. - * @return the base animation - */ - public Animation getBaseAnimation(){ - return this.anim.baseAnimation; - } - - /** - * Not supported by this class. - */ - @Override - public void setAnimation(Animation anim){} - - /** - * Not supported by this class. - */ - @Override - public void setEntity(Entity entity){} +public class PlayerTweener extends Player { + + /** + * Indicates whether to update the {@link Player} instances this instance is holding. If this + * variable is set to false, you will have to call {@link Player#update(float)} on + * your own. + */ + public boolean updatePlayers = true; + /** + * The name of root bone to start the tweening at. Set it to null to tween the whole hierarchy. + */ + public String baseBoneName = null; + + private TweenedAnimation anim; + private Player player1, player2; + + /** + * Creates a player tweener which will tween the given two players. + * + * @param player1 the first player + * @param player2 the second player + */ + public PlayerTweener(Player player1, Player player2) { + super(player1.getEntity()); + this.setPlayers(player1, player2); + } + + /** + * Creates a player tweener based on the entity. The players to tween will be created by this + * instance. + * + * @param entity the entity the players will animate + */ + public PlayerTweener(Entity entity) { + this(new Player(entity), new Player(entity)); + } + + /** + * Tweens the current set players. This method will update the set players if {@link + * #updatePlayers} is true. + * + * @throws SpriterException if no bone with {@link #baseBoneName} exists + */ + @Override + public void update(float dt) { + if (updatePlayers) { + player1.update(dt * speed); + player2.update(dt * speed); + } + anim.setAnimations(player1.animation, player2.animation); + super.update(dt); + if (baseBoneName != null) { + int index = + anim.onFirstMainLine() + ? player1.getBoneIndex(baseBoneName) + : player2.getBoneIndex(baseBoneName); + if (index == -1) + throw new SpriterException( + "A bone with name \"" + baseBoneName + "\" does no exist!"); + anim.base = anim.getCurrentKey().getBoneRef(index); + super.update(dt); + } + } + + /** + * Sets the players for this tweener. Both players have to hold the same {@link Entity} + * + * @param player1 the first player + * @param player2 the second player + */ + public void setPlayers(Player player1, Player player2) { + if (player1.entity != player2.entity) + throw new SpriterException("player1 and player2 have to hold the same entity!"); + this.player1 = player1; + this.player2 = player2; + if (player1.entity == entity) return; + this.anim = new TweenedAnimation(player1.getEntity()); + anim.setAnimations(player1.animation, player2.animation); + super.setEntity(player1.getEntity()); + super.setAnimation(anim); + } + + /** + * Returns the first set player. + * + * @return the first player + */ + public Player getFirstPlayer() { + return this.player1; + } + + /** + * Returns the second set player. + * + * @return the second player + */ + public Player getSecondPlayer() { + return this.player2; + } + + /** + * Returns the interpolation weight. + * + * @return the interpolation weight between 0.0f and 1.0f + */ + public float getWeight() { + return this.anim.weight; + } + + /** + * Sets the interpolation weight of this tweener. + * + * @param weight the interpolation weight between 0.0f and 1.0f + */ + public void setWeight(float weight) { + this.anim.weight = weight; + } + + /** + * Returns the base animation if this tweener. + * + * @return the base animation + */ + public Animation getBaseAnimation() { + return this.anim.baseAnimation; + } + + /** + * Sets the base animation of this tweener. Has only an effect if {@link #baseBoneName} is not + * null. + * + * @param anim the base animation + */ + public void setBaseAnimation(Animation anim) { + this.anim.baseAnimation = anim; + } + + /** + * Sets the base animation of this tweener by the given animation index. Has only an effect if + * {@link #baseBoneName} is not null. + * + * @param index the index of the base animation + */ + public void setBaseAnimation(int index) { + this.setBaseAnimation(entity.getAnimation(index)); + } + + /** + * Sets the base animation of this tweener by the given name. Has only an effect if {@link + * #baseBoneName} is not null. + * + * @param name the name of the base animation + */ + public void setBaseAnimation(String name) { + this.setBaseAnimation(entity.getAnimation(name)); + } + + /** Not supported by this class. */ + @Override + public void setAnimation(Animation anim) {} + + /** Not supported by this class. */ + @Override + public void setEntity(Entity entity) {} } diff --git a/src/main/java/com/brashmonkey/spriter/Point.java b/src/main/java/com/brashmonkey/spriter/Point.java index b8616c7..e6b6b3a 100644 --- a/src/main/java/com/brashmonkey/spriter/Point.java +++ b/src/main/java/com/brashmonkey/spriter/Point.java @@ -1,135 +1,138 @@ package com.brashmonkey.spriter; /** - * A utility class to keep the code short. - * A point is essentially that what you would expect if you think about a point in a 2D space. - * It holds an x and y value. You can {@link #translate(Point)}, {@link #scale(Point)}, {@link #rotate(float)} and {@link #set(Point)} a point. - * @author Trixt0r + * A utility class to keep the code short. A point is essentially that what you would expect if you + * think about a point in a 2D space. It holds an x and y value. You can {@link #translate(Point)}, + * {@link #scale(Point)}, {@link #rotate(float)} and {@link #set(Point)} a point. * + * @author Trixt0r */ public class Point { - - /** - * The x coordinates of this point. - */ - public float x; - /** - * The y coordinates of this point. - */ - public float y; - - /** - * Creates a point at (0,0). - */ - public Point(){ - this(0,0); - } - - /** - * Creates a point at the position of the given point. - * @param point the point to set this point at - */ - public Point(Point point){ - this(point.x, point.y); - } - - /** - * Creates a point at (x, y). - * @param x the x coordinate - * @param y the y coordinate - */ - public Point(float x, float y){ - this.set(x, y); - } - - /** - * Sets this point to the given coordinates. - * @param x the x coordinate - * @param y the y coordinate - * @return this point for chained operations - */ - public Point set(float x, float y){ - this.x = x; - this.y = y; - return this; - } - - /** - * Adds the given amount to this point. - * @param x the amount in x direction to add - * @param y the amount in y direction to add - * @return this point for chained operations - */ - public Point translate(float x, float y){ - return this.set(this.x+x, this.y+y); - } - - /** - * Scales this point by the given amount. - * @param x the scale amount in x direction - * @param y the scale amount in y direction - * @return this point for chained operations - */ - public Point scale(float x, float y){ - return this.set(this.x*x, this.y*y); - } - - /** - * Sets this point to the given point. - * @param point the new coordinates - * @return this point for chained operations - */ - public Point set(Point point){ - return this.set(point.x, point.y); - } - - /** - * Adds the given amount to this point. - * @param amount the amount to add - * @return this point for chained operations - */ - public Point translate(Point amount){ - return this.translate(amount.x, amount.y); - } - - /** - * Scales this point by the given amount. - * @param amount the amount to scale - * @return this point for chained operations - */ - public Point scale(Point amount){ - return this.scale(amount.x, amount.y); - } - - /** - * Rotates this point around (0,0) by the given amount of degrees. - * @param degrees the angle to rotate this point - * @return this point for chained operations - */ - public Point rotate(float degrees){ - if(x != 0 || y != 0){ - float cos = Calculator.cosDeg(degrees); - float sin = Calculator.sinDeg(degrees); - - float xx = x*cos-y*sin; - float yy = x*sin+y*cos; - - this.x = xx; - this.y = yy; - } - return this; - } - - /** - * Returns a copy of this point with the current set values. - * @return a copy of this point - */ - public Point copy(){ - return new Point(x,y); - } - - public String toString(){ - return "["+x+","+y+"]"; - } + /** The x coordinates of this point. */ + public float x; + /** The y coordinates of this point. */ + public float y; + + /** Creates a point at (0,0). */ + public Point() { + this(0, 0); + } + + /** + * Creates a point at the position of the given point. + * + * @param point the point to set this point at + */ + public Point(Point point) { + this(point.x, point.y); + } + + /** + * Creates a point at (x, y). + * + * @param x the x coordinate + * @param y the y coordinate + */ + public Point(float x, float y) { + this.set(x, y); + } + + /** + * Sets this point to the given coordinates. + * + * @param x the x coordinate + * @param y the y coordinate + * @return this point for chained operations + */ + public Point set(float x, float y) { + this.x = x; + this.y = y; + return this; + } + + /** + * Adds the given amount to this point. + * + * @param x the amount in x direction to add + * @param y the amount in y direction to add + * @return this point for chained operations + */ + public Point translate(float x, float y) { + return this.set(this.x + x, this.y + y); + } + + /** + * Scales this point by the given amount. + * + * @param x the scale amount in x direction + * @param y the scale amount in y direction + * @return this point for chained operations + */ + public Point scale(float x, float y) { + return this.set(this.x * x, this.y * y); + } + + /** + * Sets this point to the given point. + * + * @param point the new coordinates + * @return this point for chained operations + */ + public Point set(Point point) { + return this.set(point.x, point.y); + } + + /** + * Adds the given amount to this point. + * + * @param amount the amount to add + * @return this point for chained operations + */ + public Point translate(Point amount) { + return this.translate(amount.x, amount.y); + } + + /** + * Scales this point by the given amount. + * + * @param amount the amount to scale + * @return this point for chained operations + */ + public Point scale(Point amount) { + return this.scale(amount.x, amount.y); + } + + /** + * Rotates this point around (0,0) by the given amount of degrees. + * + * @param degrees the angle to rotate this point + * @return this point for chained operations + */ + public Point rotate(float degrees) { + if (x != 0 || y != 0) { + float cos = Calculator.cosDeg(degrees); + float sin = Calculator.sinDeg(degrees); + + float xx = x * cos - y * sin; + float yy = x * sin + y * cos; + + this.x = xx; + this.y = yy; + } + return this; + } + + /** + * Returns a copy of this point with the current set values. + * + * @return a copy of this point + */ + public Point copy() { + return new Point(x, y); + } + + public String toString() { + return "[" + x + "," + y + "]"; + } } diff --git a/src/main/java/com/brashmonkey/spriter/Rectangle.java b/src/main/java/com/brashmonkey/spriter/Rectangle.java index 42c1387..befc38f 100644 --- a/src/main/java/com/brashmonkey/spriter/Rectangle.java +++ b/src/main/java/com/brashmonkey/spriter/Rectangle.java @@ -1,118 +1,122 @@ package com.brashmonkey.spriter; /** - * Represents a 2D rectangle with left, top, right and bottom bounds. - * A rectangle is responsible for calculating its own size and checking if a point is inside it or if it is intersecting with another rectangle. - * @author Trixt0r + * Represents a 2D rectangle with left, top, right and bottom bounds. A rectangle is responsible for + * calculating its own size and checking if a point is inside it or if it is intersecting with + * another rectangle. * + * @author Trixt0r */ public class Rectangle { - - /** - * Belongs to the bounds of this rectangle. - */ - public float left, top, right, bottom; - /** - * The size of this rectangle. - */ - public final Dimension size; - - /** - * Creates a rectangle with the given bounds. - * @param left left bounding - * @param top top bounding - * @param right right bounding - * @param bottom bottom bounding - */ - public Rectangle(float left, float top, float right, float bottom){ - this.set(left, top, right, bottom); - this.size = new Dimension(0, 0); - this.calculateSize(); - } - - /** - * Creates a rectangle with the bounds of the given rectangle. - * @param rect rectangle containing the bounds. - */ - public Rectangle(Rectangle rect){ - this(rect.left, rect.top, rect.right, rect.bottom); - } - - /** - * Returns whether the given point (x,y) is inside this rectangle. - * @param x the x coordinate - * @param y the y coordinate - * @return true if (x,y) is inside - */ - public boolean isInside(float x, float y){ - return x >= this.left && x <= this.right && y <= this.top && y >= this.bottom; - } - - /** - * Returns whether the given point is inside this rectangle. - * @param point the point - * @return true if the point is inside - */ - public boolean isInside(Point point){ - return isInside(point.x, point.y); - } - - /** - * Calculates the size of this rectangle. - */ - public void calculateSize(){ - this.size.set(right-left, top-bottom); - } - - /** - * Sets the bounds of this rectangle to the bounds of the given rectangle. - * @param rect rectangle containing the bounds. - */ - public void set(Rectangle rect){ - if(rect == null) return; - this.bottom = rect.bottom; - this.left = rect.left; - this.right = rect.right; - this.top = rect.top; - this.calculateSize(); - } - - /** - * Sets the bounds of this rectangle to the given bounds. - * @param left left bounding - * @param top top bounding - * @param right right bounding - * @param bottom bottom bounding - */ - public void set(float left, float top, float right, float bottom){ - this.left = left; - this.top = top; - this.right = right; - this.bottom = bottom; - } - - /** - * Returns whether the given two rectangles are intersecting. - * @param rect1 the first rectangle - * @param rect2 the second rectangle - * @return true if the rectangles are intersecting - */ - public static boolean areIntersecting(Rectangle rect1, Rectangle rect2){ - return rect1.isInside(rect2.left, rect2.top) || rect1.isInside(rect2.right, rect2.top) - || rect1.isInside(rect2.left, rect2.bottom) || rect1.isInside(rect2.right, rect2.bottom); - } - - /** - * Creates a bigger rectangle of the given two and saves it in the target. - * @param rect1 the first rectangle - * @param rect2 the second rectangle - * @param target the target to save the new bounds. - */ - public static void setBiggerRectangle(Rectangle rect1, Rectangle rect2, Rectangle target){ - target.left = Math.min(rect1.left, rect2.left); - target.bottom = Math.min(rect1.bottom, rect2.bottom); - target.right = Math.max(rect1.right, rect2.right); - target.top = Math.max(rect1.top, rect2.top); - } + /** The size of this rectangle. */ + public final Dimension size; + /** Belongs to the bounds of this rectangle. */ + public float left, top, right, bottom; + + /** + * Creates a rectangle with the given bounds. + * + * @param left left bounding + * @param top top bounding + * @param right right bounding + * @param bottom bottom bounding + */ + public Rectangle(float left, float top, float right, float bottom) { + this.set(left, top, right, bottom); + this.size = new Dimension(0, 0); + this.calculateSize(); + } + + /** + * Creates a rectangle with the bounds of the given rectangle. + * + * @param rect rectangle containing the bounds. + */ + public Rectangle(Rectangle rect) { + this(rect.left, rect.top, rect.right, rect.bottom); + } + + /** + * Returns whether the given two rectangles are intersecting. + * + * @param rect1 the first rectangle + * @param rect2 the second rectangle + * @return true if the rectangles are intersecting + */ + public static boolean areIntersecting(Rectangle rect1, Rectangle rect2) { + return rect1.isInside(rect2.left, rect2.top) + || rect1.isInside(rect2.right, rect2.top) + || rect1.isInside(rect2.left, rect2.bottom) + || rect1.isInside(rect2.right, rect2.bottom); + } + + /** + * Creates a bigger rectangle of the given two and saves it in the target. + * + * @param rect1 the first rectangle + * @param rect2 the second rectangle + * @param target the target to save the new bounds. + */ + public static void setBiggerRectangle(Rectangle rect1, Rectangle rect2, Rectangle target) { + target.left = Math.min(rect1.left, rect2.left); + target.bottom = Math.min(rect1.bottom, rect2.bottom); + target.right = Math.max(rect1.right, rect2.right); + target.top = Math.max(rect1.top, rect2.top); + } + + /** + * Returns whether the given point (x,y) is inside this rectangle. + * + * @param x the x coordinate + * @param y the y coordinate + * @return true if (x,y) is inside + */ + public boolean isInside(float x, float y) { + return x >= this.left && x <= this.right && y <= this.top && y >= this.bottom; + } + + /** + * Returns whether the given point is inside this rectangle. + * + * @param point the point + * @return true if the point is inside + */ + public boolean isInside(Point point) { + return isInside(point.x, point.y); + } + + /** Calculates the size of this rectangle. */ + public void calculateSize() { + this.size.set(right - left, top - bottom); + } + + /** + * Sets the bounds of this rectangle to the bounds of the given rectangle. + * + * @param rect rectangle containing the bounds. + */ + public void set(Rectangle rect) { + if (rect == null) return; + this.bottom = rect.bottom; + this.left = rect.left; + this.right = rect.right; + this.top = rect.top; + this.calculateSize(); + } + + /** + * Sets the bounds of this rectangle to the given bounds. + * + * @param left left bounding + * @param top top bounding + * @param right right bounding + * @param bottom bottom bounding + */ + public void set(float left, float top, float right, float bottom) { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } } diff --git a/src/main/java/com/brashmonkey/spriter/SCMLReader.java b/src/main/java/com/brashmonkey/spriter/SCMLReader.java index f5f5c8a..a6c793e 100644 --- a/src/main/java/com/brashmonkey/spriter/SCMLReader.java +++ b/src/main/java/com/brashmonkey/spriter/SCMLReader.java @@ -1,305 +1,398 @@ package com.brashmonkey.spriter; +import com.brashmonkey.spriter.Entity.CharacterMap; +import com.brashmonkey.spriter.Entity.ObjectInfo; +import com.brashmonkey.spriter.Entity.ObjectType; +import com.brashmonkey.spriter.Mainline.Key.BoneRef; +import com.brashmonkey.spriter.Mainline.Key.ObjectRef; +import com.brashmonkey.spriter.XmlReader.Element; + import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; -import com.brashmonkey.spriter.Entity.*; -import com.brashmonkey.spriter.Mainline.Key.*; -import com.brashmonkey.spriter.XmlReader.*; - /** - * This class parses a SCML file and creates a {@link Data} instance. - * If you want to keep track of what is going on during the build process of the objects parsed from the SCML file, - * you could extend this class and override the load*() methods for pre or post processing. - * This could be e.g. useful for a loading screen which responds to the current building or parsing state. + * This class parses a SCML file and creates a {@link Data} instance. If you want to keep track of + * what is going on during the build process of the objects parsed from the SCML file, you could + * extend this class and override the load*() methods for pre or post processing. This could be e.g. + * useful for a loading screen which responds to the current building or parsing state. + * * @author Trixt0r */ public class SCMLReader { - - protected Data data; - - /** - * Creates a new SCML reader and will parse all objects in the given stream. - * @param stream the stream - */ - public SCMLReader(InputStream stream){ - this.data = this.load(stream); - } - - /** - * Creates a new SCML reader and will parse the given xml string. - * @param xml the xml string - */ - public SCMLReader(String xml){ - this.data = this.load(xml); - } - - /** - * Parses the SCML object save in the given xml string and returns the build data object. - * @param xml the xml string - * @return the built data - */ - protected Data load(String xml){ - XmlReader reader = new XmlReader(); - return load(reader.parse(xml)); - } - - /** - * Parses the SCML objects saved in the given stream and returns the built data object. - * @param stream the stream from the SCML file - * @return the built data - */ - protected Data load(InputStream stream){ - try { - XmlReader reader = new XmlReader(); - return load(reader.parse(stream)); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - /** - * Reads the data from the given root element, i.e. the spriter_data node. - * @param root - * @return - */ - protected Data load(Element root) { - ArrayList folders = root.getChildrenByName("folder"); - ArrayList entities = root.getChildrenByName("entity"); - data = new Data(root.get("scml_version"), root.get("generator"), root.get("generator_version"), - Data.PixelMode.get(root.getInt("pixel_mode", 0)), - folders.size(), entities.size()); - loadFolders(folders); - loadEntities(entities); - return data; - } + protected Data data; - /** - * Iterates through the given folders and adds them to the current {@link Data} object. - * @param folders a list of folders to load - */ - protected void loadFolders(ArrayList folders){ - for(int i = 0; i < folders.size(); i++){ - Element repo = folders.get(i); - ArrayList files = repo.getChildrenByName("file"); - Folder folder = new Folder(repo.getInt("id"), repo.get("name", "no_name_"+i), files.size()); - loadFiles(files, folder); - data.addFolder(folder); - } - } - - /** - * Iterates through the given files and adds them to the given {@link Folder} object. - * @param files a list of files to load - * @param folder the folder containing the files - */ - protected void loadFiles(ArrayList files, Folder folder){ - for(int j = 0; j < files.size(); j++){ - Element f = files.get(j); - File file = new File(f.getInt("id"), f.get("name"), - new Dimension(f.getInt("width", 0), f.getInt("height", 0)), - new Point(f.getFloat("pivot_x", 0f), f.getFloat("pivot_y", 1f))); - - folder.addFile(file); - } - } + /** + * Creates a new SCML reader and will parse all objects in the given stream. + * + * @param stream the stream + */ + public SCMLReader(InputStream stream) { + this.data = this.load(stream); + } - /** - * Iterates through the given entities and adds them to the current {@link Data} object. - * @param entities a list of entities to load - */ - protected void loadEntities(ArrayList entities){ - for(int i = 0; i < entities.size(); i++){ - Element e = entities.get(i); - ArrayList infos = e.getChildrenByName("obj_info"); - ArrayList charMaps = e.getChildrenByName("character_map"); - ArrayList animations = e.getChildrenByName("animation"); - Entity entity = new Entity(e.getInt("id"), e.get("name"), - animations.size(), charMaps.size(), infos.size()); - data.addEntity(entity); - loadObjectInfos(infos, entity); - loadCharacterMaps(charMaps, entity); - loadAnimations(animations, entity); - } - } - - /** - * Iterates through the given object infos and adds them to the given {@link Entity} object. - * @param infos a list of infos to load - * @param entity the entity containing the infos - */ - protected void loadObjectInfos(ArrayList infos, Entity entity){ - for(int i = 0; i< infos.size(); i++){ - Element info = infos.get(i); - ObjectInfo objInfo = new ObjectInfo(info.get("name","info"+i), - ObjectType.getObjectInfoFor(info.get("type","")), - new Dimension(info.getFloat("w", 0), info.getFloat("h", 0))); - entity.addInfo(objInfo); - Element frames = info.getChildByName("frames"); - if(frames == null) continue; - ArrayList frameIndices = frames.getChildrenByName("i"); - for (int i1 = 0; i1 < frameIndices.size(); i1++) { - Element index = frameIndices.get(i1); - int folder = index.getInt("folder", 0); - int file = index.getInt("file", 0); - objInfo.frames.add(new FileReference(folder, file)); - } - } - } - - /** - * Iterates through the given character maps and adds them to the given {@link Entity} object. - * @param maps a list of character maps to load - * @param entity the entity containing the character maps - */ - protected void loadCharacterMaps(ArrayList maps, Entity entity){ - for(int i = 0; i< maps.size(); i++){ - Element map = maps.get(i); - CharacterMap charMap = new CharacterMap(map.getInt("id"), map.getAttribute("name", "charMap"+i)); - entity.addCharacterMap(charMap); - ArrayList mappings = map.getChildrenByName("map"); - for (int i1 = 0; i1 < mappings.size(); i1++) { - Element mapping = mappings.get(i1); - int folder = mapping.getInt("folder"); - int file = mapping.getInt("file"); - charMap.put(new FileReference(folder, file), - new FileReference(mapping.getInt("target_folder", folder), mapping.getInt("target_file", file))); - } - } - } - - /** - * Iterates through the given animations and adds them to the given {@link Entity} object. - * @param animations a list of animations to load - * @param entity the entity containing the animations maps - */ - protected void loadAnimations(ArrayList animations, Entity entity){ - for(int i = 0; i < animations.size(); i++){ - Element a = animations.get(i); - ArrayList timelines = a.getChildrenByName("timeline"); - Element mainline = a.getChildByName("mainline"); - ArrayList mainlineKeys = mainline.getChildrenByName("key"); - Animation animation = new Animation(new Mainline(mainlineKeys.size()), - a.getInt("id"), a.get("name"), a.getInt("length"), - a.getBoolean("looping", true),timelines.size()); - entity.addAnimation(animation); - loadMainlineKeys(mainlineKeys, animation.mainline); - loadTimelines(timelines, animation, entity); - animation.prepare(); - } - } - - /** - * Iterates through the given mainline keys and adds them to the given {@link Mainline} object. - * @param keys a list of mainline keys - * @param main the mainline - */ - protected void loadMainlineKeys(ArrayList keys, Mainline main){ - for(int i = 0; i < main.keys.length; i++){ - Element k = keys.get(i); - ArrayList objectRefs = k.getChildrenByName("object_ref"); - ArrayList boneRefs = k.getChildrenByName("bone_ref"); - Curve curve = new Curve(); - curve.setType(Curve.getType(k.get("curve_type","linear"))); - curve.constraints.set(k.getFloat("c1", 0f),k.getFloat("c2", 0f),k.getFloat("c3", 0f),k.getFloat("c4", 0f)); - Mainline.Key key = new Mainline.Key(k.getInt("id"), k.getInt("time", 0), curve, - boneRefs.size(), objectRefs.size()); - main.addKey(key); - loadRefs(objectRefs, boneRefs, key); - } - } - - /** - * Iterates through the given bone and object references and adds them to the given {@link Mainline.Key} object. - * @param objectRefs a list of object references - * @param boneRefs a list if bone references - * @param key the mainline key - */ - protected void loadRefs(ArrayList objectRefs, ArrayList boneRefs, Mainline.Key key){ - for (int i = 0; i < boneRefs.size(); i++) { - Element e = boneRefs.get(i); - BoneRef boneRef = new BoneRef(e.getInt("id"), e.getInt("timeline"), - e.getInt("key"), key.getBoneRef(e.getInt("parent", -1))); - key.addBoneRef(boneRef); - } + /** + * Creates a new SCML reader and will parse the given xml string. + * + * @param xml the xml string + */ + public SCMLReader(String xml) { + this.data = this.load(xml); + } - for (int i = 0; i < objectRefs.size(); i++) { - Element o = objectRefs.get(i); - ObjectRef objectRef = new ObjectRef(o.getInt("id"), o.getInt("timeline"), - o.getInt("key"), key.getBoneRef(o.getInt("parent", -1)), o.getInt("z_index", 0)); - key.addObjectRef(objectRef); - } - Arrays.sort(key.objectRefs); - } - - /** - * Iterates through the given timelines and adds them to the given {@link Animation} object. - * @param timelines a list of timelines - * @param animation the animation containing the timelines - * @param entity entity for assigning the timeline an object info - */ - protected void loadTimelines(ArrayList timelines, Animation animation, Entity entity){ - for(int i = 0; i< timelines.size(); i++){ - Element t = timelines.get(i); - ArrayList keys = timelines.get(i).getChildrenByName("key"); - String name = t.get("name", "no_name_"+i); - ObjectType type = ObjectType.getObjectInfoFor(t.get("object_type", "sprite")); - ObjectInfo info = entity.getInfo(name); - if(info == null) info = new ObjectInfo(name, type, new Dimension(0,0)); - Timeline timeline = new Timeline(t.getInt("id"), name, info, keys.size()); - animation.addTimeline(timeline); - loadTimelineKeys(keys, timeline); - } - } - - /** - * Iterates through the given timeline keys and adds them to the given {@link Timeline} object. - * @param keys a list if timeline keys - * @param timeline the timeline containing the keys - */ - protected void loadTimelineKeys(ArrayList keys, Timeline timeline){ - for(int i = 0; i< keys.size(); i++){ - Element k = keys.get(i); - Curve curve = new Curve(); - curve.setType(Curve.getType(k.get("curve_type", "linear"))); - curve.constraints.set(k.getFloat("c1", 0f),k.getFloat("c2", 0f),k.getFloat("c3", 0f),k.getFloat("c4", 0f)); - Timeline.Key key = new Timeline.Key(k.getInt("id"), k.getInt("time", 0), k.getInt("spin", 1), curve); - Element obj = k.getChildByName("bone"); - if(obj == null) obj = k.getChildByName("object"); - - Point position = new Point(obj.getFloat("x", 0f), obj.getFloat("y", 0f)); - Point scale = new Point(obj.getFloat("scale_x", 1f), obj.getFloat("scale_y", 1f)); - Point pivot = new Point(obj.getFloat("pivot_x", 0f), obj.getFloat("pivot_y", (timeline.objectInfo.type == ObjectType.Bone)? .5f:1f)); - float angle = obj.getFloat("angle", 0f), alpha = 1f; - int folder = -1, file = -1; - if(obj.getName().equals("object")){ - if(timeline.objectInfo.type == ObjectType.Sprite){ - alpha = obj.getFloat("a", 1f); - folder = obj.getInt("folder", -1); - file = obj.getInt("file", -1); - File f = data.getFolder(folder).getFile(file); - pivot = new Point(obj.getFloat("pivot_x", f.pivot.x), obj.getFloat("pivot_y", f.pivot.y)); - timeline.objectInfo.size.set(f.size); - } - } - Timeline.Key.Object object; - if(obj.getName().equals("bone")) object = new Timeline.Key.Object(position, scale, pivot, angle, alpha, new FileReference(folder, file)); - else object = new Timeline.Key.Object(position, scale, pivot, angle, alpha, new FileReference(folder, file)); - key.setObject(object); - timeline.addKey(key); - } - } - - /** - * Returns the loaded SCML data. - * @return the SCML data. - */ - public Data getData(){ - return data; - } - -} + /** + * Parses the SCML object save in the given xml string and returns the build data object. + * + * @param xml the xml string + * @return the built data + */ + protected Data load(String xml) { + XmlReader reader = new XmlReader(); + return load(reader.parse(xml)); + } + + /** + * Parses the SCML objects saved in the given stream and returns the built data object. + * + * @param stream the stream from the SCML file + * @return the built data + */ + protected Data load(InputStream stream) { + try { + XmlReader reader = new XmlReader(); + return load(reader.parse(stream)); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Reads the data from the given root element, i.e. the spriter_data node. + * + * @param root + * @return + */ + protected Data load(Element root) { + ArrayList folders = root.getChildrenByName("folder"); + ArrayList entities = root.getChildrenByName("entity"); + data = + new Data( + root.get("scml_version"), + root.get("generator"), + root.get("generator_version"), + Data.PixelMode.get(root.getInt("pixel_mode", 0)), + folders.size(), + entities.size()); + loadFolders(folders); + loadEntities(entities); + return data; + } + + /** + * Iterates through the given folders and adds them to the current {@link Data} object. + * + * @param folders a list of folders to load + */ + protected void loadFolders(ArrayList folders) { + for (int i = 0; i < folders.size(); i++) { + Element repo = folders.get(i); + ArrayList files = repo.getChildrenByName("file"); + Folder folder = + new Folder(repo.getInt("id"), repo.get("name", "no_name_" + i), files.size()); + loadFiles(files, folder); + data.addFolder(folder); + } + } + + /** + * Iterates through the given files and adds them to the given {@link Folder} object. + * + * @param files a list of files to load + * @param folder the folder containing the files + */ + protected void loadFiles(ArrayList files, Folder folder) { + for (int j = 0; j < files.size(); j++) { + Element f = files.get(j); + File file = + new File( + f.getInt("id"), + f.get("name"), + new Dimension(f.getInt("width", 0), f.getInt("height", 0)), + new Point(f.getFloat("pivot_x", 0f), f.getFloat("pivot_y", 1f))); + + folder.addFile(file); + } + } + + /** + * Iterates through the given entities and adds them to the current {@link Data} object. + * + * @param entities a list of entities to load + */ + protected void loadEntities(ArrayList entities) { + for (int i = 0; i < entities.size(); i++) { + Element e = entities.get(i); + ArrayList infos = e.getChildrenByName("obj_info"); + ArrayList charMaps = e.getChildrenByName("character_map"); + ArrayList animations = e.getChildrenByName("animation"); + Entity entity = + new Entity( + e.getInt("id"), + e.get("name"), + animations.size(), + charMaps.size(), + infos.size()); + data.addEntity(entity); + loadObjectInfos(infos, entity); + loadCharacterMaps(charMaps, entity); + loadAnimations(animations, entity); + } + } + + /** + * Iterates through the given object infos and adds them to the given {@link Entity} object. + * + * @param infos a list of infos to load + * @param entity the entity containing the infos + */ + protected void loadObjectInfos(ArrayList infos, Entity entity) { + for (int i = 0; i < infos.size(); i++) { + Element info = infos.get(i); + ObjectInfo objInfo = + new ObjectInfo( + info.get("name", "info" + i), + ObjectType.getObjectInfoFor(info.get("type", "")), + new Dimension(info.getFloat("w", 0), info.getFloat("h", 0))); + entity.addInfo(objInfo); + Element frames = info.getChildByName("frames"); + if (frames == null) continue; + ArrayList frameIndices = frames.getChildrenByName("i"); + for (int i1 = 0; i1 < frameIndices.size(); i1++) { + Element index = frameIndices.get(i1); + int folder = index.getInt("folder", 0); + int file = index.getInt("file", 0); + objInfo.frames.add(new FileReference(folder, file)); + } + } + } + /** + * Iterates through the given character maps and adds them to the given {@link Entity} object. + * + * @param maps a list of character maps to load + * @param entity the entity containing the character maps + */ + protected void loadCharacterMaps(ArrayList maps, Entity entity) { + for (int i = 0; i < maps.size(); i++) { + Element map = maps.get(i); + CharacterMap charMap = + new CharacterMap(map.getInt("id"), map.getAttribute("name", "charMap" + i)); + entity.addCharacterMap(charMap); + ArrayList mappings = map.getChildrenByName("map"); + for (int i1 = 0; i1 < mappings.size(); i1++) { + Element mapping = mappings.get(i1); + int folder = mapping.getInt("folder"); + int file = mapping.getInt("file"); + charMap.put( + new FileReference(folder, file), + new FileReference( + mapping.getInt("target_folder", folder), + mapping.getInt("target_file", file))); + } + } + } + + /** + * Iterates through the given animations and adds them to the given {@link Entity} object. + * + * @param animations a list of animations to load + * @param entity the entity containing the animations maps + */ + protected void loadAnimations(ArrayList animations, Entity entity) { + for (int i = 0; i < animations.size(); i++) { + Element a = animations.get(i); + ArrayList timelines = a.getChildrenByName("timeline"); + Element mainline = a.getChildByName("mainline"); + ArrayList mainlineKeys = mainline.getChildrenByName("key"); + Animation animation = + new Animation( + new Mainline(mainlineKeys.size()), + a.getInt("id"), + a.get("name"), + a.getInt("length"), + a.getBoolean("looping", true), + timelines.size()); + entity.addAnimation(animation); + loadMainlineKeys(mainlineKeys, animation.mainline); + loadTimelines(timelines, animation, entity); + animation.prepare(); + } + } + + /** + * Iterates through the given mainline keys and adds them to the given {@link Mainline} object. + * + * @param keys a list of mainline keys + * @param main the mainline + */ + protected void loadMainlineKeys(ArrayList keys, Mainline main) { + for (int i = 0; i < main.keys.length; i++) { + Element k = keys.get(i); + ArrayList objectRefs = k.getChildrenByName("object_ref"); + ArrayList boneRefs = k.getChildrenByName("bone_ref"); + Curve curve = new Curve(); + curve.setType(Curve.getType(k.get("curve_type", "linear"))); + curve.constraints.set( + k.getFloat("c1", 0f), + k.getFloat("c2", 0f), + k.getFloat("c3", 0f), + k.getFloat("c4", 0f)); + Mainline.Key key = + new Mainline.Key( + k.getInt("id"), + k.getInt("time", 0), + curve, + boneRefs.size(), + objectRefs.size()); + main.addKey(key); + loadRefs(objectRefs, boneRefs, key); + } + } + + /** + * Iterates through the given bone and object references and adds them to the given {@link + * Mainline.Key} object. + * + * @param objectRefs a list of object references + * @param boneRefs a list if bone references + * @param key the mainline key + */ + protected void loadRefs( + ArrayList objectRefs, ArrayList boneRefs, Mainline.Key key) { + for (int i = 0; i < boneRefs.size(); i++) { + Element e = boneRefs.get(i); + BoneRef boneRef = + new BoneRef( + e.getInt("id"), + e.getInt("timeline"), + e.getInt("key"), + key.getBoneRef(e.getInt("parent", -1))); + key.addBoneRef(boneRef); + } + + for (int i = 0; i < objectRefs.size(); i++) { + Element o = objectRefs.get(i); + ObjectRef objectRef = + new ObjectRef( + o.getInt("id"), + o.getInt("timeline"), + o.getInt("key"), + key.getBoneRef(o.getInt("parent", -1)), + o.getInt("z_index", 0)); + key.addObjectRef(objectRef); + } + Arrays.sort(key.objectRefs); + } + + /** + * Iterates through the given timelines and adds them to the given {@link Animation} object. + * + * @param timelines a list of timelines + * @param animation the animation containing the timelines + * @param entity entity for assigning the timeline an object info + */ + protected void loadTimelines(ArrayList timelines, Animation animation, Entity entity) { + for (int i = 0; i < timelines.size(); i++) { + Element t = timelines.get(i); + ArrayList keys = timelines.get(i).getChildrenByName("key"); + String name = t.get("name", "no_name_" + i); + ObjectType type = ObjectType.getObjectInfoFor(t.get("object_type", "sprite")); + ObjectInfo info = entity.getInfo(name); + if (info == null) info = new ObjectInfo(name, type, new Dimension(0, 0)); + Timeline timeline = new Timeline(t.getInt("id"), name, info, keys.size()); + animation.addTimeline(timeline); + loadTimelineKeys(keys, timeline); + } + } + + /** + * Iterates through the given timeline keys and adds them to the given {@link Timeline} object. + * + * @param keys a list if timeline keys + * @param timeline the timeline containing the keys + */ + protected void loadTimelineKeys(ArrayList keys, Timeline timeline) { + for (int i = 0; i < keys.size(); i++) { + Element k = keys.get(i); + Curve curve = new Curve(); + curve.setType(Curve.getType(k.get("curve_type", "linear"))); + curve.constraints.set( + k.getFloat("c1", 0f), + k.getFloat("c2", 0f), + k.getFloat("c3", 0f), + k.getFloat("c4", 0f)); + Timeline.Key key = + new Timeline.Key( + k.getInt("id"), k.getInt("time", 0), k.getInt("spin", 1), curve); + Element obj = k.getChildByName("bone"); + if (obj == null) obj = k.getChildByName("object"); + + Point position = new Point(obj.getFloat("x", 0f), obj.getFloat("y", 0f)); + Point scale = new Point(obj.getFloat("scale_x", 1f), obj.getFloat("scale_y", 1f)); + Point pivot = + new Point( + obj.getFloat("pivot_x", 0f), + obj.getFloat( + "pivot_y", + (timeline.objectInfo.type == ObjectType.Bone) ? .5f : 1f)); + float angle = obj.getFloat("angle", 0f), alpha = 1f; + int folder = -1, file = -1; + if (obj.getName().equals("object")) { + if (timeline.objectInfo.type == ObjectType.Sprite) { + alpha = obj.getFloat("a", 1f); + folder = obj.getInt("folder", -1); + file = obj.getInt("file", -1); + File f = data.getFolder(folder).getFile(file); + pivot = + new Point( + obj.getFloat("pivot_x", f.pivot.x), + obj.getFloat("pivot_y", f.pivot.y)); + timeline.objectInfo.size.set(f.size); + } + } + Timeline.Key.Object object; + if (obj.getName().equals("bone")) + object = + new Timeline.Key.Object( + position, + scale, + pivot, + angle, + alpha, + new FileReference(folder, file)); + else + object = + new Timeline.Key.Object( + position, + scale, + pivot, + angle, + alpha, + new FileReference(folder, file)); + key.setObject(object); + timeline.addKey(key); + } + } + + /** + * Returns the loaded SCML data. + * + * @return the SCML data. + */ + public Data getData() { + return data; + } +} diff --git a/src/main/java/com/brashmonkey/spriter/SCONReader.java b/src/main/java/com/brashmonkey/spriter/SCONReader.java index e82a6f8..8bf48f2 100644 --- a/src/main/java/com/brashmonkey/spriter/SCONReader.java +++ b/src/main/java/com/brashmonkey/spriter/SCONReader.java @@ -13,296 +13,388 @@ import java.util.Arrays; /** - * This class parses a SCON file and creates a {@link Data} instance. - * If you want to keep track of what is going on during the build process of the objects parsed from the SCON file, - * you could extend this class and override the load*() methods for pre or post processing. - * This could be e.g. useful for a loading screen which responds to the current building or parsing state. + * This class parses a SCON file and creates a {@link Data} instance. If you want to keep track of + * what is going on during the build process of the objects parsed from the SCON file, you could + * extend this class and override the load*() methods for pre or post processing. This could be e.g. + * useful for a loading screen which responds to the current building or parsing state. + * * @author mrdlink */ public class SCONReader { - protected Data data; + protected Data data; - /** - * Creates a new SCON reader and will parse all objects in the given stream. - * @param stream the stream - */ - public SCONReader(InputStream stream){ - this.data = this.load(stream); - } + /** + * Creates a new SCON reader and will parse all objects in the given stream. + * + * @param stream the stream + */ + public SCONReader(InputStream stream) { + this.data = this.load(stream); + } - /** - * Creates a new SCON reader and will parse the given json string. - * @param json the json string - */ - public SCONReader(String json){ - this.data = this.load(json); - } - - /** - * Parses the SCON object save in the given json string and returns the build data object. - * @param json the json string - * @return the built data - */ - protected Data load(String json){ - return load(JsonReader.parse(json)); - } - - /** - * Parses the SCON objects saved in the given stream and returns the built data object. - * @param stream the stream from the SCON file - * @return the built data - */ - protected Data load(InputStream stream){ - try { - return load(JsonReader.parse(stream)); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } + /** + * Creates a new SCON reader and will parse the given json string. + * + * @param json the json string + */ + public SCONReader(String json) { + this.data = this.load(json); + } - /** - * Reads the data from the given root element, i.e. the spriter_data node. - * @param root - * @return - */ - protected Data load(JSONObject root) { - JSONArray folders = root.getJSONArray("folder"); - JSONArray entities = root.getJSONArray("entity"); - data = new Data(root.getString("scon_version"), root.getString("generator"), root.getString("generator_version"), - Data.PixelMode.get(root.optInt("pixel_mode", 0)), - folders.length(), entities.length()); - loadFolders(folders); - loadEntities(entities); - return data; - } + /** + * Parses the SCON object save in the given json string and returns the build data object. + * + * @param json the json string + * @return the built data + */ + protected Data load(String json) { + return load(JsonReader.parse(json)); + } - /** - * Iterates through the given folders and adds them to the current {@link Data} object. - * @param folders a list of folders to load - */ - protected void loadFolders(JSONArray folders){ - for(int i = 0; i < folders.length(); i++){ - JSONObject repo = folders.getJSONObject(i); - JSONArray files = repo.getJSONArray("file"); + /** + * Parses the SCON objects saved in the given stream and returns the built data object. + * + * @param stream the stream from the SCON file + * @return the built data + */ + protected Data load(InputStream stream) { + try { + return load(JsonReader.parse(stream)); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } - Folder folder = new Folder(repo.getInt("id"), repo.optString("name", "no_name_"+i), files.length()); - loadFiles(files, folder); - data.addFolder(folder); - } - } - - /** - * Iterates through the given files and adds them to the given {@link Folder} object. - * @param files a list of files to load - * @param folder the folder containing the files - */ - protected void loadFiles(JSONArray files, Folder folder){ - for(int j = 0; j < files.length(); j++){ - JSONObject f = files.getJSONObject(j); + /** + * Reads the data from the given root element, i.e. the spriter_data node. + * + * @param root + * @return + */ + protected Data load(JSONObject root) { + JSONArray folders = root.getJSONArray("folder"); + JSONArray entities = root.getJSONArray("entity"); + data = + new Data( + root.getString("scon_version"), + root.getString("generator"), + root.getString("generator_version"), + Data.PixelMode.get(root.optInt("pixel_mode", 0)), + folders.length(), + entities.length()); + loadFolders(folders); + loadEntities(entities); + return data; + } - File file = new File(f.getInt("id"), f.getString("name"), - new Dimension(f.optInt("width", 0), f.optInt("height", 0)), - new Point(f.optFloat("pivot_x", 0f), f.optFloat("pivot_y", 0f))); - - folder.addFile(file); - } - } + /** + * Iterates through the given folders and adds them to the current {@link Data} object. + * + * @param folders a list of folders to load + */ + protected void loadFolders(JSONArray folders) { + for (int i = 0; i < folders.length(); i++) { + JSONObject repo = folders.getJSONObject(i); + JSONArray files = repo.getJSONArray("file"); - /** - * Iterates through the given entities and adds them to the current {@link Data} object. - * @param entities a list of entities to load - */ - protected void loadEntities(JSONArray entities){ - for(int i = 0; i < entities.length(); i++){ - JSONObject e = entities.getJSONObject(i); - JSONArray infos = e.getJSONArray("obj_info"); - JSONArray charMaps = e.getJSONArray("character_map"); - JSONArray animations = e.getJSONArray("animation"); - Entity entity = new Entity(e.getInt("id"), e.getString("name"), - animations.length(), charMaps.length(), infos.length()); - data.addEntity(entity); - loadObjectInfos(infos, entity); - loadCharacterMaps(charMaps, entity); - loadAnimations(animations, entity); - } - } - - /** - * Iterates through the given object infos and adds them to the given {@link Entity} object. - * @param infos a list of infos to load - * @param entity the entity containing the infos - */ - protected void loadObjectInfos(JSONArray infos, Entity entity){ - for(int i = 0; i< infos.length(); i++){ - JSONObject info = infos.getJSONObject(i); - ObjectInfo objInfo = new ObjectInfo(info.getString("name"), - ObjectType.getObjectInfoFor(info.optString("type", "")), - new Dimension(info.optFloat("w", 0f), info.optFloat("h", 0f))); - entity.addInfo(objInfo); - JSONObject frames = info.optJSONObject("frames"); - if(frames == null) continue; - JSONArray frameIndices = frames.getJSONArray("i"); - for (int i1 = 0; i1 < frameIndices.length(); i1++) { - JSONObject index = frameIndices.getJSONObject(i1); - int folder = index.optInt("folder", 0); - int file = index.optInt("file", 0); - objInfo.frames.add(new FileReference(folder, file)); - } - } - } - - /** - * Iterates through the given character maps and adds them to the given {@link Entity} object. - * @param maps a list of character maps to load - * @param entity the entity containing the character maps - */ - protected void loadCharacterMaps(JSONArray maps, Entity entity){ - for(int i = 0; i< maps.length(); i++){ - JSONObject map = maps.getJSONObject(i); - CharacterMap charMap = new CharacterMap(map.getInt("id"), map.optString("name", "charMap"+i)); - entity.addCharacterMap(charMap); - JSONArray mappings = map.getJSONArray("map"); - for (int i1 = 0; i1 < mappings.length(); i1++) { - JSONObject mapping = mappings.getJSONObject(i1); - int folder = mapping.getInt("folder"); - int file = mapping.getInt("file"); - charMap.put(new FileReference(folder, file), - new FileReference(mapping.optInt("target_folder", folder), mapping.optInt("target_file", file))); - } - } - } - - /** - * Iterates through the given animations and adds them to the given {@link Entity} object. - * @param animations a list of animations to load - * @param entity the entity containing the animations maps - */ - protected void loadAnimations(JSONArray animations, Entity entity){ - for(int i = 0; i < animations.length(); i++){ - JSONObject a = animations.getJSONObject(i); - JSONArray timelines = a.getJSONArray("timeline"); - JSONObject mainline = a.getJSONObject("mainline"); - JSONArray mainlineKeys = mainline.getJSONArray("key"); - Animation animation = new Animation(new Mainline(mainlineKeys.length()), - a.getInt("id"), a.getString("name"), a.getInt("length"), - a.optBoolean("looping", true),timelines.length()); - entity.addAnimation(animation); - loadMainlineKeys(mainlineKeys, animation.mainline); - loadTimelines(timelines, animation, entity); - animation.prepare(); - } - } - - /** - * Iterates through the given mainline keys and adds them to the given {@link Mainline} object. - * @param keys a list of mainline keys - * @param main the mainline - */ - protected void loadMainlineKeys(JSONArray keys, Mainline main){ - for(int i = 0; i < main.keys.length; i++){ - JSONObject k = keys.getJSONObject(i); - JSONArray objectRefs = k.getJSONArray("object_ref"); - JSONArray boneRefs = k.getJSONArray("bone_ref"); - Curve curve = new Curve(); - curve.setType(Curve.getType(k.optString("curve_type","linear"))); - curve.constraints.set(k.optFloat("c1", 0f),k.optFloat("c2", 0f),k.optFloat("c3", 0f),k.optFloat("c4", 0f)); - Mainline.Key key = new Mainline.Key(k.getInt("id"), k.optInt("time", 0), curve, - boneRefs.length(), objectRefs.length()); - main.addKey(key); - loadRefs(objectRefs, boneRefs, key); - } - } - - /** - * Iterates through the given bone and object references and adds them to the given {@link Mainline.Key} object. - * @param objectRefs a list of object references - * @param boneRefs a list if bone references - * @param key the mainline key - */ - protected void loadRefs(JSONArray objectRefs, JSONArray boneRefs, Mainline.Key key){ - for (int i = 0; i < boneRefs.length(); i++) { - JSONObject e = boneRefs.getJSONObject(i); - BoneRef boneRef = new BoneRef(e.getInt("id"), e.getInt("timeline"), - e.getInt("key"), key.getBoneRef(e.optInt("parent", -1))); - key.addBoneRef(boneRef); - } + Folder folder = + new Folder( + repo.getInt("id"), + repo.optString("name", "no_name_" + i), + files.length()); + loadFiles(files, folder); + data.addFolder(folder); + } + } - for (int i = 0; i < objectRefs.length(); i++) { - JSONObject o = objectRefs.getJSONObject(i); - ObjectRef objectRef = new ObjectRef(o.getInt("id"), o.getInt("timeline"), - o.getInt("key"), key.getBoneRef(o.optInt("parent", -1)), o.optInt("z_index", 0)); - key.addObjectRef(objectRef); - } - Arrays.sort(key.objectRefs); - } - - /** - * Iterates through the given timelines and adds them to the given {@link Animation} object. - * @param timelines a list of timelines - * @param animation the animation containing the timelines - * @param entity entity for assigning the timeline an object info - */ - protected void loadTimelines(JSONArray timelines, Animation animation, Entity entity){ - for(int i = 0; i< timelines.length(); i++){ - JSONObject t = timelines.getJSONObject(i); - JSONArray keys = timelines.getJSONObject(i).getJSONArray("key"); - String name = t.getString("name"); - ObjectType type = ObjectType.getObjectInfoFor(t.optString("object_type", "sprite")); - ObjectInfo info = entity.getInfo(name); - if(info == null) info = new ObjectInfo(name, type, new Dimension(0,0)); - Timeline timeline = new Timeline(t.getInt("id"), name, info, keys.length()); - animation.addTimeline(timeline); - loadTimelineKeys(keys, timeline); - } - } - - /** - * Iterates through the given timeline keys and adds them to the given {@link Timeline} object. - * @param keys a list if timeline keys - * @param timeline the timeline containing the keys - */ - protected void loadTimelineKeys(JSONArray keys, Timeline timeline){ - for(int i = 0; i< keys.length(); i++){ - JSONObject k = keys.getJSONObject(i); - Curve curve = new Curve(); - curve.setType(Curve.getType(k.optString("curve_type", "linear"))); - curve.constraints.set(k.optFloat("c1", 0f),k.optFloat("c2", 0f),k.optFloat("c3", 0f),k.optFloat("c4", 0f)); - Timeline.Key key = new Timeline.Key(k.getInt("id"), k.optInt("time", 0), k.optInt("spin", 1), curve); - JSONObject obj = k.optJSONObject("bone"); - if(obj == null) obj = k.getJSONObject("object"); - - Point position = new Point(obj.optFloat("x", 0f), obj.optFloat("y", 0f)); - Point scale = new Point(obj.optFloat("scale_x", 1f), obj.optFloat("scale_y", 1f)); - Point pivot = new Point(obj.optFloat("pivot_x", 0f), obj.optFloat("pivot_y", (timeline.objectInfo.type == ObjectType.Bone)? .5f:1f)); - float angle = obj.optFloat("angle", 0f), alpha = 1f; - int folder = -1, file = -1; - if(obj.optString("name", "no_name_"+i).equals("object")){ - if(timeline.objectInfo.type == ObjectType.Sprite){ - alpha = obj.optFloat("a", 1f); - folder = obj.optInt("folder", -1); - file = obj.optInt("file", -1); - File f = data.getFolder(folder).getFile(file); - pivot = new Point(obj.optFloat("pivot_x", f.pivot.x), obj.optFloat("pivot_y", f.pivot.y)); - timeline.objectInfo.size.set(f.size); - } - } - Timeline.Key.Object object; - if(obj.optString("name", "no_name_"+i).equals("bone")) object = new Timeline.Key.Object(position, scale, pivot, angle, alpha, new FileReference(folder, file)); - else object = new Timeline.Key.Object(position, scale, pivot, angle, alpha, new FileReference(folder, file)); - key.setObject(object); - timeline.addKey(key); - } - } - - /** - * Returns the loaded SCON data. - * @return the SCON data. - */ - public Data getData(){ - return data; - } - -} + /** + * Iterates through the given files and adds them to the given {@link Folder} object. + * + * @param files a list of files to load + * @param folder the folder containing the files + */ + protected void loadFiles(JSONArray files, Folder folder) { + for (int j = 0; j < files.length(); j++) { + JSONObject f = files.getJSONObject(j); + + File file = + new File( + f.getInt("id"), + f.getString("name"), + new Dimension(f.optInt("width", 0), f.optInt("height", 0)), + new Point(f.optFloat("pivot_x", 0f), f.optFloat("pivot_y", 0f))); + + folder.addFile(file); + } + } + + /** + * Iterates through the given entities and adds them to the current {@link Data} object. + * + * @param entities a list of entities to load + */ + protected void loadEntities(JSONArray entities) { + for (int i = 0; i < entities.length(); i++) { + JSONObject e = entities.getJSONObject(i); + JSONArray infos = e.getJSONArray("obj_info"); + JSONArray charMaps = e.getJSONArray("character_map"); + JSONArray animations = e.getJSONArray("animation"); + Entity entity = + new Entity( + e.getInt("id"), + e.getString("name"), + animations.length(), + charMaps.length(), + infos.length()); + data.addEntity(entity); + loadObjectInfos(infos, entity); + loadCharacterMaps(charMaps, entity); + loadAnimations(animations, entity); + } + } + + /** + * Iterates through the given object infos and adds them to the given {@link Entity} object. + * + * @param infos a list of infos to load + * @param entity the entity containing the infos + */ + protected void loadObjectInfos(JSONArray infos, Entity entity) { + for (int i = 0; i < infos.length(); i++) { + JSONObject info = infos.getJSONObject(i); + ObjectInfo objInfo = + new ObjectInfo( + info.getString("name"), + ObjectType.getObjectInfoFor(info.optString("type", "")), + new Dimension(info.optFloat("w", 0f), info.optFloat("h", 0f))); + entity.addInfo(objInfo); + JSONObject frames = info.optJSONObject("frames"); + if (frames == null) continue; + JSONArray frameIndices = frames.getJSONArray("i"); + for (int i1 = 0; i1 < frameIndices.length(); i1++) { + JSONObject index = frameIndices.getJSONObject(i1); + int folder = index.optInt("folder", 0); + int file = index.optInt("file", 0); + objInfo.frames.add(new FileReference(folder, file)); + } + } + } + + /** + * Iterates through the given character maps and adds them to the given {@link Entity} object. + * + * @param maps a list of character maps to load + * @param entity the entity containing the character maps + */ + protected void loadCharacterMaps(JSONArray maps, Entity entity) { + for (int i = 0; i < maps.length(); i++) { + JSONObject map = maps.getJSONObject(i); + CharacterMap charMap = + new CharacterMap(map.getInt("id"), map.optString("name", "charMap" + i)); + entity.addCharacterMap(charMap); + JSONArray mappings = map.getJSONArray("map"); + for (int i1 = 0; i1 < mappings.length(); i1++) { + JSONObject mapping = mappings.getJSONObject(i1); + int folder = mapping.getInt("folder"); + int file = mapping.getInt("file"); + charMap.put( + new FileReference(folder, file), + new FileReference( + mapping.optInt("target_folder", folder), + mapping.optInt("target_file", file))); + } + } + } + + /** + * Iterates through the given animations and adds them to the given {@link Entity} object. + * + * @param animations a list of animations to load + * @param entity the entity containing the animations maps + */ + protected void loadAnimations(JSONArray animations, Entity entity) { + for (int i = 0; i < animations.length(); i++) { + JSONObject a = animations.getJSONObject(i); + JSONArray timelines = a.getJSONArray("timeline"); + JSONObject mainline = a.getJSONObject("mainline"); + JSONArray mainlineKeys = mainline.getJSONArray("key"); + Animation animation = + new Animation( + new Mainline(mainlineKeys.length()), + a.getInt("id"), + a.getString("name"), + a.getInt("length"), + a.optBoolean("looping", true), + timelines.length()); + entity.addAnimation(animation); + loadMainlineKeys(mainlineKeys, animation.mainline); + loadTimelines(timelines, animation, entity); + animation.prepare(); + } + } + /** + * Iterates through the given mainline keys and adds them to the given {@link Mainline} object. + * + * @param keys a list of mainline keys + * @param main the mainline + */ + protected void loadMainlineKeys(JSONArray keys, Mainline main) { + for (int i = 0; i < main.keys.length; i++) { + JSONObject k = keys.getJSONObject(i); + JSONArray objectRefs = k.getJSONArray("object_ref"); + JSONArray boneRefs = k.getJSONArray("bone_ref"); + Curve curve = new Curve(); + curve.setType(Curve.getType(k.optString("curve_type", "linear"))); + curve.constraints.set( + k.optFloat("c1", 0f), + k.optFloat("c2", 0f), + k.optFloat("c3", 0f), + k.optFloat("c4", 0f)); + Mainline.Key key = + new Mainline.Key( + k.getInt("id"), + k.optInt("time", 0), + curve, + boneRefs.length(), + objectRefs.length()); + main.addKey(key); + loadRefs(objectRefs, boneRefs, key); + } + } + + /** + * Iterates through the given bone and object references and adds them to the given {@link + * Mainline.Key} object. + * + * @param objectRefs a list of object references + * @param boneRefs a list if bone references + * @param key the mainline key + */ + protected void loadRefs(JSONArray objectRefs, JSONArray boneRefs, Mainline.Key key) { + for (int i = 0; i < boneRefs.length(); i++) { + JSONObject e = boneRefs.getJSONObject(i); + BoneRef boneRef = + new BoneRef( + e.getInt("id"), + e.getInt("timeline"), + e.getInt("key"), + key.getBoneRef(e.optInt("parent", -1))); + key.addBoneRef(boneRef); + } + + for (int i = 0; i < objectRefs.length(); i++) { + JSONObject o = objectRefs.getJSONObject(i); + ObjectRef objectRef = + new ObjectRef( + o.getInt("id"), + o.getInt("timeline"), + o.getInt("key"), + key.getBoneRef(o.optInt("parent", -1)), + o.optInt("z_index", 0)); + key.addObjectRef(objectRef); + } + Arrays.sort(key.objectRefs); + } + + /** + * Iterates through the given timelines and adds them to the given {@link Animation} object. + * + * @param timelines a list of timelines + * @param animation the animation containing the timelines + * @param entity entity for assigning the timeline an object info + */ + protected void loadTimelines(JSONArray timelines, Animation animation, Entity entity) { + for (int i = 0; i < timelines.length(); i++) { + JSONObject t = timelines.getJSONObject(i); + JSONArray keys = timelines.getJSONObject(i).getJSONArray("key"); + String name = t.getString("name"); + ObjectType type = ObjectType.getObjectInfoFor(t.optString("object_type", "sprite")); + ObjectInfo info = entity.getInfo(name); + if (info == null) info = new ObjectInfo(name, type, new Dimension(0, 0)); + Timeline timeline = new Timeline(t.getInt("id"), name, info, keys.length()); + animation.addTimeline(timeline); + loadTimelineKeys(keys, timeline); + } + } + + /** + * Iterates through the given timeline keys and adds them to the given {@link Timeline} object. + * + * @param keys a list if timeline keys + * @param timeline the timeline containing the keys + */ + protected void loadTimelineKeys(JSONArray keys, Timeline timeline) { + for (int i = 0; i < keys.length(); i++) { + JSONObject k = keys.getJSONObject(i); + Curve curve = new Curve(); + curve.setType(Curve.getType(k.optString("curve_type", "linear"))); + curve.constraints.set( + k.optFloat("c1", 0f), + k.optFloat("c2", 0f), + k.optFloat("c3", 0f), + k.optFloat("c4", 0f)); + Timeline.Key key = + new Timeline.Key( + k.getInt("id"), k.optInt("time", 0), k.optInt("spin", 1), curve); + JSONObject obj = k.optJSONObject("bone"); + if (obj == null) obj = k.getJSONObject("object"); + + Point position = new Point(obj.optFloat("x", 0f), obj.optFloat("y", 0f)); + Point scale = new Point(obj.optFloat("scale_x", 1f), obj.optFloat("scale_y", 1f)); + Point pivot = + new Point( + obj.optFloat("pivot_x", 0f), + obj.optFloat( + "pivot_y", + (timeline.objectInfo.type == ObjectType.Bone) ? .5f : 1f)); + float angle = obj.optFloat("angle", 0f), alpha = 1f; + int folder = -1, file = -1; + if (obj.optString("name", "no_name_" + i).equals("object")) { + if (timeline.objectInfo.type == ObjectType.Sprite) { + alpha = obj.optFloat("a", 1f); + folder = obj.optInt("folder", -1); + file = obj.optInt("file", -1); + File f = data.getFolder(folder).getFile(file); + pivot = + new Point( + obj.optFloat("pivot_x", f.pivot.x), + obj.optFloat("pivot_y", f.pivot.y)); + timeline.objectInfo.size.set(f.size); + } + } + Timeline.Key.Object object; + if (obj.optString("name", "no_name_" + i).equals("bone")) + object = + new Timeline.Key.Object( + position, + scale, + pivot, + angle, + alpha, + new FileReference(folder, file)); + else + object = + new Timeline.Key.Object( + position, + scale, + pivot, + angle, + alpha, + new FileReference(folder, file)); + key.setObject(object); + timeline.addKey(key); + } + } + + /** + * Returns the loaded SCON data. + * + * @return the SCON data. + */ + public Data getData() { + return data; + } +} diff --git a/src/main/java/com/brashmonkey/spriter/Spriter.java b/src/main/java/com/brashmonkey/spriter/Spriter.java index 4185a03..3e766f6 100644 --- a/src/main/java/com/brashmonkey/spriter/Spriter.java +++ b/src/main/java/com/brashmonkey/spriter/Spriter.java @@ -10,258 +10,286 @@ /** * A utility class for managing multiple {@link Loader} and {@link Player} instances. - * @author Trixt0r * + * @author Trixt0r */ - @SuppressWarnings("rawtypes") public class Spriter { - - private static Object[] loaderDependencies = new Object[1], drawerDependencies = new Object[1]; - private static Class[] loaderTypes = new Class[1], drawerTypes = new Class[1]; - static{ - loaderTypes[0] = Data.class; - drawerTypes[0] = Loader.class; - } - private static Class loaderClass; - - private static final HashMap loadedData = new HashMap(); - private static final List players = new ArrayList(); - private static final List loaders = new ArrayList(); - private static Drawer drawer; - private static final HashMap entityToLoader = new HashMap(); - private static boolean initialized = false; - - /** - * Sets the dependencies for implemented {@link Loader}. - * @param loaderDependencies the dependencies a loader has to get - */ - public static void setLoaderDependencies(Object... loaderDependencies){ - if(loaderDependencies == null) return; - Spriter.loaderDependencies = new Object[loaderDependencies.length+1]; - System.arraycopy(loaderDependencies, 0, Spriter.loaderDependencies, 1, loaderDependencies.length); - loaderTypes = new Class[loaderDependencies.length+1]; - loaderTypes[0] = Data.class; - for(int i = 0; i< loaderDependencies.length; i++) - loaderTypes[i+1] = loaderDependencies[i].getClass(); - } - - /** - * Sets the dependencies for implemented {@link Drawer}. - * @param drawerDependencies the dependencies a drawer has to get - */ - public static void setDrawerDependencies(Object... drawerDependencies){ - if(drawerDependencies == null) return; - Spriter.drawerDependencies = new Object[drawerDependencies.length+1]; - Spriter.drawerDependencies[0] = null; - System.arraycopy(drawerDependencies, 0, Spriter.drawerDependencies, 1, drawerDependencies.length); - drawerTypes = new Class[drawerDependencies.length+1]; - drawerTypes[0] = Loader.class; - for(int i = 0; i< drawerDependencies.length; i++) - if(drawerDependencies[i] != null) - drawerTypes[i+1] = drawerDependencies[i].getClass(); - } - - /** - * Initializes this class with the implemented {@link Loader} class and {@link Drawer} class. - * Before calling this method make sure that you have set all necessary dependecies with {@link #setDrawerDependencies(Object...)} and {@link #setLoaderDependencies(Object...)}. - * A drawer is created with this method. - * @param loaderClass the loader class - * @param drawerClass the drawer class - */ - public static void init(Class loaderClass, Class drawerClass){ - Spriter.loaderClass = loaderClass; - try { - drawer = drawerClass.getDeclaredConstructor(drawerTypes).newInstance(drawerDependencies); - } catch (Exception e) { - e.printStackTrace(); - } - initialized = drawer != null; - } - - /** - * Loads a SCML file with the given path. - * @param scmlFile the path to the SCML file - */ - public static void load(String scmlFile){ - load(new File(scmlFile)); - } - - /** - * Loads the given SCML file. - * @param scmlFile the scml file - */ - public static void load(File scmlFile){ - try { - load(new FileInputStream(scmlFile), scmlFile.getPath().replaceAll("\\\\", "/")); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - } - - /** - * Loads the given SCML stream pointing to a file saved at the given path. - * @param stream the SCML stream - * @param scmlFile the path to the SCML file - */ - public static void load(InputStream stream, String scmlFile){ - SCMLReader reader = new SCMLReader(stream); - Data data = reader.data; - loadedData.put(scmlFile, data); - loaderDependencies[0] = data; - try { - Loader loader = loaderClass.getDeclaredConstructor(loaderTypes).newInstance(loaderDependencies); - loader.load(new File(scmlFile)); - loaders.add(loader); - for(Entity entity: data.entities) - entityToLoader.put(entity, loader); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Creates a new {@link Player} instance based on the given SCML file with the given entity index - * @param scmlFile name of the SCML file - * @param entityIndex the index of the entity - * @return a {@link Player} instance managed by this class - * @throws SpriterException if the given SCML file was not loaded yet - */ - public static Player newPlayer(String scmlFile, int entityIndex){ - return newPlayer(scmlFile, entityIndex, Player.class); - } - - /** - * Creates a new {@link Player} instance based on the given SCML file with the given entity index and the given class extending a {@link Player} - * @param scmlFile name of the SCML file - * @param entityIndex the index of the entity - * @param playerClass the class extending a {@link Player} class, e.g. {@link PlayerTweener}. - * @return a {@link Player} instance managed by this class - * @throws SpriterException if the given SCML file was not loaded yet - */ - public static Player newPlayer(String scmlFile, int entityIndex, Class playerClass){ - if(!loadedData.containsKey(scmlFile)) throw new SpriterException("You have to load \""+scmlFile+"\" before using it!"); - try { - Player player = playerClass.getDeclaredConstructor(Entity.class).newInstance(loadedData.get(scmlFile).getEntity(entityIndex)); - players.add(player); - return player; - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - /** - * Creates a new {@link Player} instance based on the given SCML file with the given entity name - * @param scmlFile name of the SCML file - * @param entityName name of the entity - * @return a {@link Player} instance managed by this class - * @throws SpriterException if the given SCML file was not loaded yet - */ - public static Player newPlayer(String scmlFile, String entityName){ - if(!loadedData.containsKey(scmlFile)) throw new SpriterException("You have to load \""+scmlFile+"\" before using it!"); - return newPlayer(scmlFile, loadedData.get(scmlFile).getEntityIndex(entityName)); - } - - /** - * Returns a loader for the given SCML filename. - * @param scmlFile the name of the SCML file - * @return the loader for the given SCML filename - * @throws NullPointerException if the SCML file was not loaded yet - */ - public static Loader getLoader(String scmlFile){ - return entityToLoader.get(getData(scmlFile).getEntity(0)); - } - /** - * Updates each created player by this class and immediately draws it. - * This method should only be called if you want to update and render on the same thread. - * @throws SpriterException if {@link #init(Class, Class)} was not called before - */ - @SuppressWarnings("unchecked") - public static void updateAndDraw(){ - if(!initialized) throw new SpriterException("Call init() before updating!"); - for (int i = 0; i < players.size(); i++) { - players.get(i).update(); - drawer.loader = entityToLoader.get(players.get(i).getEntity()); - drawer.draw(players.get(i)); - } - } - - /** - * Updates each created player by this class. - * @throws SpriterException if {@link #init(Class, Class)} was not called before - */ - public static void update(){ - if(!initialized) throw new SpriterException("Call init() before updating!"); - for (int i = 0; i < players.size(); i++) { - players.get(i).update(); - } - } - - /** - * Draws each created player by this class. - * @throws SpriterException if {@link #init(Class, Class)} was not called before - */ - @SuppressWarnings("unchecked") - public static void draw(){ - if(!initialized) throw new SpriterException("Call init() before drawing!"); - for (int i = 0; i < players.size(); i++) { - drawer.loader = entityToLoader.get(players.get(i).getEntity()); - drawer.draw(players.get(i)); - } - } - - /** - * Returns the drawer instance this class is using. - * @return the drawer which draws all players - */ - public static Drawer drawer(){ - return drawer; - } - - /** - * Returns the data for the given SCML filename. - * @param fileName the name of the SCML file - * @return the data for the given SCML filename or null if not loaed yet - */ - public static Data getData(String fileName){ - return loadedData.get(fileName); - } - - /** - * The number of players this class is managing. - * @return number of players - */ - public static int players(){ - return players.size(); - } - - /** - * Clears all previous created players, Spriter datas, disposes all loaders, deletes the drawer and resets all internal lists. - * After this methods was called {@link #init(Class, Class)} has to be called again so that everything works again. - */ - public static void dispose(){ - drawer = null; - drawerDependencies = new Object[1]; - drawerTypes = new Class[1]; - drawerTypes[0] = Loader.class; - - entityToLoader.clear(); + private static final HashMap loadedData = new HashMap(); + private static final List players = new ArrayList(); + private static final List loaders = new ArrayList(); + private static final HashMap entityToLoader = new HashMap(); + private static Object[] loaderDependencies = new Object[1], drawerDependencies = new Object[1]; + private static Class[] loaderTypes = new Class[1], drawerTypes = new Class[1]; + private static Class loaderClass; + private static Drawer drawer; + private static boolean initialized = false; + + static { + loaderTypes[0] = Data.class; + drawerTypes[0] = Loader.class; + } + + /** + * Sets the dependencies for implemented {@link Loader}. + * + * @param loaderDependencies the dependencies a loader has to get + */ + public static void setLoaderDependencies(Object... loaderDependencies) { + if (loaderDependencies == null) return; + Spriter.loaderDependencies = new Object[loaderDependencies.length + 1]; + System.arraycopy( + loaderDependencies, 0, Spriter.loaderDependencies, 1, loaderDependencies.length); + loaderTypes = new Class[loaderDependencies.length + 1]; + loaderTypes[0] = Data.class; + for (int i = 0; i < loaderDependencies.length; i++) + loaderTypes[i + 1] = loaderDependencies[i].getClass(); + } + + /** + * Sets the dependencies for implemented {@link Drawer}. + * + * @param drawerDependencies the dependencies a drawer has to get + */ + public static void setDrawerDependencies(Object... drawerDependencies) { + if (drawerDependencies == null) return; + Spriter.drawerDependencies = new Object[drawerDependencies.length + 1]; + Spriter.drawerDependencies[0] = null; + System.arraycopy( + drawerDependencies, 0, Spriter.drawerDependencies, 1, drawerDependencies.length); + drawerTypes = new Class[drawerDependencies.length + 1]; + drawerTypes[0] = Loader.class; + for (int i = 0; i < drawerDependencies.length; i++) + if (drawerDependencies[i] != null) + drawerTypes[i + 1] = drawerDependencies[i].getClass(); + } + + /** + * Initializes this class with the implemented {@link Loader} class and {@link Drawer} class. + * Before calling this method make sure that you have set all necessary dependecies with {@link + * #setDrawerDependencies(Object...)} and {@link #setLoaderDependencies(Object...)}. A drawer is + * created with this method. + * + * @param loaderClass the loader class + * @param drawerClass the drawer class + */ + public static void init( + Class loaderClass, Class drawerClass) { + Spriter.loaderClass = loaderClass; + try { + drawer = + drawerClass.getDeclaredConstructor(drawerTypes).newInstance(drawerDependencies); + } catch (Exception e) { + e.printStackTrace(); + } + initialized = drawer != null; + } + + /** + * Loads a SCML file with the given path. + * + * @param scmlFile the path to the SCML file + */ + public static void load(String scmlFile) { + load(new File(scmlFile)); + } + + /** + * Loads the given SCML file. + * + * @param scmlFile the scml file + */ + public static void load(File scmlFile) { + try { + load(new FileInputStream(scmlFile), scmlFile.getPath().replaceAll("\\\\", "/")); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + /** + * Loads the given SCML stream pointing to a file saved at the given path. + * + * @param stream the SCML stream + * @param scmlFile the path to the SCML file + */ + public static void load(InputStream stream, String scmlFile) { + SCMLReader reader = new SCMLReader(stream); + Data data = reader.data; + loadedData.put(scmlFile, data); + loaderDependencies[0] = data; + try { + Loader loader = + loaderClass.getDeclaredConstructor(loaderTypes).newInstance(loaderDependencies); + loader.load(new File(scmlFile)); + loaders.add(loader); + for (Entity entity : data.entities) entityToLoader.put(entity, loader); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Creates a new {@link Player} instance based on the given SCML file with the given entity + * index + * + * @param scmlFile name of the SCML file + * @param entityIndex the index of the entity + * @return a {@link Player} instance managed by this class + * @throws SpriterException if the given SCML file was not loaded yet + */ + public static Player newPlayer(String scmlFile, int entityIndex) { + return newPlayer(scmlFile, entityIndex, Player.class); + } + + /** + * Creates a new {@link Player} instance based on the given SCML file with the given entity + * index and the given class extending a {@link Player} + * + * @param scmlFile name of the SCML file + * @param entityIndex the index of the entity + * @param playerClass the class extending a {@link Player} class, e.g. {@link PlayerTweener}. + * @return a {@link Player} instance managed by this class + * @throws SpriterException if the given SCML file was not loaded yet + */ + public static Player newPlayer( + String scmlFile, int entityIndex, Class playerClass) { + if (!loadedData.containsKey(scmlFile)) + throw new SpriterException("You have to load \"" + scmlFile + "\" before using it!"); + try { + Player player = + playerClass + .getDeclaredConstructor(Entity.class) + .newInstance(loadedData.get(scmlFile).getEntity(entityIndex)); + players.add(player); + return player; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * Creates a new {@link Player} instance based on the given SCML file with the given entity name + * + * @param scmlFile name of the SCML file + * @param entityName name of the entity + * @return a {@link Player} instance managed by this class + * @throws SpriterException if the given SCML file was not loaded yet + */ + public static Player newPlayer(String scmlFile, String entityName) { + if (!loadedData.containsKey(scmlFile)) + throw new SpriterException("You have to load \"" + scmlFile + "\" before using it!"); + return newPlayer(scmlFile, loadedData.get(scmlFile).getEntityIndex(entityName)); + } + + /** + * Returns a loader for the given SCML filename. + * + * @param scmlFile the name of the SCML file + * @return the loader for the given SCML filename + * @throws NullPointerException if the SCML file was not loaded yet + */ + public static Loader getLoader(String scmlFile) { + return entityToLoader.get(getData(scmlFile).getEntity(0)); + } + + /** + * Updates each created player by this class and immediately draws it. This method should only + * be called if you want to update and render on the same thread. + * + * @throws SpriterException if {@link #init(Class, Class)} was not called before + */ + @SuppressWarnings("unchecked") + public static void updateAndDraw(float dt) { + if (!initialized) throw new SpriterException("Call init() before updating!"); + for (int i = 0; i < players.size(); i++) { + players.get(i).update(dt); + drawer.loader = entityToLoader.get(players.get(i).getEntity()); + drawer.draw(players.get(i)); + } + } + + /** + * Updates each created player by this class. + * + * @throws SpriterException if {@link #init(Class, Class)} was not called before + */ + public static void update(float dt) { + if (!initialized) throw new SpriterException("Call init() before updating!"); + for (int i = 0; i < players.size(); i++) { + players.get(i).update(dt); + } + } + + /** + * Draws each created player by this class. + * + * @throws SpriterException if {@link #init(Class, Class)} was not called before + */ + @SuppressWarnings("unchecked") + public static void draw() { + if (!initialized) throw new SpriterException("Call init() before drawing!"); + for (int i = 0; i < players.size(); i++) { + drawer.loader = entityToLoader.get(players.get(i).getEntity()); + drawer.draw(players.get(i)); + } + } + + /** + * Returns the drawer instance this class is using. + * + * @return the drawer which draws all players + */ + public static Drawer drawer() { + return drawer; + } + + /** + * Returns the data for the given SCML filename. + * + * @param fileName the name of the SCML file + * @return the data for the given SCML filename or null if not loaed yet + */ + public static Data getData(String fileName) { + return loadedData.get(fileName); + } + + /** + * The number of players this class is managing. + * + * @return number of players + */ + public static int players() { + return players.size(); + } + + /** + * Clears all previous created players, Spriter datas, disposes all loaders, deletes the drawer + * and resets all internal lists. After this methods was called {@link #init(Class, Class)} has + * to be called again so that everything works again. + */ + public static void dispose() { + drawer = null; + drawerDependencies = new Object[1]; + drawerTypes = new Class[1]; + drawerTypes[0] = Loader.class; + + entityToLoader.clear(); + + for (int i = 0; i < loaders.size(); i++) { + loaders.get(i).dispose(); + } + loaders.clear(); + loadedData.clear(); + loaderClass = null; + loaderTypes = new Class[1]; + loaderTypes[0] = Data.class; + loaderDependencies = new Object[1]; - for (int i = 0; i < loaders.size(); i++) { - loaders.get(i).dispose(); - } - loaders.clear(); - loadedData.clear(); - loaderClass = null; - loaderTypes = new Class[1]; - loaderTypes[0] = Data.class; - loaderDependencies = new Object[1]; - - players.clear(); - - initialized = false; - } + players.clear(); + initialized = false; + } } diff --git a/src/main/java/com/brashmonkey/spriter/SpriterException.java b/src/main/java/com/brashmonkey/spriter/SpriterException.java index b01a43b..4252251 100644 --- a/src/main/java/com/brashmonkey/spriter/SpriterException.java +++ b/src/main/java/com/brashmonkey/spriter/SpriterException.java @@ -2,15 +2,14 @@ /** * An Exception which will be thrown if a Spriter specific issue happens at runtime. - * @author Trixt0r * + * @author Trixt0r */ public class SpriterException extends RuntimeException { - - private static final long serialVersionUID = 1L; - - public SpriterException(String message){ - super(message); - } + private static final long serialVersionUID = 1L; + + public SpriterException(String message) { + super(message); + } } diff --git a/src/main/java/com/brashmonkey/spriter/Timeline.java b/src/main/java/com/brashmonkey/spriter/Timeline.java index 1996d0e..1880b16 100644 --- a/src/main/java/com/brashmonkey/spriter/Timeline.java +++ b/src/main/java/com/brashmonkey/spriter/Timeline.java @@ -3,275 +3,368 @@ import com.brashmonkey.spriter.Entity.ObjectInfo; /** - * Represents a time line in a Spriter SCML file. - * A time line holds an {@link #id}, a {@link #name} and at least one {@link Key}. - * @author Trixt0r + * Represents a time line in a Spriter SCML file. A time line holds an {@link #id}, a {@link #name} + * and at least one {@link Key}. * + * @author Trixt0r */ public class Timeline { public final Key[] keys; - private int keyPointer = 0; public final int id; public final String name; public final ObjectInfo objectInfo; - - Timeline(int id, String name, ObjectInfo objectInfo, int keys){ - this.id = id; - this.name = name; - this.objectInfo = objectInfo; - this.keys = new Key[keys]; + private int keyPointer = 0; + + Timeline(int id, String name, ObjectInfo objectInfo, int keys) { + this.id = id; + this.name = name; + this.objectInfo = objectInfo; + this.keys = new Key[keys]; } - - void addKey(Key key){ - this.keys[keyPointer++] = key; + + void addKey(Key key) { + this.keys[keyPointer++] = key; } - + /** * Returns a {@link Key} at the given index + * * @param index the index of the key. * @return the key with the given index. * @throws IndexOutOfBoundsException if the index is out of range */ - public Key getKey(int index){ - return this.keys[index]; + public Key getKey(int index) { + return this.keys[index]; } - - public String toString(){ - String toReturn = getClass().getSimpleName()+"|[id:"+id+", name: "+name+", object_info: "+objectInfo; - for(Key key: keys) - toReturn += "\n"+key; - toReturn+="]"; - return toReturn; + + public String toString() { + String toReturn = + getClass().getSimpleName() + + "|[id:" + + id + + ", name: " + + name + + ", object_info: " + + objectInfo; + for (Key key : keys) toReturn += "\n" + key; + toReturn += "]"; + return toReturn; } - + /** - * Represents a time line key in a Spriter SCML file. - * A key holds an {@link #id}, a {@link #time}, a {@link #spin}, an {@link #object()} and a {@link #curve}. - * @author Trixt0r + * Represents a time line key in a Spriter SCML file. A key holds an {@link #id}, a {@link + * #time}, a {@link #spin}, an {@link #object()} and a {@link #curve}. * + * @author Trixt0r */ - public static class Key{ - - public final int id, spin; - public int time; - public final Curve curve; - public boolean active; - private Object object; - - public Key(int id, int time, int spin, Curve curve){ - this.id = id; - this.time = time; - this.spin = spin; - this.curve = curve; - } - - public Key(int id,int time, int spin){ - this(id, time, 1, new Curve()); - } - - public Key(int id, int time){ - this(id, time, 1); - } - - public Key(int id){ - this(id, 0); - } - - public void setObject(Object object){ - if(object == null) throw new IllegalArgumentException("object can not be null!"); - this.object = object; - } - - public Object object(){ - return this.object; - } - - public String toString(){ - return getClass().getSimpleName()+"|[id: "+id+", time: "+time+", spin: "+spin+"\ncurve: "+curve+"\nobject:"+object+"]"; - } - - /** - * Represents a bone in a Spriter SCML file. - * A bone holds a {@link #position}, {@link #scale}, an {@link #angle} and a {@link #pivot}. - * Bones are the only objects which can be used as a parent for other tweenable objects. - * @author Trixt0r - * - */ - public static class Bone{ - public final Point position, scale, pivot; - public float angle; - - public Bone(Point position, Point scale, Point pivot, float angle){ - this.position = new Point(position); - this.scale = new Point(scale); - this.angle = angle; - this.pivot = new Point(pivot); - } - - public Bone(Bone bone){ - this(bone.position, bone.scale, bone.pivot, bone.angle); - } - - public Bone(Point position){ - this(position, new Point(1f,1f), new Point(0f, 1f), 0f); - } - - public Bone(){ - this(new Point()); - } - - /** - * Returns whether this instance is a Spriter object or a bone. - * @return true if this instance is a Spriter bone - */ - public boolean isBone(){ - return !(this instanceof Object); - } - - /** - * Sets the values of this bone to the values of the given bone - * @param bone the bone - */ - public void set(Bone bone){ - this.set(bone.position, bone.angle, bone.scale, bone.pivot); - } - - /** - * Sets the given values for this bone. - * @param x the new position in x direction - * @param y the new position in y direction - * @param angle the new angle - * @param scaleX the new scale in x direction - * @param scaleY the new scale in y direction - * @param pivotX the new pivot in x direction - * @param pivotY the new pivot in y direction - */ - public void set(float x, float y, float angle, float scaleX, float scaleY, float pivotX, float pivotY){ - this.angle = angle; - this.position.set(x, y); - this.scale.set(scaleX, scaleY); - this.pivot.set(pivotX, pivotY); - } - - /** - * Sets the given values for this bone. - * @param position the new position - * @param angle the new angle - * @param scale the new scale - * @param pivot the new pivot - */ - public void set(Point position, float angle, Point scale, Point pivot){ - this.set(position.x, position.y, angle, scale.x, scale.y, pivot.x, pivot.y); - } - - /** - * Maps this bone from it's parent's coordinate system to a global one. - * @param parent the parent bone of this bone - */ - public void unmap(Bone parent){ - this.angle *= Math.signum(parent.scale.x)*Math.signum(parent.scale.y); - this.angle += parent.angle; - this.scale.scale(parent.scale); - this.position.scale(parent.scale); - this.position.rotate(parent.angle); - this.position.translate(parent.position); - } - - /** - * Maps this from it's global coordinate system to the parent's one. - * @param parent the parent bone of this bone - */ - public void map(Bone parent){ - this.position.translate(-parent.position.x, -parent.position.y); - this.position.rotate(-parent.angle); - this.position.scale(1f/parent.scale.x, 1f/parent.scale.y); - this.scale.scale(1f/parent.scale.x, 1f/parent.scale.y); - this.angle -=parent.angle; - this.angle *= Math.signum(parent.scale.x)*Math.signum(parent.scale.y); - } - - public String toString(){ - return getClass().getSimpleName()+"|position: "+position+", scale: "+scale+", angle: "+angle; - } - } - - - /** - * Represents an object in a Spriter SCML file. - * A file has the same properties as a bone with an alpha and file extension. - * @author Trixt0r - * - */ - public static class Object extends Bone{ - - public float alpha; - public final FileReference ref; - - public Object(Point position, Point scale, Point pivot, float angle, float alpha, FileReference ref) { - super(position, scale, pivot, angle); - this.alpha = alpha; - this.ref = ref; - } - - public Object(Point position) { - this(position, new Point(1f,1f), new Point(0f,1f), 0f, 1f, new FileReference(-1,-1)); - } - - public Object(Object object){ - this(object.position.copy(), object.scale.copy(),object.pivot.copy(),object.angle,object.alpha,object.ref); - } - - public Object(){ - this(new Point()); - } - - /** - * Sets the values of this object to the values of the given object. - * @param object the object - */ - public void set(Object object){ - this.set(object.position, object.angle, object.scale, object.pivot, object.alpha, object.ref); - } - - /** - * Sets the given values for this object. - * @param x the new position in x direction - * @param y the new position in y direction - * @param angle the new angle - * @param scaleX the new scale in x direction - * @param scaleY the new scale in y direction - * @param pivotX the new pivot in x direction - * @param pivotY the new pivot in y direction - * @param alpha the new alpha value - * @param folder the new folder index - * @param file the new file index - */ - public void set(float x, float y, float angle, float scaleX, float scaleY, float pivotX, float pivotY, float alpha, int folder, int file){ - super.set(x, y, angle, scaleX, scaleY, pivotX, pivotY); - this.alpha = alpha; - this.ref.folder = folder; - this.ref.file = file; - } - - /** - * Sets the given values for this object. - * @param position the new position - * @param angle the new angle - * @param scale the new scale - * @param pivot the new pivot - * @param alpha the new alpha value - * @param fileRef the new file reference - */ - public void set(Point position, float angle, Point scale, Point pivot, float alpha, FileReference fileRef){ - this.set(position.x, position.y, angle, scale.x, scale.y, pivot.x, pivot.y, alpha , fileRef.folder, fileRef.file); - } - - public String toString(){ - return super.toString()+", pivot: "+pivot+", alpha: "+alpha+", reference: "+ref; - } - - } - } + public static class Key { + + public final int id, spin; + public final Curve curve; + public int time; + public boolean active; + private Object object; + + public Key(int id, int time, int spin, Curve curve) { + this.id = id; + this.time = time; + this.spin = spin; + this.curve = curve; + } + + public Key(int id, int time, int spin) { + this(id, time, 1, new Curve()); + } + + public Key(int id, int time) { + this(id, time, 1); + } + + public Key(int id) { + this(id, 0); + } + + public void setObject(Object object) { + if (object == null) throw new IllegalArgumentException("object can not be null!"); + this.object = object; + } + + public Object object() { + return this.object; + } + + public String toString() { + return getClass().getSimpleName() + + "|[id: " + + id + + ", time: " + + time + + ", spin: " + + spin + + "\ncurve: " + + curve + + "\nobject:" + + object + + "]"; + } + + /** + * Represents a bone in a Spriter SCML file. A bone holds a {@link #position}, {@link + * #scale}, an {@link #angle} and a {@link #pivot}. Bones are the only objects which can be + * used as a parent for other tweenable objects. + * + * @author Trixt0r + */ + public static class Bone { + public final Point position, scale, pivot; + public float angle; + public Bone(Point position, Point scale, Point pivot, float angle) { + this.position = new Point(position); + this.scale = new Point(scale); + this.angle = angle; + this.pivot = new Point(pivot); + } + + public Bone(Bone bone) { + this(bone.position, bone.scale, bone.pivot, bone.angle); + } + + public Bone(Point position) { + this(position, new Point(1f, 1f), new Point(0f, 1f), 0f); + } + + public Bone() { + this(new Point()); + } + + /** + * Returns whether this instance is a Spriter object or a bone. + * + * @return true if this instance is a Spriter bone + */ + public boolean isBone() { + return !(this instanceof Object); + } + + /** + * Sets the values of this bone to the values of the given bone + * + * @param bone the bone + */ + public void set(Bone bone) { + this.set(bone.position, bone.angle, bone.scale, bone.pivot); + } + + /** + * Sets the given values for this bone. + * + * @param x the new position in x direction + * @param y the new position in y direction + * @param angle the new angle + * @param scaleX the new scale in x direction + * @param scaleY the new scale in y direction + * @param pivotX the new pivot in x direction + * @param pivotY the new pivot in y direction + */ + public void set( + float x, + float y, + float angle, + float scaleX, + float scaleY, + float pivotX, + float pivotY) { + this.angle = angle; + this.position.set(x, y); + this.scale.set(scaleX, scaleY); + this.pivot.set(pivotX, pivotY); + } + + /** + * Sets the given values for this bone. + * + * @param position the new position + * @param angle the new angle + * @param scale the new scale + * @param pivot the new pivot + */ + public void set(Point position, float angle, Point scale, Point pivot) { + this.set(position.x, position.y, angle, scale.x, scale.y, pivot.x, pivot.y); + } + + /** + * Maps this bone from it's parent's coordinate system to a global one. + * + * @param parent the parent bone of this bone + */ + public void unmap(Bone parent) { + this.angle *= Math.signum(parent.scale.x) * Math.signum(parent.scale.y); + this.angle += parent.angle; + this.scale.scale(parent.scale); + this.position.scale(parent.scale); + this.position.rotate(parent.angle); + this.position.translate(parent.position); + } + + /** + * Maps this from it's global coordinate system to the parent's one. + * + * @param parent the parent bone of this bone + */ + public void map(Bone parent) { + this.position.translate(-parent.position.x, -parent.position.y); + this.position.rotate(-parent.angle); + this.position.scale(1f / parent.scale.x, 1f / parent.scale.y); + this.scale.scale(1f / parent.scale.x, 1f / parent.scale.y); + this.angle -= parent.angle; + this.angle *= Math.signum(parent.scale.x) * Math.signum(parent.scale.y); + } + + public String toString() { + return getClass().getSimpleName() + + "|position: " + + position + + ", scale: " + + scale + + ", angle: " + + angle; + } + } + + /** + * Represents an object in a Spriter SCML file. A file has the same properties as a bone + * with an alpha and file extension. + * + * @author Trixt0r + */ + public static class Object extends Bone { + + public final FileReference ref; + public float alpha; + + public Object( + Point position, + Point scale, + Point pivot, + float angle, + float alpha, + FileReference ref) { + super(position, scale, pivot, angle); + this.alpha = alpha; + this.ref = ref; + } + + public Object(Point position) { + this( + position, + new Point(1f, 1f), + new Point(0f, 1f), + 0f, + 1f, + new FileReference(-1, -1)); + } + + public Object(Object object) { + this( + object.position.copy(), + object.scale.copy(), + object.pivot.copy(), + object.angle, + object.alpha, + object.ref); + } + + public Object() { + this(new Point()); + } + + /** + * Sets the values of this object to the values of the given object. + * + * @param object the object + */ + public void set(Object object) { + this.set( + object.position, + object.angle, + object.scale, + object.pivot, + object.alpha, + object.ref); + } + + /** + * Sets the given values for this object. + * + * @param x the new position in x direction + * @param y the new position in y direction + * @param angle the new angle + * @param scaleX the new scale in x direction + * @param scaleY the new scale in y direction + * @param pivotX the new pivot in x direction + * @param pivotY the new pivot in y direction + * @param alpha the new alpha value + * @param folder the new folder index + * @param file the new file index + */ + public void set( + float x, + float y, + float angle, + float scaleX, + float scaleY, + float pivotX, + float pivotY, + float alpha, + int folder, + int file) { + super.set(x, y, angle, scaleX, scaleY, pivotX, pivotY); + this.alpha = alpha; + this.ref.folder = folder; + this.ref.file = file; + } + + /** + * Sets the given values for this object. + * + * @param position the new position + * @param angle the new angle + * @param scale the new scale + * @param pivot the new pivot + * @param alpha the new alpha value + * @param fileRef the new file reference + */ + public void set( + Point position, + float angle, + Point scale, + Point pivot, + float alpha, + FileReference fileRef) { + this.set( + position.x, + position.y, + angle, + scale.x, + scale.y, + pivot.x, + pivot.y, + alpha, + fileRef.folder, + fileRef.file); + } + + public String toString() { + return super.toString() + + ", pivot: " + + pivot + + ", alpha: " + + alpha + + ", reference: " + + ref; + } + } + } } diff --git a/src/main/java/com/brashmonkey/spriter/TweenedAnimation.java b/src/main/java/com/brashmonkey/spriter/TweenedAnimation.java index 190ff6a..b3008cc 100644 --- a/src/main/java/com/brashmonkey/spriter/TweenedAnimation.java +++ b/src/main/java/com/brashmonkey/spriter/TweenedAnimation.java @@ -6,215 +6,247 @@ import com.brashmonkey.spriter.Timeline.Key.Object; /** - * A tweened animation is responsible for updating itself based on two given animations. - * The values of the two given animations will get interpolated and save in this animation. - * When tweening two animations, you have to make sure that they have the same structure. - * The best result is achieved if bones of two different animations are named in the same way. - * There are still issues with sprites, which are hard to resolve since Spriter does not save them in a useful order or naming convention. - * @author Trixt0r + * A tweened animation is responsible for updating itself based on two given animations. The values + * of the two given animations will get interpolated and save in this animation. When tweening two + * animations, you have to make sure that they have the same structure. The best result is achieved + * if bones of two different animations are named in the same way. There are still issues with + * sprites, which are hard to resolve since Spriter does not save them in a useful order or naming + * convention. * + * @author Trixt0r */ -public class TweenedAnimation extends Animation{ - - /** - * The weight of the interpolation. 0.5f is the default value. - * Values closer to 0.0f mean the first animation will have more influence. - */ - public float weight = .5f; - - /** - * Indicates when a sprite should be switched form the first animation object to the second one. - * A value closer to 0.0f means that the sprites of the second animation will be drawn. - */ - public float spriteThreshold = .5f; - - /** - * The curve which will tween the animations. - * The default type of the curve is {@link Curve.Type#Linear}. - */ - public final Curve curve; - - /** - * The entity the animations have be part of. - * Animations of two different entities can not be tweened. - */ - public final Entity entity; - private Animation anim1, anim2; - - /** - * The base animation an object or bone will get if it will not be tweened. - */ - public Animation baseAnimation; - BoneRef base = null; - - /** - * Indicates whether to tween sprites or not. Default value is false. - * Tweening sprites should be only enabled if they have exactly the same structure. - * If all animations are bone based and sprites only change their references it is not recommended to tween sprites. - */ - public boolean tweenSprites = false; - - /** - * Creates a tweened animation based on the given entity. - * @param entity the entity animations have to be part of - */ - public TweenedAnimation(Entity entity) { - super(new Mainline(0), -1, "__interpolatedAnimation__", 0, true, entity.getAnimationWithMostTimelines().timelines()); - this.entity = entity; - this.curve = new Curve(); - this.setUpTimelines(); - } - - /** - * Returns the current mainline key. - * @return the mainline key - */ - public Mainline.Key getCurrentKey(){ - return this.currentKey; - } - - @Override - public void update(int time, Bone root){ - super.currentKey = onFirstMainLine() ? anim1.currentKey: anim2.currentKey; - for(Timeline.Key timelineKey: this.unmappedTweenedKeys) - timelineKey.active = false; - if(base != null){//TODO: Sprites not working properly because of different timeline naming - Animation currentAnim = onFirstMainLine() ? anim1: anim2; - Animation baseAnim = baseAnimation == null ? (onFirstMainLine() ? anim1:anim2) : baseAnimation; - for(BoneRef ref: currentKey.boneRefs){ - Timeline timeline = baseAnim.getSimilarTimeline(currentAnim.getTimeline(ref.timeline)); - if(timeline == null) continue; - Timeline.Key key, mappedKey; - key = baseAnim.tweenedKeys[timeline.id]; - mappedKey = baseAnim.unmappedTweenedKeys[timeline.id]; - this.tweenedKeys[ref.timeline].active = key.active; - this.tweenedKeys[ref.timeline].object().set(key.object()); - this.unmappedTweenedKeys[ref.timeline].active = mappedKey.active; - this.unmapTimelineObject(ref.timeline, false,(ref.parent != null) ? - this.unmappedTweenedKeys[ref.parent.timeline].object(): root); - } - /*for(ObjectRef ref: baseAnim.currentKey.objectRefs){ - Timeline timeline = baseAnim.getTimeline(ref.timeline);//getSimilarTimeline(ref, tempTimelines); - if(timeline != null){ - //tempTimelines.addLast(timeline); - Timeline.Key key = baseAnim.tweenedKeys[timeline.id]; - Timeline.Key mappedKey = baseAnim.mappedTweenedKeys[timeline.id]; - Object obj = (Object) key.object(); - - this.tweenedKeys[ref.timeline].active = key.active; - ((Object)this.tweenedKeys[ref.timeline].object()).set(obj); - this.mappedTweenedKeys[ref.timeline].active = mappedKey.active; - this.unmapTimelineObject(ref.timeline, true,(ref.parent != null) ? - this.mappedTweenedKeys[ref.parent.timeline].object(): root); - } - }*/ - //tempTimelines.clear(); - } - - this.tweenBoneRefs(base, root); - for(ObjectRef ref: super.currentKey.objectRefs){ - //if(ref.parent == base) - this.update(ref, root, 0); - } +public class TweenedAnimation extends Animation { + + /** + * The curve which will tween the animations. The default type of the curve is {@link + * Curve.Type#Linear}. + */ + public final Curve curve; + /** + * The entity the animations have be part of. Animations of two different entities can not be + * tweened. + */ + public final Entity entity; + /** + * The weight of the interpolation. 0.5f is the default value. Values closer to 0.0f mean the + * first animation will have more influence. + */ + public float weight = .5f; + /** + * Indicates when a sprite should be switched form the first animation object to the second one. + * A value closer to 0.0f means that the sprites of the second animation will be drawn. + */ + public float spriteThreshold = .5f; + /** The base animation an object or bone will get if it will not be tweened. */ + public Animation baseAnimation; + /** + * Indicates whether to tween sprites or not. Default value is false. Tweening + * sprites should be only enabled if they have exactly the same structure. If all animations are + * bone based and sprites only change their references it is not recommended to tween sprites. + */ + public boolean tweenSprites = false; + + BoneRef base = null; + private Animation anim1, anim2; + + /** + * Creates a tweened animation based on the given entity. + * + * @param entity the entity animations have to be part of + */ + public TweenedAnimation(Entity entity) { + super( + new Mainline(0), + -1, + "__interpolatedAnimation__", + 0, + true, + entity.getAnimationWithMostTimelines().timelines()); + this.entity = entity; + this.curve = new Curve(); + this.setUpTimelines(); + } + + /** + * Returns the current mainline key. + * + * @return the mainline key + */ + public Mainline.Key getCurrentKey() { + return this.currentKey; } - - private void tweenBoneRefs(BoneRef base, Bone root){ - int startIndex = base == null ? -1 : base.id-1; - int length = super.currentKey.boneRefs.length; - for(int i = startIndex+1; i < length; i++){ - BoneRef ref = currentKey.boneRefs[i]; - if(base == ref || ref.parent == base) this.update(ref, root, 0); - if(base == ref.parent) this.tweenBoneRefs(ref, root); - } - } - - @Override - protected void update(BoneRef ref, Bone root, int time){ - boolean isObject = ref instanceof ObjectRef; - //Tween bone/object - Bone bone1 = null, bone2 = null, tweenTarget = null; - Timeline t1 = onFirstMainLine() ? anim1.getTimeline(ref.timeline) : anim1.getSimilarTimeline(anim2.getTimeline(ref.timeline)); - Timeline t2 = onFirstMainLine() ? anim2.getSimilarTimeline(t1) : anim2.getTimeline(ref.timeline); - Timeline targetTimeline = super.getTimeline(onFirstMainLine() ? t1.id:t2.id); - if(t1 != null) bone1 = anim1.tweenedKeys[t1.id].object(); - if(t2 != null) bone2 = anim2.tweenedKeys[t2.id].object(); - if(targetTimeline != null) tweenTarget = this.tweenedKeys[targetTimeline.id].object(); - if(isObject && (t2 == null || !tweenSprites)){ - if(!onFirstMainLine()) bone1 = bone2; - else bone2 = bone1; - } - if(bone2 != null && tweenTarget != null && bone1 != null){ - if(isObject) this.tweenObject((Object)bone1, (Object)bone2, (Object)tweenTarget, this.weight, this.curve); - else this.tweenBone(bone1, bone2, tweenTarget, this.weight, this.curve); - this.unmappedTweenedKeys[targetTimeline.id].active = true; - } - //Transform the bone relative to the parent bone or the root - if(this.unmappedTweenedKeys[ref.timeline].active){ - this.unmapTimelineObject(targetTimeline.id, isObject,(ref.parent != null) ? - this.unmappedTweenedKeys[ref.parent.timeline].object(): root); - } + + @Override + public void update(float time, Bone root) { + super.currentKey = onFirstMainLine() ? anim1.currentKey : anim2.currentKey; + for (Timeline.Key timelineKey : this.unmappedTweenedKeys) timelineKey.active = false; + if (base != null) { // TODO: Sprites not working properly because of different timeline + // naming + Animation currentAnim = onFirstMainLine() ? anim1 : anim2; + Animation baseAnim = + baseAnimation == null ? (onFirstMainLine() ? anim1 : anim2) : baseAnimation; + for (BoneRef ref : currentKey.boneRefs) { + Timeline timeline = + baseAnim.getSimilarTimeline(currentAnim.getTimeline(ref.timeline)); + if (timeline == null) continue; + Timeline.Key key, mappedKey; + key = baseAnim.tweenedKeys[timeline.id]; + mappedKey = baseAnim.unmappedTweenedKeys[timeline.id]; + this.tweenedKeys[ref.timeline].active = key.active; + this.tweenedKeys[ref.timeline].object().set(key.object()); + this.unmappedTweenedKeys[ref.timeline].active = mappedKey.active; + this.unmapTimelineObject( + ref.timeline, + false, + (ref.parent != null) + ? this.unmappedTweenedKeys[ref.parent.timeline].object() + : root); + } + /*for(ObjectRef ref: baseAnim.currentKey.objectRefs){ + Timeline timeline = baseAnim.getTimeline(ref.timeline);//getSimilarTimeline(ref, tempTimelines); + if(timeline != null){ + //tempTimelines.addLast(timeline); + Timeline.Key key = baseAnim.tweenedKeys[timeline.id]; + Timeline.Key mappedKey = baseAnim.mappedTweenedKeys[timeline.id]; + Object obj = (Object) key.object(); + + this.tweenedKeys[ref.timeline].active = key.active; + ((Object)this.tweenedKeys[ref.timeline].object()).set(obj); + this.mappedTweenedKeys[ref.timeline].active = mappedKey.active; + this.unmapTimelineObject(ref.timeline, true,(ref.parent != null) ? + this.mappedTweenedKeys[ref.parent.timeline].object(): root); + } + }*/ + // tempTimelines.clear(); + } + + this.tweenBoneRefs(base, root); + for (ObjectRef ref : super.currentKey.objectRefs) { + // if(ref.parent == base) + this.update(ref, root, 0); + } + } + + private void tweenBoneRefs(BoneRef base, Bone root) { + int startIndex = base == null ? -1 : base.id - 1; + int length = super.currentKey.boneRefs.length; + for (int i = startIndex + 1; i < length; i++) { + BoneRef ref = currentKey.boneRefs[i]; + if (base == ref || ref.parent == base) this.update(ref, root, 0); + if (base == ref.parent) this.tweenBoneRefs(ref, root); + } + } + + @Override + protected void update(BoneRef ref, Bone root, float time) { + boolean isObject = ref instanceof ObjectRef; + // Tween bone/object + Bone bone1 = null, bone2 = null, tweenTarget = null; + Timeline t1 = + onFirstMainLine() + ? anim1.getTimeline(ref.timeline) + : anim1.getSimilarTimeline(anim2.getTimeline(ref.timeline)); + Timeline t2 = + onFirstMainLine() ? anim2.getSimilarTimeline(t1) : anim2.getTimeline(ref.timeline); + Timeline targetTimeline = super.getTimeline(onFirstMainLine() ? t1.id : t2.id); + if (t1 != null) bone1 = anim1.tweenedKeys[t1.id].object(); + if (t2 != null) bone2 = anim2.tweenedKeys[t2.id].object(); + if (targetTimeline != null) tweenTarget = this.tweenedKeys[targetTimeline.id].object(); + if (isObject && (t2 == null || !tweenSprites)) { + if (!onFirstMainLine()) bone1 = bone2; + else bone2 = bone1; + } + if (bone2 != null && tweenTarget != null && bone1 != null) { + if (isObject) + this.tweenObject( + (Object) bone1, + (Object) bone2, + (Object) tweenTarget, + this.weight, + this.curve); + else this.tweenBone(bone1, bone2, tweenTarget, this.weight, this.curve); + this.unmappedTweenedKeys[targetTimeline.id].active = true; + } + // Transform the bone relative to the parent bone or the root + if (this.unmappedTweenedKeys[ref.timeline].active) { + this.unmapTimelineObject( + targetTimeline.id, + isObject, + (ref.parent != null) + ? this.unmappedTweenedKeys[ref.parent.timeline].object() + : root); + } } - - private void tweenBone(Bone bone1, Bone bone2, Bone target, float t, Curve curve){ - target.angle = curve.tweenAngle(bone1.angle, bone2.angle, t); - 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); - } - - private void tweenObject(Object object1, Object object2, Object target, float t, Curve curve){ - this.tweenBone(object1, object2, target, t, curve); - target.alpha = curve.tweenAngle(object1.alpha, object2.alpha, t); - target.ref.set(object1.ref); - } - - /** - * Returns whether the current mainline key is the one from the first animation or from the second one. - * @return true if the mainline key is the one from the first animation - */ - public boolean onFirstMainLine(){ - return this.weight < this.spriteThreshold; - } - - private void setUpTimelines(){ - Animation maxAnim = this.entity.getAnimationWithMostTimelines(); - int max = maxAnim.timelines(); - for(int i = 0; i < max; i++){ - Timeline t = new Timeline(i, maxAnim.getTimeline(i).name, maxAnim.getTimeline(i).objectInfo, 1); - addTimeline(t); - } - prepare(); - } - - /** - * Sets the animations to tween. - * @param animation1 the first animation - * @param animation2 the second animation - * @throws SpriterException if {@link #entity} does not contain one of the given animations. - */ - public void setAnimations(Animation animation1, Animation animation2){ - boolean areInterpolated = animation1 instanceof TweenedAnimation || animation2 instanceof TweenedAnimation; - if(animation1 == anim1 && animation2 == anim2) return; - if((!this.entity.containsAnimation(animation1) || !this.entity.containsAnimation(animation2)) && !areInterpolated) - throw new SpriterException("Both animations have to be part of the same entity!"); - this.anim1 = animation1; - this.anim2 = animation2; - } - - /** - * Returns the first animation. - * @return the first animation - */ - public Animation getFirstAnimation(){ - return this.anim1; - } - - /** - * Returns the second animation. - * @return the second animation - */ - public Animation getSecondAnimation(){ - return this.anim2; - } + private void tweenBone(Bone bone1, Bone bone2, Bone target, float t, Curve curve) { + target.angle = curve.tweenAngle(bone1.angle, bone2.angle, t); + 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); + } + + private void tweenObject(Object object1, Object object2, Object target, float t, Curve curve) { + this.tweenBone(object1, object2, target, t, curve); + target.alpha = curve.tweenAngle(object1.alpha, object2.alpha, t); + target.ref.set(object1.ref); + } + + /** + * Returns whether the current mainline key is the one from the first animation or from the + * second one. + * + * @return true if the mainline key is the one from the first animation + */ + public boolean onFirstMainLine() { + return this.weight < this.spriteThreshold; + } + + private void setUpTimelines() { + Animation maxAnim = this.entity.getAnimationWithMostTimelines(); + int max = maxAnim.timelines(); + for (int i = 0; i < max; i++) { + Timeline t = + new Timeline( + i, maxAnim.getTimeline(i).name, maxAnim.getTimeline(i).objectInfo, 1); + addTimeline(t); + } + prepare(); + } + + /** + * Sets the animations to tween. + * + * @param animation1 the first animation + * @param animation2 the second animation + * @throws SpriterException if {@link #entity} does not contain one of the given animations. + */ + public void setAnimations(Animation animation1, Animation animation2) { + boolean areInterpolated = + animation1 instanceof TweenedAnimation || animation2 instanceof TweenedAnimation; + if (animation1 == anim1 && animation2 == anim2) return; + if ((!this.entity.containsAnimation(animation1) + || !this.entity.containsAnimation(animation2)) + && !areInterpolated) + throw new SpriterException("Both animations have to be part of the same entity!"); + this.anim1 = animation1; + this.anim2 = animation2; + } + + /** + * Returns the first animation. + * + * @return the first animation + */ + public Animation getFirstAnimation() { + return this.anim1; + } + + /** + * Returns the second animation. + * + * @return the second animation + */ + public Animation getSecondAnimation() { + return this.anim2; + } } diff --git a/src/main/java/com/brashmonkey/spriter/XmlReader.java b/src/main/java/com/brashmonkey/spriter/XmlReader.java index e1faffa..c64a302 100644 --- a/src/main/java/com/brashmonkey/spriter/XmlReader.java +++ b/src/main/java/com/brashmonkey/spriter/XmlReader.java @@ -4,683 +4,752 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.Map.Entry; -/** Lightweight XML parser. Supports a subset of XML features: elements, attributes, text, predefined entities, CDATA, mixed - * content. Namespaces are parsed as part of the element or attribute name. Prologs and doctypes are ignored. Only 8-bit character - * encodings are supported. Input is assumed to be well formed.
+/** + * Lightweight XML parser. Supports a subset of XML features: elements, attributes, text, predefined + * entities, CDATA, mixed content. Namespaces are parsed as part of the element or attribute name. + * Prologs and doctypes are ignored. Only 8-bit character encodings are supported. Input is assumed + * to be well formed.
*
- * The default behavior is to parse the XML into a DOM. Extends this class and override methods to perform event driven parsing. - * When this is done, the parse methods will return null. - * @author Nathan Sweet */ + * The default behavior is to parse the XML into a DOM. Extends this class and override methods to + * perform event driven parsing. When this is done, the parse methods will return null. + * + * @author Nathan Sweet + */ public class XmlReader { - private final ArrayList elements = new ArrayList(8); - private Element root, current; - private final StringBuilder textBuffer = new StringBuilder(64); - - public Element parse (String xml) { - char[] data = xml.toCharArray(); - return parse(data, 0, data.length); - } - - public Element parse (Reader reader) throws IOException { - char[] data = new char[1024]; - int offset = 0; - while (true) { - int length = reader.read(data, offset, data.length - offset); - if (length == -1) break; - if (length == 0) { - char[] newData = new char[data.length * 2]; - System.arraycopy(data, 0, newData, 0, data.length); - data = newData; - } else - offset += length; - } - return parse(data, 0, offset); - } - - public Element parse (InputStream input) throws IOException { - return parse(new InputStreamReader(input, "ISO-8859-1")); - } - - @SuppressWarnings("unused") - public Element parse (char[] data, int offset, int length) { - int cs, p = offset, pe = length; - - int s = 0; - String attributeName = null; - boolean hasBody = false; - - // line 3 "XmlReader.java" - { - cs = xml_start; - } - - // line 7 "XmlReader.java" - { - int _klen; - int _trans = 0; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: - while (true) { - switch (_goto_targ) { - case 0: - if (p == pe) { - _goto_targ = 4; - continue _goto; - } - if (cs == 0) { - _goto_targ = 5; - continue _goto; - } - case 1: - _match: - do { - _keys = _xml_key_offsets[cs]; - _trans = _xml_index_offsets[cs]; - _klen = _xml_single_lengths[cs]; - if (_klen > 0) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if (_upper < _lower) break; - - _mid = _lower + ((_upper - _lower) >> 1); - if (data[p] < _xml_trans_keys[_mid]) - _upper = _mid - 1; - else if (data[p] > _xml_trans_keys[_mid]) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _xml_range_lengths[cs]; - if (_klen > 0) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen << 1) - 2; - while (true) { - if (_upper < _lower) break; - - _mid = _lower + (((_upper - _lower) >> 1) & ~1); - if (data[p] < _xml_trans_keys[_mid]) - _upper = _mid - 2; - else if (data[p] > _xml_trans_keys[_mid + 1]) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys) >> 1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - _trans = _xml_indicies[_trans]; - cs = _xml_trans_targs[_trans]; - - if (_xml_trans_actions[_trans] != 0) { - _acts = _xml_trans_actions[_trans]; - _nacts = (int)_xml_actions[_acts++]; - while (_nacts-- > 0) { - switch (_xml_actions[_acts++]) { - case 0: - // line 80 "XmlReader.rl" - { - s = p; - } - break; - case 1: - // line 81 "XmlReader.rl" - { - char c = data[s]; - if (c == '?' || c == '!') { - if (data[s + 1] == '[' && // - data[s + 2] == 'C' && // - data[s + 3] == 'D' && // - data[s + 4] == 'A' && // - data[s + 5] == 'T' && // - data[s + 6] == 'A' && // - data[s + 7] == '[') { - s += 8; - p = s + 2; - while (data[p - 2] != ']' || data[p - 1] != ']' || data[p] != '>') - p++; - text(new String(data, s, p - s - 2)); - } else - while (data[p] != '>') - p++; - { - cs = 15; - _goto_targ = 2; - if (true) continue _goto; - } - } - hasBody = true; - open(new String(data, s, p - s)); - } - break; - case 2: - // line 105 "XmlReader.rl" - { - hasBody = false; - close(); - { - cs = 15; - _goto_targ = 2; - if (true) continue _goto; - } - } - break; - case 3: - // line 110 "XmlReader.rl" - { - close(); - { - cs = 15; - _goto_targ = 2; - if (true) continue _goto; - } - } - break; - case 4: - // line 114 "XmlReader.rl" - { - if (hasBody) { - cs = 15; - _goto_targ = 2; - if (true) continue _goto; - } - } - break; - case 5: - // line 117 "XmlReader.rl" - { - attributeName = new String(data, s, p - s); - } - break; - case 6: - // line 120 "XmlReader.rl" - { - attribute(attributeName, new String(data, s, p - s)); - } - break; - case 7: - // line 123 "XmlReader.rl" - { - int end = p; - while (end != s) { - switch (data[end - 1]) { - case ' ': - case '\t': - case '\n': - case '\r': - end--; - continue; - } - break; - } - int current = s; - boolean entityFound = false; - while (current != end) { - if (data[current++] != '&') continue; - int entityStart = current; - while (current != end) { - if (data[current++] != ';') continue; - textBuffer.append(data, s, entityStart - s - 1); - String name = new String(data, entityStart, current - entityStart - 1); - String value = entity(name); - textBuffer.append(value != null ? value : name); - s = current; - entityFound = true; - break; - } - } - if (entityFound) { - if (s < end) textBuffer.append(data, s, end - s); - text(textBuffer.toString()); - textBuffer.setLength(0); - } else - text(new String(data, s, end - s)); - } - break; - // line 190 "XmlReader.java" - } - } - } - - case 2: - if (cs == 0) { - _goto_targ = 5; - continue _goto; - } - if (++p != pe) { - _goto_targ = 1; - continue _goto; - } - case 4: - case 5: - } - break; - } - } - - // line 170 "XmlReader.rl" - - if (p < pe) { - int lineNumber = 1; - for (int i = 0; i < p; i++) - if (data[i] == '\n') lineNumber++; - throw new SpriterException("Error parsing XML on line " + lineNumber + " near: " - + new String(data, p, Math.min(32, pe - p))); - } else if (elements.size() != 0) { - Element element = elements.get(elements.size()-1); - elements.clear(); - throw new SpriterException("Error parsing XML, unclosed element: " + element.getName()); - } - Element root = this.root; - this.root = null; - return root; - } - - // line 210 "XmlReader.java" - private static byte[] init__xml_actions_0 () { - return new byte[] {0, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 2, 0, 6, 2, 1, 4, 2, 2, 4}; - } - - private static final byte _xml_actions[] = init__xml_actions_0(); - - private static byte[] init__xml_key_offsets_0 () { - return new byte[] {0, 0, 4, 9, 14, 20, 26, 30, 35, 36, 37, 42, 46, 50, 51, 52, 56, 57, 62, 67, 73, 79, 83, 88, 89, 90, 95, - 99, 103, 104, 108, 109, 110, 111, 112, 115}; - } - - private static final byte _xml_key_offsets[] = init__xml_key_offsets_0(); - - private static char[] init__xml_trans_keys_0 () { - return new char[] {32, 60, 9, 13, 32, 47, 62, 9, 13, 32, 47, 62, 9, 13, 32, 47, 61, 62, 9, 13, 32, 47, 61, 62, 9, 13, 32, - 61, 9, 13, 32, 34, 39, 9, 13, 34, 34, 32, 47, 62, 9, 13, 32, 62, 9, 13, 32, 62, 9, 13, 39, 39, 32, 60, 9, 13, 60, 32, - 47, 62, 9, 13, 32, 47, 62, 9, 13, 32, 47, 61, 62, 9, 13, 32, 47, 61, 62, 9, 13, 32, 61, 9, 13, 32, 34, 39, 9, 13, 34, - 34, 32, 47, 62, 9, 13, 32, 62, 9, 13, 32, 62, 9, 13, 60, 32, 47, 9, 13, 62, 62, 39, 39, 32, 9, 13, 0}; - } - - private static final char _xml_trans_keys[] = init__xml_trans_keys_0(); - - private static byte[] init__xml_single_lengths_0 () { - return new byte[] {0, 2, 3, 3, 4, 4, 2, 3, 1, 1, 3, 2, 2, 1, 1, 2, 1, 3, 3, 4, 4, 2, 3, 1, 1, 3, 2, 2, 1, 2, 1, 1, 1, 1, 1, - 0}; - } - - private static final byte _xml_single_lengths[] = init__xml_single_lengths_0(); - - private static byte[] init__xml_range_lengths_0 () { - return new byte[] {0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, - 0}; - } - - private static final byte _xml_range_lengths[] = init__xml_range_lengths_0(); - - private static short[] init__xml_index_offsets_0 () { - return new short[] {0, 0, 4, 9, 14, 20, 26, 30, 35, 37, 39, 44, 48, 52, 54, 56, 60, 62, 67, 72, 78, 84, 88, 93, 95, 97, - 102, 106, 110, 112, 116, 118, 120, 122, 124, 127}; - } - - private static final short _xml_index_offsets[] = init__xml_index_offsets_0(); - - private static byte[] init__xml_indicies_0 () { - return new byte[] {0, 2, 0, 1, 2, 1, 1, 2, 3, 5, 6, 7, 5, 4, 9, 10, 1, 11, 9, 8, 13, 1, 14, 1, 13, 12, 15, 16, 15, 1, 16, - 17, 18, 16, 1, 20, 19, 22, 21, 9, 10, 11, 9, 1, 23, 24, 23, 1, 25, 11, 25, 1, 20, 26, 22, 27, 29, 30, 29, 28, 32, 31, - 30, 34, 1, 30, 33, 36, 37, 38, 36, 35, 40, 41, 1, 42, 40, 39, 44, 1, 45, 1, 44, 43, 46, 47, 46, 1, 47, 48, 49, 47, 1, - 51, 50, 53, 52, 40, 41, 42, 40, 1, 54, 55, 54, 1, 56, 42, 56, 1, 57, 1, 57, 34, 57, 1, 1, 58, 59, 58, 51, 60, 53, 61, - 62, 62, 1, 1, 0}; - } - - private static final byte _xml_indicies[] = init__xml_indicies_0(); - - private static byte[] init__xml_trans_targs_0 () { - return new byte[] {1, 0, 2, 3, 3, 4, 11, 34, 5, 4, 11, 34, 5, 6, 7, 6, 7, 8, 13, 9, 10, 9, 10, 12, 34, 12, 14, 14, 16, 15, - 17, 16, 17, 18, 30, 18, 19, 26, 28, 20, 19, 26, 28, 20, 21, 22, 21, 22, 23, 32, 24, 25, 24, 25, 27, 28, 27, 29, 31, 35, - 33, 33, 34}; - } - - private static final byte _xml_trans_targs[] = init__xml_trans_targs_0(); - - private static byte[] init__xml_trans_actions_0 () { - return new byte[] {0, 0, 0, 1, 0, 3, 3, 20, 1, 0, 0, 9, 0, 11, 11, 0, 0, 0, 0, 1, 17, 0, 13, 5, 23, 0, 1, 0, 1, 0, 0, 0, - 15, 1, 0, 0, 3, 3, 20, 1, 0, 0, 9, 0, 11, 11, 0, 0, 0, 0, 1, 17, 0, 13, 5, 23, 0, 0, 0, 7, 1, 0, 0}; - } - - private static final byte _xml_trans_actions[] = init__xml_trans_actions_0(); - - static final int xml_start = 1; - static final int xml_first_final = 34; - static final int xml_error = 0; - - static final int xml_en_elementBody = 15; - static final int xml_en_main = 1; - - // line 189 "XmlReader.rl" - - protected void open (String name) { - Element child = new Element(name, current); - Element parent = current; - if (parent != null) parent.addChild(child); - elements.add(child); - current = child; - } - - protected void attribute (String name, String value) { - current.setAttribute(name, value); - } - - protected String entity (String name) { - if (name.equals("lt")) return "<"; - if (name.equals("gt")) return ">"; - if (name.equals("amp")) return "&"; - if (name.equals("apos")) return "'"; - if (name.equals("quot")) return "\""; - return null; - } - - protected void text (String text) { - String existing = current.getText(); - current.setText(existing != null ? existing + text : text); - } - - protected void close () { - root = elements.get(elements.size()-1); - elements.remove(elements.size()-1); - current = elements.size() > 0 ? elements.get(elements.size()-1) : null; - } - - static public class Element { - private final String name; - private HashMap attributes; - private ArrayList children; - private String text; - private Element parent; - - public Element (String name, Element parent) { - this.name = name; - this.parent = parent; - } - - public String getName () { - return name; - } - - public HashMap getAttributes () { - return attributes; - } - - /** @throws RuntimeException if the attribute was not found. */ - public String getAttribute (String name) { - if (attributes == null) throw new RuntimeException("Element " + name + " doesn't have attribute: " + name); - String value = attributes.get(name); - if (value == null) throw new RuntimeException("Element " + name + " doesn't have attribute: " + name); - return value; - } - - public String getAttribute (String name, String defaultValue) { - if (attributes == null) return defaultValue; - String value = attributes.get(name); - if (value == null) return defaultValue; - return value; - } - - public void setAttribute (String name, String value) { - if (attributes == null) attributes = new HashMap(8); - attributes.put(name, value); - } - - public int getChildCount () { - if (children == null) return 0; - return children.size(); - } - - /** @throws RuntimeException if the element has no children. */ - public Element getChild (int i) { - if (children == null) throw new RuntimeException("Element has no children: " + name); - return children.get(i); - } - - public void addChild (Element element) { - if (children == null) children = new ArrayList(8); - children.add(element); - } - - public String getText () { - return text; - } - - public void setText (String text) { - this.text = text; - } - - public void removeChild (int index) { - if (children != null) children.remove(index); - } - - public void removeChild (Element child) { - if (children != null) children.remove(child); - } - - public void remove () { - parent.removeChild(this); - } - - public Element getParent () { - return parent; - } - - public String toString () { - return toString(""); - } - - public String toString (String indent) { - StringBuilder buffer = new StringBuilder(128); - buffer.append(indent); - buffer.append('<'); - buffer.append(name); - if (attributes != null) { - for (Entry entry : attributes.entrySet()) { - buffer.append(' '); - buffer.append(entry.getKey()); - buffer.append("=\""); - buffer.append(entry.getKey()); - buffer.append('\"'); - } - } - if (children == null && (text == null || text.length() == 0)) - buffer.append("/>"); - else { - buffer.append(">\n"); - String childIndent = indent + '\t'; - if (text != null && text.length() > 0) { - buffer.append(childIndent); - buffer.append(text); - buffer.append('\n'); - } - if (children != null) { - for (int i = 0; i < children.size(); i++) { - buffer.append(children.get(i).toString(childIndent)); - buffer.append('\n'); - } - } - buffer.append(indent); - buffer.append("'); - } - return buffer.toString(); - } - - /** @param name the name of the child {@link Element} - * @return the first child having the given name or null, does not recurse */ - public Element getChildByName (String name) { - if (children == null) return null; - for (int i = 0; i < children.size(); i++) { - Element element = children.get(i); - if (element.name.equals(name)) return element; - } - return null; - } - - /** @param name the name of the child {@link Element} - * @return the first child having the given name or null, recurses */ - public Element getChildByNameRecursive (String name) { - if (children == null) return null; - for (int i = 0; i < children.size(); i++) { - Element element = children.get(i); - if (element.name.equals(name)) return element; - Element found = element.getChildByNameRecursive(name); - if (found != null) return found; - } - return null; - } - - /** @param name the name of the children - * @return the children with the given name or an empty {@link ArrayList} */ - public ArrayList getChildrenByName (String name) { - ArrayList result = new ArrayList(); - if (children == null) return result; - for (int i = 0; i < children.size(); i++) { - Element child = children.get(i); - if (child.name.equals(name)) result.add(child); - } - return result; - } - - /** @param name the name of the children - * @return the children with the given name or an empty {@link ArrayList} */ - public ArrayList getChildrenByNameRecursively (String name) { - ArrayList result = new ArrayList(); - getChildrenByNameRecursively(name, result); - return result; - } - - private void getChildrenByNameRecursively (String name, ArrayList result) { - if (children == null) return; - for (int i = 0; i < children.size(); i++) { - Element child = children.get(i); - if (child.name.equals(name)) result.add(child); - child.getChildrenByNameRecursively(name, result); - } - } - - /** @throws RuntimeException if the attribute was not found. */ - public float getFloatAttribute (String name) { - return Float.parseFloat(getAttribute(name)); - } - - public float getFloatAttribute (String name, float defaultValue) { - String value = getAttribute(name, null); - if (value == null) return defaultValue; - return Float.parseFloat(value); - } - - /** @throws RuntimeException if the attribute was not found. */ - public int getIntAttribute (String name) { - return Integer.parseInt(getAttribute(name)); - } - - public int getIntAttribute (String name, int defaultValue) { - String value = getAttribute(name, null); - if (value == null) return defaultValue; - return Integer.parseInt(value); - } - - /** @throws RuntimeException if the attribute was not found. */ - public boolean getBooleanAttribute (String name) { - return Boolean.parseBoolean(getAttribute(name)); - } - - public boolean getBooleanAttribute (String name, boolean defaultValue) { - String value = getAttribute(name, null); - if (value == null) return defaultValue; - return Boolean.parseBoolean(value); - } - - /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. - * @throws RuntimeException if no attribute or child was not found. */ - public String get (String name) { - String value = get(name, null); - if (value == null) throw new RuntimeException("Element " + this.name + " doesn't have attribute or child: " + name); - return value; - } - - /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. - * @throws RuntimeException if no attribute or child was not found. */ - public String get (String name, String defaultValue) { - if (attributes != null) { - String value = attributes.get(name); - if (value != null) return value; - } - Element child = getChildByName(name); - if (child == null) return defaultValue; - String value = child.getText(); - if (value == null) return defaultValue; - return value; - } - - /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. - * @throws RuntimeException if no attribute or child was not found. */ - public int getInt (String name) { - String value = get(name, null); - if (value == null) throw new RuntimeException("Element " + this.name + " doesn't have attribute or child: " + name); - return Integer.parseInt(value); - } - - /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. - * @throws RuntimeException if no attribute or child was not found. */ - public int getInt (String name, int defaultValue) { - String value = get(name, null); - if (value == null) return defaultValue; - return Integer.parseInt(value); - } - - /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. - * @throws RuntimeException if no attribute or child was not found. */ - public float getFloat (String name) { - String value = get(name, null); - if (value == null) throw new RuntimeException("Element " + this.name + " doesn't have attribute or child: " + name); - return Float.parseFloat(value); - } - - /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. - * @throws RuntimeException if no attribute or child was not found. */ - public float getFloat (String name, float defaultValue) { - String value = get(name, null); - if (value == null) return defaultValue; - return Float.parseFloat(value); - } - - /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. - * @throws RuntimeException if no attribute or child was not found. */ - public boolean getBoolean (String name) { - String value = get(name, null); - if (value == null) throw new RuntimeException("Element " + this.name + " doesn't have attribute or child: " + name); - return Boolean.parseBoolean(value); - } - - /** Returns the attribute value with the specified name, or if no attribute is found, the text of a child with the name. - * @throws RuntimeException if no attribute or child was not found. */ - public boolean getBoolean (String name, boolean defaultValue) { - String value = get(name, null); - if (value == null) return defaultValue; - return Boolean.parseBoolean(value); - } - } + static final int xml_start = 1; + static final int xml_first_final = 34; + static final int xml_error = 0; + static final int xml_en_elementBody = 15; + static final int xml_en_main = 1; + private static final byte[] _xml_actions = init__xml_actions_0(); + private static final byte[] _xml_key_offsets = init__xml_key_offsets_0(); + private static final char[] _xml_trans_keys = init__xml_trans_keys_0(); + private static final byte[] _xml_single_lengths = init__xml_single_lengths_0(); + private static final byte[] _xml_range_lengths = init__xml_range_lengths_0(); + private static final short[] _xml_index_offsets = init__xml_index_offsets_0(); + private static final byte[] _xml_indicies = init__xml_indicies_0(); + private static final byte[] _xml_trans_targs = init__xml_trans_targs_0(); + private static final byte[] _xml_trans_actions = init__xml_trans_actions_0(); + private final ArrayList elements = new ArrayList(8); + private final StringBuilder textBuffer = new StringBuilder(64); + private Element root, current; + + // line 210 "XmlReader.java" + private static byte[] init__xml_actions_0() { + return new byte[] { + 0, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 2, 0, 6, 2, 1, 4, 2, 2, 4 + }; + } + + private static byte[] init__xml_key_offsets_0() { + return new byte[] { + 0, 0, 4, 9, 14, 20, 26, 30, 35, 36, 37, 42, 46, 50, 51, 52, 56, 57, 62, 67, 73, 79, 83, + 88, 89, 90, 95, 99, 103, 104, 108, 109, 110, 111, 112, 115 + }; + } + + private static char[] init__xml_trans_keys_0() { + return new char[] { + 32, 60, 9, 13, 32, 47, 62, 9, 13, 32, 47, 62, 9, 13, 32, 47, 61, 62, 9, 13, 32, 47, 61, + 62, 9, 13, 32, 61, 9, 13, 32, 34, 39, 9, 13, 34, 34, 32, 47, 62, 9, 13, 32, 62, 9, 13, + 32, 62, 9, 13, 39, 39, 32, 60, 9, 13, 60, 32, 47, 62, 9, 13, 32, 47, 62, 9, 13, 32, 47, + 61, 62, 9, 13, 32, 47, 61, 62, 9, 13, 32, 61, 9, 13, 32, 34, 39, 9, 13, 34, 34, 32, 47, + 62, 9, 13, 32, 62, 9, 13, 32, 62, 9, 13, 60, 32, 47, 9, 13, 62, 62, 39, 39, 32, 9, 13, 0 + }; + } + + private static byte[] init__xml_single_lengths_0() { + return new byte[] { + 0, 2, 3, 3, 4, 4, 2, 3, 1, 1, 3, 2, 2, 1, 1, 2, 1, 3, 3, 4, 4, 2, 3, 1, 1, 3, 2, 2, 1, + 2, 1, 1, 1, 1, 1, 0 + }; + } + + private static byte[] init__xml_range_lengths_0() { + return new byte[] { + 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 1, 0 + }; + } + + private static short[] init__xml_index_offsets_0() { + return new short[] { + 0, 0, 4, 9, 14, 20, 26, 30, 35, 37, 39, 44, 48, 52, 54, 56, 60, 62, 67, 72, 78, 84, 88, + 93, 95, 97, 102, 106, 110, 112, 116, 118, 120, 122, 124, 127 + }; + } + + private static byte[] init__xml_indicies_0() { + return new byte[] { + 0, 2, 0, 1, 2, 1, 1, 2, 3, 5, 6, 7, 5, 4, 9, 10, 1, 11, 9, 8, 13, 1, 14, 1, 13, 12, 15, + 16, 15, 1, 16, 17, 18, 16, 1, 20, 19, 22, 21, 9, 10, 11, 9, 1, 23, 24, 23, 1, 25, 11, + 25, 1, 20, 26, 22, 27, 29, 30, 29, 28, 32, 31, 30, 34, 1, 30, 33, 36, 37, 38, 36, 35, + 40, 41, 1, 42, 40, 39, 44, 1, 45, 1, 44, 43, 46, 47, 46, 1, 47, 48, 49, 47, 1, 51, 50, + 53, 52, 40, 41, 42, 40, 1, 54, 55, 54, 1, 56, 42, 56, 1, 57, 1, 57, 34, 57, 1, 1, 58, + 59, 58, 51, 60, 53, 61, 62, 62, 1, 1, 0 + }; + } + + private static byte[] init__xml_trans_targs_0() { + return new byte[] { + 1, 0, 2, 3, 3, 4, 11, 34, 5, 4, 11, 34, 5, 6, 7, 6, 7, 8, 13, 9, 10, 9, 10, 12, 34, 12, + 14, 14, 16, 15, 17, 16, 17, 18, 30, 18, 19, 26, 28, 20, 19, 26, 28, 20, 21, 22, 21, 22, + 23, 32, 24, 25, 24, 25, 27, 28, 27, 29, 31, 35, 33, 33, 34 + }; + } + + private static byte[] init__xml_trans_actions_0() { + return new byte[] { + 0, 0, 0, 1, 0, 3, 3, 20, 1, 0, 0, 9, 0, 11, 11, 0, 0, 0, 0, 1, 17, 0, 13, 5, 23, 0, 1, + 0, 1, 0, 0, 0, 15, 1, 0, 0, 3, 3, 20, 1, 0, 0, 9, 0, 11, 11, 0, 0, 0, 0, 1, 17, 0, 13, + 5, 23, 0, 0, 0, 7, 1, 0, 0 + }; + } + + public Element parse(String xml) { + char[] data = xml.toCharArray(); + return parse(data, 0, data.length); + } + + public Element parse(Reader reader) throws IOException { + char[] data = new char[1024]; + int offset = 0; + while (true) { + int length = reader.read(data, offset, data.length - offset); + if (length == -1) break; + if (length == 0) { + char[] newData = new char[data.length * 2]; + System.arraycopy(data, 0, newData, 0, data.length); + data = newData; + } else offset += length; + } + return parse(data, 0, offset); + } + + public Element parse(InputStream input) throws IOException { + return parse(new InputStreamReader(input, StandardCharsets.ISO_8859_1)); + } + + @SuppressWarnings("unused") + public Element parse(char[] data, int offset, int length) { + int cs, p = offset, pe = length; + + int s = 0; + String attributeName = null; + boolean hasBody = false; + + // line 3 "XmlReader.java" + { + cs = xml_start; + } + + // line 7 "XmlReader.java" + { + int _klen; + int _trans = 0; + int _acts; + int _nacts; + int _keys; + int _goto_targ = 0; + + _goto: + while (true) { + switch (_goto_targ) { + case 0: + if (p == pe) { + _goto_targ = 4; + continue _goto; + } + if (cs == 0) { + _goto_targ = 5; + continue _goto; + } + case 1: + _match: + do { + _keys = _xml_key_offsets[cs]; + _trans = _xml_index_offsets[cs]; + _klen = _xml_single_lengths[cs]; + if (_klen > 0) { + int _lower = _keys; + int _mid; + int _upper = _keys + _klen - 1; + while (true) { + if (_upper < _lower) break; + + _mid = _lower + ((_upper - _lower) >> 1); + if (data[p] < _xml_trans_keys[_mid]) _upper = _mid - 1; + else if (data[p] > _xml_trans_keys[_mid]) _lower = _mid + 1; + else { + _trans += (_mid - _keys); + break _match; + } + } + _keys += _klen; + _trans += _klen; + } + + _klen = _xml_range_lengths[cs]; + if (_klen > 0) { + int _lower = _keys; + int _mid; + int _upper = _keys + (_klen << 1) - 2; + while (true) { + if (_upper < _lower) break; + + _mid = _lower + (((_upper - _lower) >> 1) & ~1); + if (data[p] < _xml_trans_keys[_mid]) _upper = _mid - 2; + else if (data[p] > _xml_trans_keys[_mid + 1]) _lower = _mid + 2; + else { + _trans += ((_mid - _keys) >> 1); + break _match; + } + } + _trans += _klen; + } + } while (false); + + _trans = _xml_indicies[_trans]; + cs = _xml_trans_targs[_trans]; + + if (_xml_trans_actions[_trans] != 0) { + _acts = _xml_trans_actions[_trans]; + _nacts = (int) _xml_actions[_acts++]; + while (_nacts-- > 0) { + switch (_xml_actions[_acts++]) { + case 0: + // line 80 "XmlReader.rl" + { + s = p; + } + break; + case 1: + // line 81 "XmlReader.rl" + { + char c = data[s]; + if (c == '?' || c == '!') { + if (data[s + 1] == '[' + && // + data[s + 2] == 'C' + && // + data[s + 3] == 'D' + && // + data[s + 4] == 'A' + && // + data[s + 5] == 'T' + && // + data[s + 6] == 'A' + && // + data[s + 7] == '[') { + s += 8; + p = s + 2; + while (data[p - 2] != ']' + || data[p - 1] != ']' + || data[p] != '>') p++; + text(new String(data, s, p - s - 2)); + } else while (data[p] != '>') p++; + { + cs = 15; + _goto_targ = 2; + if (true) continue _goto; + } + } + hasBody = true; + open(new String(data, s, p - s)); + } + break; + case 2: + // line 105 "XmlReader.rl" + { + hasBody = false; + close(); + { + cs = 15; + _goto_targ = 2; + if (true) continue _goto; + } + } + break; + case 3: + // line 110 "XmlReader.rl" + { + close(); + { + cs = 15; + _goto_targ = 2; + if (true) continue _goto; + } + } + break; + case 4: + // line 114 "XmlReader.rl" + { + if (hasBody) { + cs = 15; + _goto_targ = 2; + if (true) continue _goto; + } + } + break; + case 5: + // line 117 "XmlReader.rl" + { + attributeName = new String(data, s, p - s); + } + break; + case 6: + // line 120 "XmlReader.rl" + { + attribute(attributeName, new String(data, s, p - s)); + } + break; + case 7: + // line 123 "XmlReader.rl" + { + int end = p; + while (end != s) { + switch (data[end - 1]) { + case ' ': + case '\t': + case '\n': + case '\r': + end--; + continue; + } + break; + } + int current = s; + boolean entityFound = false; + while (current != end) { + if (data[current++] != '&') continue; + int entityStart = current; + while (current != end) { + if (data[current++] != ';') continue; + textBuffer.append(data, s, entityStart - s - 1); + String name = + new String( + data, + entityStart, + current - entityStart - 1); + String value = entity(name); + textBuffer.append(value != null ? value : name); + s = current; + entityFound = true; + break; + } + } + if (entityFound) { + if (s < end) textBuffer.append(data, s, end - s); + text(textBuffer.toString()); + textBuffer.setLength(0); + } else text(new String(data, s, end - s)); + } + break; + // line 190 "XmlReader.java" + } + } + } + + case 2: + if (cs == 0) { + _goto_targ = 5; + continue _goto; + } + if (++p != pe) { + _goto_targ = 1; + continue _goto; + } + case 4: + case 5: + } + break; + } + } + + // line 170 "XmlReader.rl" + + if (p < pe) { + int lineNumber = 1; + for (int i = 0; i < p; i++) if (data[i] == '\n') lineNumber++; + throw new SpriterException( + "Error parsing XML on line " + + lineNumber + + " near: " + + new String(data, p, Math.min(32, pe - p))); + } else if (elements.size() != 0) { + Element element = elements.get(elements.size() - 1); + elements.clear(); + throw new SpriterException("Error parsing XML, unclosed element: " + element.getName()); + } + Element root = this.root; + this.root = null; + return root; + } + + // line 189 "XmlReader.rl" + + protected void open(String name) { + Element child = new Element(name, current); + Element parent = current; + if (parent != null) parent.addChild(child); + elements.add(child); + current = child; + } + + protected void attribute(String name, String value) { + current.setAttribute(name, value); + } + + protected String entity(String name) { + if (name.equals("lt")) return "<"; + if (name.equals("gt")) return ">"; + if (name.equals("amp")) return "&"; + if (name.equals("apos")) return "'"; + if (name.equals("quot")) return "\""; + return null; + } + + protected void text(String text) { + String existing = current.getText(); + current.setText(existing != null ? existing + text : text); + } + + protected void close() { + root = elements.get(elements.size() - 1); + elements.remove(elements.size() - 1); + current = elements.size() > 0 ? elements.get(elements.size() - 1) : null; + } + + public static class Element { + private final String name; + private HashMap attributes; + private ArrayList children; + private String text; + private Element parent; + + public Element(String name, Element parent) { + this.name = name; + this.parent = parent; + } + + public String getName() { + return name; + } + + public HashMap getAttributes() { + return attributes; + } + + /** @throws RuntimeException if the attribute was not found. */ + public String getAttribute(String name) { + if (attributes == null) + throw new RuntimeException("Element " + name + " doesn't have attribute: " + name); + String value = attributes.get(name); + if (value == null) + throw new RuntimeException("Element " + name + " doesn't have attribute: " + name); + return value; + } + + public String getAttribute(String name, String defaultValue) { + if (attributes == null) return defaultValue; + String value = attributes.get(name); + if (value == null) return defaultValue; + return value; + } + + public void setAttribute(String name, String value) { + if (attributes == null) attributes = new HashMap(8); + attributes.put(name, value); + } + + public int getChildCount() { + if (children == null) return 0; + return children.size(); + } + + /** @throws RuntimeException if the element has no children. */ + public Element getChild(int i) { + if (children == null) throw new RuntimeException("Element has no children: " + name); + return children.get(i); + } + + public void addChild(Element element) { + if (children == null) children = new ArrayList(8); + children.add(element); + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public void removeChild(int index) { + if (children != null) children.remove(index); + } + + public void removeChild(Element child) { + if (children != null) children.remove(child); + } + + public void remove() { + parent.removeChild(this); + } + + public Element getParent() { + return parent; + } + + public String toString() { + return toString(""); + } + + public String toString(String indent) { + StringBuilder buffer = new StringBuilder(128); + buffer.append(indent); + buffer.append('<'); + buffer.append(name); + if (attributes != null) { + for (Entry entry : attributes.entrySet()) { + buffer.append(' '); + buffer.append(entry.getKey()); + buffer.append("=\""); + buffer.append(entry.getKey()); + buffer.append('\"'); + } + } + if (children == null && (text == null || text.length() == 0)) buffer.append("/>"); + else { + buffer.append(">\n"); + String childIndent = indent + '\t'; + if (text != null && text.length() > 0) { + buffer.append(childIndent); + buffer.append(text); + buffer.append('\n'); + } + if (children != null) { + for (int i = 0; i < children.size(); i++) { + buffer.append(children.get(i).toString(childIndent)); + buffer.append('\n'); + } + } + buffer.append(indent); + buffer.append("'); + } + return buffer.toString(); + } + + /** + * @param name the name of the child {@link Element} + * @return the first child having the given name or null, does not recurse + */ + public Element getChildByName(String name) { + if (children == null) return null; + for (int i = 0; i < children.size(); i++) { + Element element = children.get(i); + if (element.name.equals(name)) return element; + } + return null; + } + + /** + * @param name the name of the child {@link Element} + * @return the first child having the given name or null, recurses + */ + public Element getChildByNameRecursive(String name) { + if (children == null) return null; + for (int i = 0; i < children.size(); i++) { + Element element = children.get(i); + if (element.name.equals(name)) return element; + Element found = element.getChildByNameRecursive(name); + if (found != null) return found; + } + return null; + } + + /** + * @param name the name of the children + * @return the children with the given name or an empty {@link ArrayList} + */ + public ArrayList getChildrenByName(String name) { + ArrayList result = new ArrayList(); + if (children == null) return result; + for (int i = 0; i < children.size(); i++) { + Element child = children.get(i); + if (child.name.equals(name)) result.add(child); + } + return result; + } + + /** + * @param name the name of the children + * @return the children with the given name or an empty {@link ArrayList} + */ + public ArrayList getChildrenByNameRecursively(String name) { + ArrayList result = new ArrayList(); + getChildrenByNameRecursively(name, result); + return result; + } + + private void getChildrenByNameRecursively(String name, ArrayList result) { + if (children == null) return; + for (int i = 0; i < children.size(); i++) { + Element child = children.get(i); + if (child.name.equals(name)) result.add(child); + child.getChildrenByNameRecursively(name, result); + } + } + + /** @throws RuntimeException if the attribute was not found. */ + public float getFloatAttribute(String name) { + return Float.parseFloat(getAttribute(name)); + } + + public float getFloatAttribute(String name, float defaultValue) { + String value = getAttribute(name, null); + if (value == null) return defaultValue; + return Float.parseFloat(value); + } + + /** @throws RuntimeException if the attribute was not found. */ + public int getIntAttribute(String name) { + return Integer.parseInt(getAttribute(name)); + } + + public int getIntAttribute(String name, int defaultValue) { + String value = getAttribute(name, null); + if (value == null) return defaultValue; + return Integer.parseInt(value); + } + + /** @throws RuntimeException if the attribute was not found. */ + public boolean getBooleanAttribute(String name) { + return Boolean.parseBoolean(getAttribute(name)); + } + + public boolean getBooleanAttribute(String name, boolean defaultValue) { + String value = getAttribute(name, null); + if (value == null) return defaultValue; + return Boolean.parseBoolean(value); + } + + /** + * Returns the attribute value with the specified name, or if no attribute is found, the + * text of a child with the name. + * + * @throws RuntimeException if no attribute or child was not found. + */ + public String get(String name) { + String value = get(name, null); + if (value == null) + throw new RuntimeException( + "Element " + this.name + " doesn't have attribute or child: " + name); + return value; + } + + /** + * Returns the attribute value with the specified name, or if no attribute is found, the + * text of a child with the name. + * + * @throws RuntimeException if no attribute or child was not found. + */ + public String get(String name, String defaultValue) { + if (attributes != null) { + String value = attributes.get(name); + if (value != null) return value; + } + Element child = getChildByName(name); + if (child == null) return defaultValue; + String value = child.getText(); + if (value == null) return defaultValue; + return value; + } + + /** + * Returns the attribute value with the specified name, or if no attribute is found, the + * text of a child with the name. + * + * @throws RuntimeException if no attribute or child was not found. + */ + public int getInt(String name) { + String value = get(name, null); + if (value == null) + throw new RuntimeException( + "Element " + this.name + " doesn't have attribute or child: " + name); + return Integer.parseInt(value); + } + + /** + * Returns the attribute value with the specified name, or if no attribute is found, the + * text of a child with the name. + * + * @throws RuntimeException if no attribute or child was not found. + */ + public int getInt(String name, int defaultValue) { + String value = get(name, null); + if (value == null) return defaultValue; + return Integer.parseInt(value); + } + + /** + * Returns the attribute value with the specified name, or if no attribute is found, the + * text of a child with the name. + * + * @throws RuntimeException if no attribute or child was not found. + */ + public float getFloat(String name) { + String value = get(name, null); + if (value == null) + throw new RuntimeException( + "Element " + this.name + " doesn't have attribute or child: " + name); + return Float.parseFloat(value); + } + + /** + * Returns the attribute value with the specified name, or if no attribute is found, the + * text of a child with the name. + * + * @throws RuntimeException if no attribute or child was not found. + */ + public float getFloat(String name, float defaultValue) { + String value = get(name, null); + if (value == null) return defaultValue; + return Float.parseFloat(value); + } + + /** + * Returns the attribute value with the specified name, or if no attribute is found, the + * text of a child with the name. + * + * @throws RuntimeException if no attribute or child was not found. + */ + public boolean getBoolean(String name) { + String value = get(name, null); + if (value == null) + throw new RuntimeException( + "Element " + this.name + " doesn't have attribute or child: " + name); + return Boolean.parseBoolean(value); + } + + /** + * Returns the attribute value with the specified name, or if no attribute is found, the + * text of a child with the name. + * + * @throws RuntimeException if no attribute or child was not found. + */ + public boolean getBoolean(String name, boolean defaultValue) { + String value = get(name, null); + if (value == null) return defaultValue; + return Boolean.parseBoolean(value); + } + } }