All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.brunothg.game.engine.d2.object.SceneObject Maven / Gradle / Ivy

The newest version!
package com.github.brunothg.game.engine.d2.object;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;

import com.github.brunothg.game.engine.d2.commons.Point;
import com.github.brunothg.game.engine.d2.commons.RenderingOptions;
import com.github.brunothg.game.engine.d2.commons.Size;

/**
 * Helpful class for managing Objects in a scene. Provides methods for collision
 * handling.
 * 
 * @author Marvin Bruns
 *
 */
public abstract class SceneObject {

	protected final static Point ORIGIN_TOP_LEFT = new Point(0, 0);

	private Point position = ORIGIN_TOP_LEFT;
	private Size size = new Size(0, 0);

	private boolean drawBoundingBox;

	private RenderingOptions renderingOptions;

	public SceneObject() {

		setPosition(0, 0);
		setSize(0, 0);
	}

	/**
	 * Paint this SceneObject. This method maybe called for collision testing with
	 * an elapsed time of zero. For correct collision the {@link SceneObject} it is
	 * important, that the object is drawn in it's actual state.
	 * 
	 * @param g
	 *            Graphics Object for painting
	 * @param elapsedTime
	 *            Elapsed time since the last call to this method
	 */
	protected abstract void paint(Graphics2D g, long elapsedTime);

	/**
	 * Paints this SceneObject and applies the actual {@link RenderingOptions}.
	 * 
	 * @param g
	 *            Graphics Object for painting
	 * @param elapsedTime
	 *            Elapsed time since the last call to this method
	 * @deprecated Use {@link #paintOnScene(Graphics2D, Size, long)} instead
	 */
	@Deprecated
	public void paintOnScene(Graphics2D g, long elapsedTime) {
		paintAbsoluteOnScene(g, elapsedTime);
	}

	/**
	 * Paints this SceneObject and applies the actual {@link RenderingOptions}.
	 * 
	 * @param g
	 *            Graphics Object for painting
	 * @param elapsedTime
	 *            Elapsed time since the last call to this method
	 * @param width
	 *            Scene width
	 * @param height
	 *            scene height
	 * @see #paintOnScene(Graphics2D, Size, long)
	 */
	public void paintOnScene(Graphics2D g, int width, int height, long elapsedTime) {
		paintOnScene(g, new Size(width, height), elapsedTime);
	}

	/**
	 * Paints this SceneObject and applies the actual {@link RenderingOptions}.
	 * 
	 * @param g
	 *            Graphics Object for painting
	 * @param sceneSize
	 *            Scene size
	 * @param elapsedTime
	 *            Elapsed time since the last call to this method
	 * @see #paintOnScene(Graphics2D, Size, long)
	 */
	public void paintOnScene(Graphics2D g, Size sceneSize, long elapsedTime) {
		paintAbsoluteOnScene(g, elapsedTime);
	}

	/**
	 * Paint this SceneObject using absolute coordinates.
	 * 
	 * @param g
	 *            Graphics Object for painting
	 * @param elapsedTime
	 *            Elapsed time since the last call to this method
	 */
	protected void paintAbsoluteOnScene(Graphics2D g, long elapsedTime) {
		Point topLeftPosition = getTopLeftPosition();
		int x_topLeft = topLeftPosition.getX();
		int y_topLeft = topLeftPosition.getY();

		int width = getWidth();
		int height = getHeight();

		paintOnScene(g, new Point(x_topLeft, y_topLeft), new Size(width, height), elapsedTime);
	}

	/**
	 * Paint on scene with given size and position
	 * 
	 * @param g
	 *            Graphics Object for painting
	 * @param positionTopLeft
	 *            Top left position of object
	 * @param size
	 *            Absolute size of object
	 * @param elapsedTime
	 *            Elapsed time since the last call to this method
	 */
	protected void paintOnScene(Graphics2D g, Point positionTopLeft, Size size, long elapsedTime) {
		Graphics2D g2d = (Graphics2D) g.create(positionTopLeft.getX(), positionTopLeft.getY(), size.getWidth(),
				size.getHeight());
		if (renderingOptions != null) {
			renderingOptions.apply(g2d);
		}
		paint(g2d, elapsedTime);

		if (isDrawBoundingBox()) {
			g2d.setColor(Color.BLACK);
			g2d.drawRect(0, 0, size.getWidth() - 1, size.getHeight() - 1);
		}

		g2d.dispose();
	}

