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;
/**
* 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
*
* @param <R> The backend specific resource. In general such a resource is called "sprite", "texture" or "image".
*/
public abstract class Drawer<R> {
/**
* The radius of a point for debug drawing purposes.
*/
public float pointRadius = 5f;
protected Loader<R> loader;
/**
* Creates a new drawer based on the given loader.
* @param loader the loader containing resources
*/
public Drawer(Loader<R> loader){
this.loader = loader;
}
/**
* Sets the loader of this drawer.
* @param loader the loader containing resources
* @throws SpriterException if the loader is <code>null</code>
*/
public void setLoader(Loader<R> 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<Bone> 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<Bone> 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<Object> 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<Object> 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 maps 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 maps the character map to draw
*/
public void draw(Iterator<Timeline.Key.Object> 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);
}