/** * * Copyright 2008 - 2011 * * 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. * * @project loon * @author cping * @email:javachenpeng@yahoo.com * @version 0.1 */ package loon.geom; import java.io.Serializable; import loon.action.sprite.ShapeEntity; import loon.canvas.LColor; import loon.utils.MathUtils; public abstract class Shape implements Serializable, XY { /** * */ private static final long serialVersionUID = 1L; public float x; public float y; protected float rotation; protected float[] points; protected float[] center; protected float scaleX, scaleY; protected float maxX, maxY; protected float minX, minY; protected float boundingCircleRadius; protected boolean pointsDirty; protected transient Triangle triangle; protected boolean trianglesDirty; protected AABB aabb; protected RectBox rect; protected ShapeEntity entity; public Shape() { pointsDirty = true; scaleX = scaleY = 1f; } public void setLocation(float x, float y) { setX(x); setY(y); } public abstract Shape transform(Matrix3 transform); protected abstract void createPoints(); public void translate(int deltaX, int deltaY) { setX(x + deltaX); setY(y + deltaY); } public int vertexCount() { return points.length / 2; } public Vector2f getPosition() { return new Vector2f(getX(), getY()); } public Vector2f getCenterPos() { return new Vector2f(getCenterX(), getCenterY()); } public float getX() { return x; } public void setX(float x) { if (x != this.x || x == 0) { float dx = x - this.x; this.x = x; if ((points == null) || (center == null)) { checkPoints(); } for (int i = 0; i < points.length / 2; i++) { points[i * 2] += dx; } center[0] += dx; x += dx; maxX += dx; minX += dx; trianglesDirty = true; } } public void setY(float y) { if (y != this.y || y == 0) { float dy = y - this.y; this.y = y; if ((points == null) || (center == null)) { checkPoints(); } for (int i = 0; i < points.length / 2; i++) { points[(i * 2) + 1] += dy; } center[1] += dy; y += dy; maxY += dy; minY += dy; trianglesDirty = true; } } public float getY() { return y; } public float length() { return MathUtils.sqrt(x * x + y * y); } public void setLocation(Vector2f loc) { setX(loc.x); setY(loc.y); } public float getCenterX() { checkPoints(); return center[0]; } public void setCenterX(float centerX) { if ((points == null) || (center == null)) { checkPoints(); } float xDiff = centerX - getCenterX(); setX(x + xDiff); } public float getCenterY() { checkPoints(); return center[1]; } public void setCenterY(float centerY) { if ((points == null) || (center == null)) { checkPoints(); } float yDiff = centerY - getCenterY(); setY(y + yDiff); } public void setCenter(Vector2f pos) { setCenterX(pos.x); setCenterY(pos.y); } public float getMaxX() { checkPoints(); return maxX; } public float getMaxY() { checkPoints(); return maxY; } public float getMinX() { checkPoints(); return minX; } public float getMinY() { checkPoints(); return minY; } public float getBoundingCircleRadius() { checkPoints(); return boundingCircleRadius; } public float[] getCenter() { checkPoints(); return center; } public float[] getPoints() { checkPoints(); return points; } public int getPointCount() { checkPoints(); return points.length / 2; } public float[] getPoint(int index) { checkPoints(); float result[] = new float[2]; result[0] = points[index * 2]; result[1] = points[index * 2 + 1]; return result; } public float[] getNormal(int index) { float[] current = getPoint(index); float[] prev = getPoint(index - 1 < 0 ? getPointCount() - 1 : index - 1); float[] next = getPoint(index + 1 >= getPointCount() ? 0 : index + 1); float[] t1 = getNormal(prev, current); float[] t2 = getNormal(current, next); if ((index == 0) && (!closed())) { return t2; } if ((index == getPointCount() - 1) && (!closed())) { return t1; } float tx = (t1[0] + t2[0]) / 2; float ty = (t1[1] + t2[1]) / 2; float len = MathUtils.sqrt((tx * tx) + (ty * ty)); return new float[] { tx / len, ty / len }; } public boolean contains(Shape other) { if (other.intersects(this)) { return false; } for (int i = 0; i < other.getPointCount(); i++) { float[] pt = other.getPoint(i); if (!contains(pt[0], pt[1])) { return false; } } return true; } private float[] getNormal(float[] start, float[] end) { float dx = start[0] - end[0]; float dy = start[1] - end[1]; float len = MathUtils.sqrt((dx * dx) + (dy * dy)); dx /= len; dy /= len; return new float[] { -dy, dx }; } public boolean includes(float x, float y) { if (points.length == 0) { return false; } checkPoints(); Line testLine = new Line(0, 0, 0, 0); Vector2f pt = new Vector2f(x, y); for (int i = 0; i < points.length; i += 2) { int n = i + 2; if (n >= points.length) { n = 0; } testLine.set(points[i], points[i + 1], points[n], points[n + 1]); if (testLine.on(pt)) { return true; } } return false; } public int indexOf(float x, float y) { for (int i = 0; i < points.length; i += 2) { if ((points[i] == x) && (points[i + 1] == y)) { return i / 2; } } return -1; } public boolean contains(float x, float y) { checkPoints(); if (points.length == 0) { return false; } boolean result = false; float xnew, ynew; float xold, yold; float x1, y1; float x2, y2; int npoints = points.length; xold = points[npoints - 2]; yold = points[npoints - 1]; for (int i = 0; i < npoints; i += 2) { xnew = points[i]; ynew = points[i + 1]; if (xnew > xold) { x1 = xold; x2 = xnew; y1 = yold; y2 = ynew; } else { x1 = xnew; x2 = xold; y1 = ynew; y2 = yold; } if ((xnew < x) == (x <= xold) && (y - y1) * (x2 - x1) < (y2 - y1) * (x - x1)) { result = !result; } xold = xnew; yold = ynew; } return result; } public boolean intersects(Shape shape) { if (shape == null) { return false; } checkPoints(); boolean result = false; float points[] = getPoints(); float thatPoints[] = shape.getPoints(); int length = points.length; int thatLength = thatPoints.length; float unknownA; float unknownB; if (!closed()) { length -= 2; } if (!shape.closed()) { thatLength -= 2; } for (int i = 0; i < length; i += 2) { int iNext = i + 2; if (iNext >= points.length) { iNext = 0; } for (int j = 0; j < thatLength; j += 2) { int jNext = j + 2; if (jNext >= thatPoints.length) { jNext = 0; } unknownA = (((points[iNext] - points[i]) * (float) (thatPoints[j + 1] - points[i + 1])) - ((points[iNext + 1] - points[i + 1]) * (thatPoints[j] - points[i]))) / (((points[iNext + 1] - points[i + 1]) * (thatPoints[jNext] - thatPoints[j])) - ((points[iNext] - points[i]) * (thatPoints[jNext + 1] - thatPoints[j + 1]))); unknownB = (((thatPoints[jNext] - thatPoints[j]) * (float) (thatPoints[j + 1] - points[i + 1])) - ((thatPoints[jNext + 1] - thatPoints[j + 1]) * (thatPoints[j] - points[i]))) / (((points[iNext + 1] - points[i + 1]) * (thatPoints[jNext] - thatPoints[j])) - ((points[iNext] - points[i]) * (thatPoints[jNext + 1] - thatPoints[j + 1]))); if (unknownA >= 0 && unknownA <= 1 && unknownB >= 0 && unknownB <= 1) { result = true; break; } } if (result) { break; } } return result; } public boolean hasVertex(float x, float y) { if (points.length == 0) { return false; } checkPoints(); for (int i = 0; i < points.length; i += 2) { if ((points[i] == x) && (points[i + 1] == y)) { return true; } } return false; } protected void findCenter() { center = new float[] { 0, 0 }; int length = points.length; for (int i = 0; i < length; i += 2) { center[0] += points[i]; center[1] += points[i + 1]; } center[0] /= (length / 2); center[1] /= (length / 2); } protected void calculateRadius() { boundingCircleRadius = 0; for (int i = 0; i < points.length; i += 2) { float temp = ((points[i] - center[0]) * (points[i] - center[0])) + ((points[i + 1] - center[1]) * (points[i + 1] - center[1])); boundingCircleRadius = (boundingCircleRadius > temp) ? boundingCircleRadius : temp; } boundingCircleRadius = MathUtils.sqrt(boundingCircleRadius); } protected void calculateTriangles() { if ((!trianglesDirty) && (triangle != null)) { return; } if (points.length >= 6) { triangle = new TriangleNeat(); for (int i = 0; i < points.length; i += 2) { triangle.addPolyPoint(points[i], points[i + 1]); } triangle.triangulate(); } trianglesDirty = false; } private void callTransform(Matrix3 m) { if (points != null) { float[] result = new float[points.length]; m.transform(points, 0, result, 0, points.length / 2); this.points = result; this.checkPoints(); } } public void setScale(float s) { this.setScale(s, s); } public void setScale(float sx, float sy) { if (scaleX != sx || scaleY != sy) { Matrix3 m = new Matrix3(); m.scale(scaleX = sx, scaleY = sy); this.callTransform(m); } } public float getScaleX() { return scaleX; } public float getScaleY() { return scaleY; } public void setRotation(float r) { if (rotation != r) { this.callTransform(Matrix3.createRotateTransform(rotation = (r / 180f * MathUtils.PI), this.center[0], this.center[1])); } } public void setRotation(float r, float x, float y) { if (rotation != r) { this.callTransform(Matrix3.createRotateTransform(rotation = (r / 180f * MathUtils.PI), x, y)); } } public float getRotation() { return (rotation * 180f / MathUtils.PI); } public void increaseTriangulation() { checkPoints(); calculateTriangles(); triangle = new TriangleOver(triangle); } public Triangle getTriangles() { checkPoints(); calculateTriangles(); return triangle; } protected synchronized final void checkPoints() { if (pointsDirty) { createPoints(); findCenter(); calculateRadius(); if (points == null) { return; } synchronized (points) { final int size = points.length; if (size > 0) { maxX = points[0]; maxY = points[1]; minX = points[0]; minY = points[1]; for (int i = 0; i < size / 2; i++) { maxX = MathUtils.max(points[i * 2], maxX); maxY = MathUtils.max(points[(i * 2) + 1], maxY); minX = MathUtils.min(points[i * 2], minX); minY = MathUtils.min(points[(i * 2) + 1], minY); } } pointsDirty = false; trianglesDirty = true; } } } public void preCache() { checkPoints(); getTriangles(); } public boolean closed() { return true; } public Polygon prune() { Polygon result = new Polygon(); for (int i = 0; i < getPointCount(); i++) { int next = i + 1 >= getPointCount() ? 0 : i + 1; int prev = i - 1 < 0 ? getPointCount() - 1 : i - 1; float dx1 = getPoint(i)[0] - getPoint(prev)[0]; float dy1 = getPoint(i)[1] - getPoint(prev)[1]; float dx2 = getPoint(next)[0] - getPoint(i)[0]; float dy2 = getPoint(next)[1] - getPoint(i)[1]; float len1 = MathUtils.sqrt((dx1 * dx1) + (dy1 * dy1)); float len2 = MathUtils.sqrt((dx2 * dx2) + (dy2 * dy2)); dx1 /= len1; dy1 /= len1; dx2 /= len2; dy2 /= len2; if ((dx1 != dx2) || (dy1 != dy2)) { result.addPoint(getPoint(i)[0], getPoint(i)[1]); } } return result; } public float getWidth() { return maxX - minX; } public float getHeight() { return maxY - minY; } public RectBox getRect() { if (rect == null) { rect = new RectBox(x, y, getWidth(), getHeight()); } else { rect.setBounds(x, y, getWidth(), getHeight()); } return rect; } public AABB getAABB() { if (aabb == null) { aabb = new AABB(minX, minY, maxX, maxY); } else { aabb.set(minX, minY, maxX, maxY); } return aabb; } public ShapeEntity getEntity() { return getEntity(LColor.white, true); } public ShapeEntity getEntity(LColor c, boolean fill) { if (entity == null) { entity = new ShapeEntity(this, c, fill); } else { entity.setShape(this); } return entity; } }