package com.indyforge.twod.engine.graphics.rendering.scenegraph;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import com.indyforge.twod.engine.graphics.rendering.scenegraph.math.Vector2f;
import com.indyforge.twod.engine.graphics.sprite.Sprite;
import com.indyforge.twod.engine.resources.Resource;
import com.indyforge.twod.engine.resources.assets.Asset;
import com.indyforge.twod.engine.resources.assets.AssetManager;
import com.indyforge.twod.engine.util.iteration.FilteredIterator;
import com.indyforge.twod.engine.util.iteration.IterationRoutines;
import com.indyforge.twod.engine.util.iteration.TypeFilter;
/**
*
* @author Christopher Probst
* @see Entity
* @see Scene
*/
public class GraphicsEntity extends Entity {
public enum GraphicsEvent {
Render
}
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Only accepts graphics entities.
*/
public static final TypeFilter TYPE_FILTER = new TypeFilter(
GraphicsEntity.class, true);
/*
* The position of this entity stored as Vector2f.
*/
private Vector2f position = Vector2f.zero(),
/*
* The scale of this entity.
*/
scale = new Vector2f(1, 1);
/*
* The last affine origin transform.
*/
private AffineTransform lastOriginWorldTransform = new AffineTransform(),
lastWorldTransform = new AffineTransform(),
lastTransform = new AffineTransform();
/*
* The rotation of this entity.
*/
private float rotation = 0;
/*
* Tells whether or not this entity is visible.
*/
private boolean visible = true;
/*
* (non-Javadoc)
*
* @see
* com.indyforge.twod.engine.graphics.rendering.scenegraph.Entity#onEvent
* (com.indyforge.twod.engine.graphics.rendering.scenegraph.Entity,
* java.lang.Object, java.lang.Object[])
*/
@Override
protected void onEvent(Entity source, Object event, Object... params) {
// Process other events...
super.onEvent(source, event, params);
// Check
if (event instanceof GraphicsEvent) {
switch ((GraphicsEvent) event) {
case Render:
/*
* At first we have to update the transforms.
*/
updateTransforms();
/*
* Do the rendering.
*/
if (visible) {
// Convert and create copy
Graphics2D original = (Graphics2D) ((Graphics2D) params[0])
.create();
try {
// Create new copy
Graphics2D transformed = (Graphics2D) original.create();
try {
// Use the last world transform
transformed.transform(lastWorldTransform);
// Render this entity
onRender(original, transformed);
} finally {
// Dispose
transformed.dispose();
}
} finally {
// Dispose
original.dispose();
}
}
break;
}
}
}
/**
* OVERRIDE FOR CUSTOM RENDER BEHAVIOUR.
*
* This method gets called every frame to render this entity if the visible
* flag is set to true.
*
* @param original
* A graphics context you can render to. There is no
* transformation applied yet.
* @param transformed
* A graphics context you can render to. The entity has already
* applied transformation, so the context origin is your
* position.
*
*/
protected void onRender(Graphics2D original, Graphics2D transformed) {
}
public GraphicsEntity() {
// Register the default entity events
events().put(GraphicsEvent.Render, iterableChildren(true, true));
}
/**
* @return the next parent scene, or this if this entity is already a scene
* or null if there is no scene at all.
*/
public Scene findScene() {
return (Scene) findParent(new TypeFilter(Scene.class), true);
}
/**
* @return the scene processor or null.
*/
public SceneProcessor findSceneProcessor() {
Scene scene = findScene();
return scene != null ? scene.processor : null;
}
/**
* @return the visible flag.
*/
public boolean isVisible() {
return visible;
}
/**
* Updates all transforms of this entity. You should only call this method
* if you know what you are doing. Usually this method is called every frame
* before rendering.
*
* @return this for chaining.
*/
public GraphicsEntity updateTransforms() {
// Set to identity
lastOriginWorldTransform.setToIdentity();
lastWorldTransform.setToIdentity();
lastTransform.setToIdentity();
// Apply local transform
lastTransform.translate(position.x, position.y);
lastTransform.rotate(rotation);
lastTransform.scale(scale.x, scale.y);
// Try to find the next graphics parent
GraphicsEntity result = (GraphicsEntity) IterationRoutines
.next(new FilteredIterator<Entity>(TYPE_FILTER,
parentIterator(false)));
// If the parent exists
if (result != null) {
// Set the last world transform
lastOriginWorldTransform.setTransform(result.lastWorldTransform());
// Copy from origin
lastWorldTransform.setTransform(lastOriginWorldTransform);
}
// Concatenate with local transform
lastWorldTransform.concatenate(lastTransform);
return this;
}
/**
* Sets the visible flag. True means this entity is rendered. This flag does
* not influence the rendering of the children, too.
*
* @param visible
* @return this for chaining.
*/
public GraphicsEntity visible(boolean visible) {
this.visible = visible;
return this;
}
/**
* @return the relative scale of this entity as vector.
*/
public Vector2f scale() {
return scale;
}
/**
* Sets the relative scale of this entity.
*
* @param scale
* The vector you want to use as scale now.
* @return this for chaining.
*/
public GraphicsEntity scale(Vector2f scale) {
if (scale == null) {
throw new NullPointerException("scale");
}
this.scale = scale;
return this;
}
/**
* @return the relative rotation of this entity in radians.
*/
public float rotation() {
return rotation;
}
/**
* Sets the relative rotation in radians.
*
* @param rotation
* The rotation value stored as float you want to set.
* @return this for chaining.
*/
public GraphicsEntity rotation(float rotation) {
this.rotation = rotation;
return this;
}
/**
* @return the relative position of this entity.
*/
public Vector2f position() {
return position;
}
/**
* @return the last affine origin world transform.
* @see GraphicsEntity#updateTransforms()
*/
public AffineTransform lastOriginWorldTransform() {
return lastOriginWorldTransform;
}
/**
* @return the last affine world transform.
* @see GraphicsEntity#updateTransforms()
*/
public AffineTransform lastWorldTransform() {
return lastWorldTransform;
}
/**
* @return the last affine transform.
* @see GraphicsEntity#updateTransforms()
*/
public AffineTransform lastTransform() {
return lastTransform;
}
/**
* Sets the relative position of this entity.
*
* @param position
* The vector you want to use as position now.
* @return this for chaining.
*/
public GraphicsEntity position(Vector2f position) {
if (position == null) {
throw new NullPointerException("position");
}
this.position = position;
return this;
}
/**
* @param key
* The key of the sprite you want to lookup.
* @return the sprite of the given key or null.
*/
public Sprite spriteProp(Object key) {
return prop(key, Sprite.class);
}
/**
* @param key
* The key of the image you want to lookup.
* @return the image of the given key or null.
*/
@SuppressWarnings("unchecked")
public Resource<? extends Image> imageProp(Object key) {
return (Resource<? extends Image>) prop(key, Asset.class);
}
/**
* Renders this entity and all children to an image. This method uses the
* visible/childrenVisible flags to determine what to render.
*
* @param destination
* The image you want to render to.
* @return the given destination image.
*/
public final Image renderTo(Image destination) {
if (destination == null) {
throw new NullPointerException("destination");
} else if (!AssetManager.isHeadless()) {
// Get the graphics context
Graphics graphics = destination.getGraphics();
try {
if (graphics instanceof Graphics2D) {
// Just render to image
render((Graphics2D) graphics);
} else {
throw new IllegalArgumentException(
"The image must return a "
+ "Graphics2D object when calling getGraphics().");
}
} finally {
// Dispose
graphics.dispose();
}
}
return destination;
}
/**
* Renders this entity and all children.
*
* @param original
* The original graphics context.
* @return this for chaining.
*/
public GraphicsEntity render(Graphics2D original) {
return render(null, original);
}
/**
* Renders this entity and all children using the given filter.
*
* @param EntityFilter
* The entity filter or null.
* @param original
* The original graphics context.
* @return this for chaining.
*/
public GraphicsEntity render(EntityFilter entityFilter, Graphics2D original) {
// Not headless ?
if (!AssetManager.isHeadless()) {
// Fire event for all children
fireEvent(entityFilter, GraphicsEvent.Render, original);
}
return this;
}
}