/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.math; /** Encapsulates a 2D polygon defined by it's vertices relative to an origin point (default of 0, 0). */ public class Polygon implements Shape2D { private float[] localVertices; private float[] worldVertices; private float x, y; private float originX, originY; private float rotation; private float scaleX = 1, scaleY = 1; private boolean dirty = true; private Rectangle bounds; /** Constructs a new polygon with no vertices. */ public Polygon () { this.localVertices = new float[0]; } /** Constructs a new polygon from a float array of parts of vertex points. * * @param vertices an array where every even element represents the horizontal part of a point, and the following element * representing the vertical part * * @throws IllegalArgumentException if less than 6 elements, representing 3 points, are provided */ public Polygon (float[] vertices) { if (vertices.length < 6) throw new IllegalArgumentException("polygons must contain at least 3 points."); this.localVertices = vertices; } /** Returns the polygon's local vertices without scaling or rotation and without being offset by the polygon position. */ public float[] getVertices () { return localVertices; } /** Calculates and returns the vertices of the polygon after scaling, rotation, and positional translations have been applied, * as they are position within the world. * * @return vertices scaled, rotated, and offset by the polygon position. */ public float[] getTransformedVertices () { if (!dirty) return worldVertices; dirty = false; final float[] localVertices = this.localVertices; if (worldVertices == null || worldVertices.length != localVertices.length) worldVertices = new float[localVertices.length]; final float[] worldVertices = this.worldVertices; final float positionX = x; final float positionY = y; final float originX = this.originX; final float originY = this.originY; final float scaleX = this.scaleX; final float scaleY = this.scaleY; final boolean scale = scaleX != 1 || scaleY != 1; final float rotation = this.rotation; final float cos = MathUtils.cosDeg(rotation); final float sin = MathUtils.sinDeg(rotation); for (int i = 0, n = localVertices.length; i < n; i += 2) { float x = localVertices[i] - originX; float y = localVertices[i + 1] - originY; // scale if needed if (scale) { x *= scaleX; y *= scaleY; } // rotate if needed if (rotation != 0) { float oldX = x; x = cos * x - sin * y; y = sin * oldX + cos * y; } worldVertices[i] = positionX + x + originX; worldVertices[i + 1] = positionY + y + originY; } return worldVertices; } /** Sets the origin point to which all of the polygon's local vertices are relative to. */ public void setOrigin (float originX, float originY) { this.originX = originX; this.originY = originY; dirty = true; } /** Sets the polygon's position within the world. */ public void setPosition (float x, float y) { this.x = x; this.y = y; dirty = true; } /** Sets the polygon's local vertices relative to the origin point, without any scaling, rotating or translations being applied. * * @param vertices float array where every even element represents the x-coordinate of a vertex, and the proceeding element * representing the y-coordinate. * @throws IllegalArgumentException if less than 6 elements, representing 3 points, are provided */ public void setVertices (float[] vertices) { if (vertices.length < 6) throw new IllegalArgumentException("polygons must contain at least 3 points."); localVertices = vertices; dirty = true; } /** Translates the polygon's position by the specified horizontal and vertical amounts. */ public void translate (float x, float y) { this.x += x; this.y += y; dirty = true; } /** Sets the polygon to be rotated by the supplied degrees. */ public void setRotation (float degrees) { this.rotation = degrees; dirty = true; } /** Applies additional rotation to the polygon by the supplied degrees. */ public void rotate (float degrees) { rotation += degrees; dirty = true; } /** Sets the amount of scaling to be applied to the polygon. */ public void setScale (float scaleX, float scaleY) { this.scaleX = scaleX; this.scaleY = scaleY; dirty = true; } /** Applies additional scaling to the polygon by the supplied amount. */ public void scale (float amount) { this.scaleX += amount; this.scaleY += amount; dirty = true; } /** Sets the polygon's world vertices to be recalculated when calling {@link #getTransformedVertices() getTransformedVertices}. */ public void dirty () { dirty = true; } /** Returns the area contained within the polygon. */ public float area () { float[] vertices = getTransformedVertices(); return GeometryUtils.polygonArea(vertices, 0, vertices.length); } /** Returns an axis-aligned bounding box of this polygon. * * Note the returned Rectangle is cached in this polygon, and will be reused if this Polygon is changed. * * @return this polygon's bounding box {@link Rectangle} */ public Rectangle getBoundingRectangle () { float[] vertices = getTransformedVertices(); float minX = vertices[0]; float minY = vertices[1]; float maxX = vertices[0]; float maxY = vertices[1]; final int numFloats = vertices.length; for (int i = 2; i < numFloats; i += 2) { minX = minX > vertices[i] ? vertices[i] : minX; minY = minY > vertices[i + 1] ? vertices[i + 1] : minY; maxX = maxX < vertices[i] ? vertices[i] : maxX; maxY = maxY < vertices[i + 1] ? vertices[i + 1] : maxY; } if (bounds == null) bounds = new Rectangle(); bounds.x = minX; bounds.y = minY; bounds.width = maxX - minX; bounds.height = maxY - minY; return bounds; } /** Returns whether an x, y pair is contained within the polygon. */ @Override public boolean contains (float x, float y) { final float[] vertices = getTransformedVertices(); final int numFloats = vertices.length; int intersects = 0; for (int i = 0; i < numFloats; i += 2) { float x1 = vertices[i]; float y1 = vertices[i + 1]; float x2 = vertices[(i + 2) % numFloats]; float y2 = vertices[(i + 3) % numFloats]; if (((y1 <= y && y < y2) || (y2 <= y && y < y1)) && x < ((x2 - x1) / (y2 - y1) * (y - y1) + x1)) intersects++; } return (intersects & 1) == 1; } @Override public boolean contains (Vector2 point) { return contains(point.x, point.y); } /** Returns the x-coordinate of the polygon's position within the world. */ public float getX () { return x; } /** Returns the y-coordinate of the polygon's position within the world. */ public float getY () { return y; } /** Returns the x-coordinate of the polygon's origin point. */ public float getOriginX () { return originX; } /** Returns the y-coordinate of the polygon's origin point. */ public float getOriginY () { return originY; } /** Returns the total rotation applied to the polygon. */ public float getRotation () { return rotation; } /** Returns the total horizontal scaling applied to the polygon. */ public float getScaleX () { return scaleX; } /** Returns the total vertical scaling applied to the polygon. */ public float getScaleY () { return scaleY; } }