	/**
	 * The position of this {@link SceneObject}'s top left corner
	 * 
	 * @return The Position of this SceneObject's top left corner
	 */
	public Point getTopLeftPosition() {
		return new Point(getPreciseX() - getOrigin().getPreciseX(), getPreciseY() - getOrigin().getPreciseY());
	}

	/**
	 * Set the position of this {@link SceneObject}'s top left corner
	 */
	public void setTopLeftPosition(Point p) {
		setPosition(
				new Point(p.getPreciseX() + getOrigin().getPreciseX(), p.getPreciseY() + getOrigin().getPreciseY()));
	}

	/**
	 * Get the position of this {@link SceneObject}
	 * 
	 * @return The position of this ScreenObject
	 */
	public Point getPosition() {
		return position;
	}

	/**
	 * The Y-Coordinate of this {@link SceneObject}
	 * 
	 * @see #getPosition()
	 * @return The Y-Coordinate of this ScreenObject
	 */
	public int getY() {
		return getPosition().getY();
	}

	/**
	 * The precise Y-Coordinate of this {@link SceneObject}
	 * 
	 * @see #getPosition()
	 * @return The Y-Coordinate of this ScreenObject
	 */
	public double getPreciseY() {
		return getPosition().getPreciseY();
	}

	/**
	 * The X-Coordinate of this {@link SceneObject}
	 * 
	 * @see #getPosition()
	 * @return The X-Coordinate of this ScreenObject
	 */
	public int getX() {
		return getPosition().getX();
	}

	/**
	 * The precise X-Coordinate of this {@link SceneObject}
	 * 
	 * @see #getPosition()
	 * @return The X-Coordinate of this ScreenObject
	 */
	public double getPreciseX() {
		return getPosition().getPreciseX();
	}

	/**
	 * Set the position of this {@link SceneObject}
	 * 
	 * @param position
	 *            The Position of this ScreenObject
	 */
	public void setPosition(Point position) {
		if (position == null) {
			throw new NullPointerException("This SceneObject must have a position");
		}

		this.position = position;
	}

	/**
	 * Set the position of this {@link SceneObject}
	 * 
	 * @see #setPosition(Point)
	 * @param x
	 *            The X-Coordinate of this ScreenObject
	 * @param y
	 *            The Y-Coordinate of this ScreenObject
	 */
	public void setPosition(int x, int y) {
		setPosition(new Point(x, y));
	}

	/**
	 * The origin of this {@link SceneObject}. This object's position will be
	 * translated to it's origin. The default implementation returns
	 * {@link #ORIGIN_TOP_LEFT}
	 * 
	 * @return The origin of this ScreenObject
	 */
	public Point getOrigin() {

		return ORIGIN_TOP_LEFT;
	}

	/**
	 * The size of this {@link SceneObject}
	 * 
	 * @return The Size of this ScreenObject
	 */
	public Size getSize() {
		return size;
	}

	/**
	 * The height of this {@link SceneObject}
	 * 
	 * @see #getSize()
	 * @return The height of this ScreenObject
	 */
	public int getHeight() {
		return getSize().getHeight();
	}

	/**
	 * The precise height of this {@link SceneObject}
	 * 
	 * @see #getSize()
	 * @return The height of this ScreenObject
	 */
	public double getPreciseHeight() {
		return getSize().getPreciseHeight();
	}

	/**
	 * The width of this {@link SceneObject}
	 * 
	 * @see #getSize()
	 * @return The width of this ScreenObject
	 */
	public int getWidth() {
		return getSize().getWidth();
	}

	/**
	 * The precise width of this {@link SceneObject}
	 * 
	 * @see #getSize()
	 * @return The width of this ScreenObject
	 */
	public double getPreciseWidth() {
		return getSize().getPreciseWidth();
	}

	/**
	 * Set the size of this {@link SceneObject}
	 * 
	 * @param size
	 *            Size of this ScreenObject
	 */
	public void setSize(Size size) {
		if (size == null || size.getHeight() < 0 || size.getWidth() < 0) {
			throw new NullPointerException("This SceneObject must have a positive size");
		}

		this.size = size;
	}

	/**
	 * @see #setSize(Size)
	 * @param width
	 *            Width of this ScreenObject
	 * @param height
	 *            Height of this ScreenObject
	 */
	public void setSize(int width, int height) {
		setSize(new Size(width, height));
	}

	/**
	 * Check if the bounding box of this {@link SceneObject} is painted or not. If
	 * it is painted a rectangle will be drawn around the dimensions of this object.
	 * 
	 * @return true if the bounding box will be painted
	 */
	public boolean isDrawBoundingBox() {
		return drawBoundingBox;
	}

	/**
	 * Change the drawing state of the bounding box.
	 * 
	 * @param drawBoundingBox
	 *            true if the bounding box should be painted
	 */
	public void setDrawBoundingBox(boolean drawBoundingBox) {
		this.drawBoundingBox = drawBoundingBox;
	}

	/**
	 * Get the {@link RenderingOptions} used for painting. The are applied to every
	 * {@link Graphics2D} object, this one is painted on.
	 * 
	 * @return The rendering options or null, if no rendering related configuration
	 *         was made
	 * @see #setRenderingOptions(RenderingOptions)
	 */
	public RenderingOptions getRenderingOptions() {
		return renderingOptions;
	}

	/**
	 * Set the {@link RenderingOptions} for painting.
	 * 
	 * @param renderingOptions
	 * @see #getRenderingOptions()
	 */
	public void setRenderingOptions(RenderingOptions renderingOptions) {
		this.renderingOptions = renderingOptions;
	}

	/**
	 * Check, if the given {@link SceneObject} is completely covered by this
	 * {@link SceneObject}.
* First test with bounding box if there maybe a overlapping situation exact * calculations are made. * * @param obj * Other Object for testing * * @return true if the given object is completely covered by this object */ public boolean consumes(SceneObject obj) { if (consumesBoundingBox(obj)) { return consumesExactly(obj); } return false; } /** * Exactly check overlapping. Uses alpha mask to detect geometry. If there's a * pixel in the given object, that has an alpha greater 0 and this object is * transparent (alpha = 0) at this position false is returned. * * * @param obj * Other object for testing * @return true if the given {@link SceneObject} is consumed by this object */ public boolean consumesExactly(SceneObject obj) { BufferedImage img1 = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g1 = img1.createGraphics(); paint(g1, 0); BufferedImage img2 = new BufferedImage(obj.getWidth(), obj.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = img2.createGraphics(); obj.paint(g2, 0); ColorModel cm1 = img1.getColorModel(); WritableRaster raster1 = img1.getRaster(); ColorModel cm2 = img2.getColorModel(); WritableRaster raster2 = img2.getRaster(); Point topLeftPosition1 = getTopLeftPosition(); Point topLeftPosition2 = obj.getTopLeftPosition(); int offsetX = topLeftPosition2.getX() - topLeftPosition1.getX(); int offsetY = topLeftPosition2.getY() - topLeftPosition1.getY(); int width1 = img1.getWidth(); int height1 = img1.getHeight(); int width2 = img2.getWidth(); int height2 = img2.getHeight(); for (int x = 0; x < width2; x++) { for (int y = 0; y < height2; y++) { int alpha2 = cm2.getAlpha(raster2.getDataElements(x, y, null)); if (alpha2 == 0) { continue; } int xt = x - offsetX; int yt = y - offsetY; // Collision not possible out of bounds if (xt < 0 || yt < 0 || xt >= width1 || yt >= height1) { return false; } int alpha1 = cm1.getAlpha(raster1.getDataElements(xt, yt, null)); if (alpha1 == 0) { return false; } } } return true; } /** * Tests overlapping with bounding box. * * @param obj * Other Object for testing */ public boolean consumesBoundingBox(SceneObject obj) { Rectangle rectangleT = getRectangle(); Rectangle rectangleO = obj.getRectangle(); if (rectangleT.contains(rectangleO)) { return true; } return false; } /** * Check, if both {@link SceneObject}s collide.
* In a first step bounding box collision will be tested. If a collision maybe * possible exact collision is checked. * * @see #collidesExactly(SceneObject, Rectangle) * @see #collidesBoundingBox(SceneObject) * * @param obj * Second SceneObject * @return true if both {@link SceneObject}s collide */ public boolean collides(SceneObject obj) { boolean isColliding; Rectangle intersection = collidesBoundingBox(obj); isColliding = intersection != null; if (isColliding) { isColliding = collidesExactly(obj, intersection); } return isColliding; } /** * Check exactly if a collision is present. The result may only be correct if * {@link #collidesBoundingBox(SceneObject)} returns a non empty region. The * default implementation tests every pixel drawn by this object. If there's a * pixel set (alpha != 0) in both objects they collide. Depending on the object * there maybe a better collision handling. Feel free to override this method * for better performance. * * @param obj * The second {@link SceneObject} for testing * @param intersection * Region in which the collision may occur * @return true if there's a collision */ public boolean collidesExactly(SceneObject obj, Rectangle intersection) { boolean collides = false; BufferedImage img1 = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g1 = img1.createGraphics(); paint(g1, 0); BufferedImage img2 = new BufferedImage(obj.getWidth(), obj.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = img2.createGraphics(); obj.paint(g2, 0); ColorModel cm1 = img1.getColorModel(); WritableRaster raster1 = img1.getRaster(); ColorModel cm2 = img2.getColorModel(); WritableRaster raster2 = img2.getRaster(); Point topLeftPosition1 = getTopLeftPosition(); Point topLeftPosition2 = obj.getTopLeftPosition(); int xOffset1 = -topLeftPosition1.getX(); int yOffset1 = -topLeftPosition1.getY(); int xOffset2 = -topLeftPosition2.getX(); int yOffset2 = -topLeftPosition2.getY(); loop: for (int y = intersection.y; y < intersection.height + intersection.y; y++) { for (int x = (int) intersection.x; x < intersection.width + intersection.x; x++) { int alpha1 = cm1.getAlpha(raster1.getDataElements(x + xOffset1, y + yOffset1, null)); // (img1.getRGB(x + xOffset1, y + yOffset1) >> 24) & 0xFF; // Collision not possible if (alpha1 == 0) { continue; } int alpha2 = cm2.getAlpha(raster2.getDataElements(x + xOffset2, y + yOffset2, null)); // (img2.getRGB(x + xOffset2, y + yOffset2) >> 24) & 0xFF; if (alpha1 == alpha2) { collides = true; break loop; } } } return collides; } /** * Check collision with bounding box algorithm. * * @param obj * The second {@link SceneObject} for testing * @return The overlapping region or null if none. Coordinates (0,0) is located * in the top-left corner. */ public Rectangle collidesBoundingBox(SceneObject obj) { Rectangle2D rect = getRectangle().createIntersection(obj.getRectangle()); Rectangle ret = null; if (rect.getWidth() >= 0 && rect.getWidth() >= 0) { ret = new Rectangle((int) rect.getX(), (int) rect.getY(), (int) rect.getWidth(), (int) rect.getHeight()); } return ret; } /** * Get the {@link Rectangle} representing this object's bounds. * * @return This object's bounds */ public Rectangle getRectangle() { Point position = getTopLeftPosition(); return new Rectangle(position.getX(), position.getY(), getWidth(), getHeight()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy