/*
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.ui.geom;
import com.codename1.io.Log;
import com.codename1.ui.Transform;
import com.codename1.util.MathUtil;
import java.util.ArrayList;
import java.util.Arrays;
/**
* <p>A general geometric path, consisting of any number of subpaths constructed
* out of straight lines and cubic or quadratic Bezier curves. The inside of the
* curve is defined for drawing purposes by a winding rule. Either the
* {@link #WIND_EVEN_ODD} or {@link #WIND_NON_ZERO} winding rule can be chosen.</p>
*
* <h4>A drawing of a GeneralPath</h4>
*
* <img
* src="http://developer.classpath.org/doc/java/awt/geom/doc-files/GeneralPath-1.png"/>
*
* <p>
* The {@link #WIND_EVEN_ODD} winding rule defines a point as inside a path if:
* A ray from the point towards infinity in an arbitrary direction intersects
* the path an odd number of times. Points {@literal A} and {@literal C} in the
* image are considered to be outside the path. (both intersect twice) Point
* {@literal B} intersects once, and is inside.</p>
*
* <p>
* The {@link #WIND_NON_ZERO} winding rule defines a point as inside a path if:
* The path intersects the ray in an equal number of opposite directions. Point
* {@link A} in the image is outside (one intersection in the 'up' direction,
* one in the 'down' direction) Point {@literal B} in the image is inside (one
* intersection 'down') Point C in the image is inside (two intersections in the
* 'down' direction)</p>
*
* <script src="https://gist.github.com/codenameone/3f2f8cdaabb7780eae6f.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/graphics-shape-fill.png" alt="Fill a shape general path" />
*
* <p>Note: This description and image were copied from <a
* href="http://developer.classpath.org/doc/java/awt/geom/GeneralPath.html">the
* GNU classpath</a>
* docs). License here http://www.gnu.org/licenses/licenses.html#FDL</p>
*
* @author shannah
*
* @see com.codename1.ui.Graphics#drawShape
* @see com.codename1.ui.Graphics#fillShape
*/
public final class GeneralPath implements Shape {
private static int MAX_POOL_SIZE=20;
private static ArrayList<GeneralPath> pathPool;
private static ArrayList<Rectangle> rectPool;
private static ArrayList<float[]> floatPool;
private static ArrayList<boolean[]> boolPool;
private static ArrayList<Iterator> iteratorPool;
private static ArrayList<GeneralPath> pathPool() {
if (pathPool == null) {
pathPool = new ArrayList<GeneralPath>();
}
return pathPool;
}
private static ArrayList<Rectangle> rectPool() {
if (rectPool == null) {
rectPool = new ArrayList<Rectangle>();
}
return rectPool;
}
private static ArrayList<float[]> floatPool() {
if (floatPool == null) {
floatPool = new ArrayList<float[]>();
}
return floatPool;
}
private static ArrayList<boolean[]> boolPool() {
if (boolPool == null) {
boolPool = new ArrayList<boolean[]>();
}
return boolPool;
}
private static ArrayList<Iterator> iteratorPool() {
if (iteratorPool == null) {
iteratorPool = new ArrayList<Iterator>();
}
return iteratorPool;
}
private static synchronized GeneralPath createPathFromPool() {
if (!pathPool().isEmpty()) {
GeneralPath out = pathPool.remove(pathPool.size()-1);
out.reset();
return out;
}
return new GeneralPath();
}
private static synchronized Rectangle createRectFromPool() {
if (!rectPool().isEmpty()) {
return rectPool.remove(rectPool.size()-1);
}
return new Rectangle();
}
private static synchronized float[] createFloatArrayFromPool(int size) {
int len = floatPool().size();
for (int i=0; i<len; i++) {
float[] arr = floatPool.get(i);
if (arr.length == size) {
return floatPool.remove(i);
}
}
return new float[size];
}
private static synchronized boolean[] createBoolArrayFromPool(int size) {
int len = boolPool().size();
for (int i=0; i<len; i++) {
boolean[] arr = boolPool.get(i);
if (arr.length == size) {
return boolPool.remove(i);
}
}
return new boolean[size];
}
private static synchronized Iterator createIteratorFromPool(GeneralPath p, Transform t) {
if (!iteratorPool().isEmpty()) {
Iterator it = iteratorPool.remove(iteratorPool.size()-1);
it.p = p;
it.transform = t;
it.reset();
return it;
}
return (Iterator)p.getPathIterator(t);
}
/**
* Returns a GeneralPath to the reusable object pool for GeneralPaths.
* @param p The path to recycle.
*
* @see #createFromPool()
*/
public static synchronized void recycle(GeneralPath p) {
if (pathPool().size() >= MAX_POOL_SIZE || p == null) return;
pathPool.add(p);
}
private static synchronized void recycle(Rectangle r) {
if (rectPool.size() >= MAX_POOL_SIZE || r == null) return;
rectPool.add(r);
}
private static synchronized void recycle(float[] a) {
if (floatPool().size() >= MAX_POOL_SIZE || a == null) return;
floatPool.add(a);
}
private static synchronized void recycle(boolean[] b) {
if (boolPool().size() >= MAX_POOL_SIZE || b == null) {
return;
}
boolPool.add(b);
}
private static synchronized void recycle(Iterator it) {
if (iteratorPool().size() >= MAX_POOL_SIZE || it == null) {
return;
}
iteratorPool.add(it);
}
/**
* Creates a new GeneralPath from an object Pool. This is useful
* if you need to create a temporary General path that you wish
* to dispose of after using.
*
* <p>You should return this object back to the pool when you are done
* using the {@link #recycle(com.codename1.ui.geom.GeneralPath) } method.
* @return
*/
public static GeneralPath createFromPool() {
return createPathFromPool();
}
private boolean dirty = false;
// END Alpha Mask Caching Functionality
//--------------------------------------------------------------------------
/**
* Same constant as {@link PathIterator#WIND_EVEN_ODD}
*/
public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD;
/**
* Same constant as {@link PathIterator#WIND_NON_ZERO}
*/
public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO;
/**
* The buffers size
*/
private static final int BUFFER_SIZE = 10;
/**
* The buffers capacity
*/
private static final int BUFFER_CAPACITY = 10;
// These are package protected for ODD
/**
* The point's types buffer
*/
byte[] types;
/**
* The points buffer
*/
float[] points;
/**
* The point's type buffer size
*/
int typeSize;
/**
* The points buffer size
*/
int pointSize;
/**
* The path rule
*/
int rule;
/**
* The space amount in points buffer for different segmenet's types
*/
private static int pointShift[] = {
2, // MOVETO
2, // LINETO
4, // QUADTO
6, // CUBICTO
0}; // CLOSE
/*
* GeneralPath path iterator
*/
private class Iterator implements PathIterator {
/**
* The current cursor position in types buffer
*/
int typeIndex;
/**
* The current cursor position in points buffer
*/
int pointIndex;
/**
* The source GeneralPath object
*/
GeneralPath p;
Transform transform;
/**
* Constructs a new GeneralPath.Iterator for given general path
*
* @param path - the source GeneralPath object
*/
Iterator(GeneralPath path) {
this.p = path;
}
private void reset() {
typeIndex = 0;
pointIndex = 0;
}
public int getWindingRule() {
return p.getWindingRule();
}
public boolean isDone() {
return typeIndex >= p.typeSize;
}
public void next() {
typeIndex++;
}
private void transformSegmentInPlace() {
if (isDone()) {
// awt.4B=Iterator out of bounds
throw new IndexOutOfBoundsException("Path done"); //$NON-NLS-1$
}
if (transform == null) {
return;
}
int type = p.types[typeIndex];
int count = GeneralPath.pointShift[type];
for (int i=0; i < count; i+=2) {
buf[0] = p.points[pointIndex + i];
buf[1] = p.points[pointIndex + i + 1];
transform.transformPoint(buf, buf);
p.points[pointIndex+i] = buf[0];
p.points[pointIndex+i+1] = buf[1];
}
}
public int currentSegment(double[] coords) {
float[] fcoords = createFloatArrayFromPool(6);
try {
int res = currentSegment(fcoords);
int type = p.types[typeIndex];
int count = GeneralPath.pointShift[type];
for (int i=0; i< count; i ++) {
coords[i] = fcoords[i];
}
return res;
} finally {
recycle(fcoords);
}
}
private float[] buf = new float[2];
public int currentSegment(float[] coords) {
if (isDone()) {
// awt.4B=Iterator out of bounds
throw new IndexOutOfBoundsException("Path done"); //$NON-NLS-1$
}
int type = p.types[typeIndex];
int count = GeneralPath.pointShift[type];
if (transform == null) {
System.arraycopy(p.points, pointIndex, coords, 0, count);
} else {
transform.transformPoints(2, p.points, pointIndex, coords, 0, count / 2);
}
pointIndex += count;
return type;
}
}
/**
* Constructs a GeneralPath with the default ({@link #WIND_NON_ZERO})
* winding rule and initial capacity (10).
*/
public GeneralPath() {
this(WIND_NON_ZERO, BUFFER_SIZE);
}
/**
* Constructs a GeneralPath with a specific winding rule and the default
* initial capacity (10).
*
* @param rule The winding rule. One of {@link #WIND_NON_ZERO} and
* {@link #WIND_EVEN_ODD}
* @see #WIND_NON_ZERO
* @see #WIND_EVEN_ODD
*/
public GeneralPath(int rule) {
this(rule, BUFFER_SIZE);
}
/**
* Constructs a GeneralPath with a specific winding rule and the initial
* capacity. The initial capacity should be the approximate number of path
* segments to be used.
*
* @param rule The winding rule. ({@link #WIND_NON_ZERO} or
* {@link #WIND_EVEN_ODD}).
* @param initialCapacity the inital capacity, in path segments
*/
public GeneralPath(int rule, int initialCapacity) {
setWindingRule(rule);
types = new byte[initialCapacity];
points = new float[initialCapacity * 2];
}
/**
* Checks to see if this path forms a polygon.
* @return True if the path is a polygon.
*/
public boolean isPolygon() {
if (isRectangle()) {
return true;
}
Iterator it = createIteratorFromPool(this, null);
float[] curr = createFloatArrayFromPool(6);
float[] firstPoint = createFloatArrayFromPool(2);
try {
boolean firstMove = false;
int cmd = -1;
while (!it.isDone()) {
switch (cmd = it.currentSegment(curr)) {
case PathIterator.SEG_MOVETO: {
if (firstMove) {
return false;
}
firstMove = true;
firstPoint[0] = curr[0];
firstPoint[1] = curr[1];
break;
}
case PathIterator.SEG_CUBICTO:
case PathIterator.SEG_QUADTO:
return false;
}
it.next();
}
return cmd == PathIterator.SEG_CLOSE || (curr[0] == firstPoint[0] && curr[1] == firstPoint[1]);
} finally {
recycle(it);
recycle(curr);
recycle(firstPoint);
}
}
/**
* Returns the number of path commands in this path.
* @return The number of path commands in this path.
*/
public int getTypesSize() {
return typeSize;
}
/**
* Returns the number of points in this path.
* @return The number of points in this path.
*/
public int getPointsSize() {
return pointSize;
}
/**
* Returns a copy of the types (aka path commands) in this path.
* @param out An array to copy the path commands into.
*/
public void getTypes(byte[] out) {
System.arraycopy(types, 0, out, 0, Math.min(types.length, out.length));
}
/**
* Returns a copy of the points in this path.
* @param out An array to copy the points into.
*/
public void getPoints(float[] out) {
System.arraycopy(points, 0, out, 0, Math.min(points.length, out.length));
}
/**
* Constructs a GeneralPath from an arbitrary shape object. The Shapes
* PathIterator path and winding rule will be used.
*
* @param shape
*/
public GeneralPath(Shape shape) {
this(WIND_NON_ZERO, BUFFER_SIZE);
if (shape.getClass() == GeneralPath.class) {
setPath((GeneralPath)shape, null);
} else {
PathIterator p = shape.getPathIterator();
setWindingRule(p.getWindingRule());
append(p, false);
}
}
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append("[General Path: ");
Iterator it = createIteratorFromPool(this, null);
float[] buf = createFloatArrayFromPool(6);//new float[6];
try {
while (!it.isDone() ){
int type = it.currentSegment(buf);
switch ( type ){
case PathIterator.SEG_MOVETO:
sb.append("Move ("+buf[0]+","+buf[1]+"), ");
break;
case PathIterator.SEG_LINETO:
sb.append("Line ("+buf[0]+","+buf[1]+"), ");
break;
case PathIterator.SEG_CUBICTO:
sb.append("Curve ("+buf[0]+","+buf[1]+".."+buf[2]+","+buf[3]+".."+buf[4]+","+buf[5]+")");
break;
case PathIterator.SEG_QUADTO:
sb.append("Curve ("+buf[0]+","+buf[1]+".."+buf[2]+","+buf[3]+")");
break;
case PathIterator.SEG_CLOSE:
sb.append(" CLOSE]");
break;
}
it.next();
}
} finally {
recycle(buf);
recycle(it);
}
return sb.toString();
}
/**
* Sets the path's winding rule, which controls which areas are considered
* 'inside' or 'outside' the path on drawing. Valid rules are
* {@link #WIND_EVEN_ODD} for an even-odd winding rule, or
* {@link #WIND_NON_ZERO} for a non-zero winding rule.
*
* @param rule the rule. ({@link #WIND_NON_ZERO} or {@link #WIND_EVEN_ODD}).
*/
public void setWindingRule(int rule) {
if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) {
// awt.209=Invalid winding rule value
throw new java.lang.IllegalArgumentException("Invalid winding rule"); //$NON-NLS-1$
}
dirty = true;
this.rule = rule;
}
public boolean equals(Shape shape, Transform t) {
if (t != null && !t.isIdentity()) {
GeneralPath p = createPathFromPool();
p.setShape(shape, t);
try {
return equals(p, (Transform)null);
} finally {
recycle(p);
}
}
if (shape == this) return true;
if (shape instanceof Rectangle) {
Rectangle r = (Rectangle)shape;
Rectangle tmpRect = createRectFromPool();
try {
getBounds(tmpRect);
return r.equals(tmpRect);
} finally {
recycle(tmpRect);
}
} else if (shape instanceof GeneralPath) {
GeneralPath tmpPath = (GeneralPath)shape;
return Arrays.equals(points, tmpPath.points) && Arrays.equals(types, tmpPath.types);
} else {
GeneralPath tmpPath2 = createPathFromPool();
try {
tmpPath2.setShape(shape, null);
return equals(tmpPath2, (Transform)null);
} finally {
recycle(tmpPath2);
}
}
}
/**
* Returns the path's current winding rule.
*
* @return {@link #WIND_NON_ZERO} or {@link #WIND_EVEN_ODD}
*/
public int getWindingRule() {
return rule;
}
/**
* Checks points and types buffer size to add pointCount points. If
* necessary realloc buffers to enlarge size.
*
* @param pointCount - the point count to be added in buffer
*/
private void checkBuf(int pointCount, boolean checkMove) {
if (checkMove && typeSize == 0) {
// awt.20A=First segment should be SEG_MOVETO type
throw new IndexOutOfBoundsException("First segment must be a moveto"); //$NON-NLS-1$
}
if (typeSize == types.length) {
byte tmp[] = new byte[typeSize + BUFFER_CAPACITY];
System.arraycopy(types, 0, tmp, 0, typeSize);
types = tmp;
}
if (pointSize + pointCount > points.length) {
float tmp[] = new float[pointSize + Math.max(BUFFER_CAPACITY * 2, pointCount)];
System.arraycopy(points, 0, tmp, 0, pointSize);
points = tmp;
}
}
public void moveTo(double x, double y){
moveTo((float)x, (float)y);
}
/**
* Adds a new point to a path.
*
* @param x the x-coordinate.
* @param y the y-coordinate.
*/
public void moveTo(float x, float y) {
if (typeSize > 0 && types[typeSize - 1] == PathIterator.SEG_MOVETO) {
points[pointSize - 2] = x;
points[pointSize - 1] = y;
} else {
checkBuf(2, false);
types[typeSize++] = PathIterator.SEG_MOVETO;
points[pointSize++] = x;
points[pointSize++] = y;
}
dirty = true;
}
public void lineTo(double x, double y){
lineTo((float)x, (float)y);
}
/**
* Appends a straight line to the current path.
*
* @param x x coordinate of the line endpoint.
* @param y y coordinate of the line endpoint.
*/
public void lineTo(float x, float y) {
checkBuf(2, true);
types[typeSize++] = PathIterator.SEG_LINETO;
points[pointSize++] = x;
points[pointSize++] = y;
dirty = true;
}
public void quadTo(double x1, double y1, double x2, double y2){
quadTo((float)x1, (float)y1, (float)x2, (float)y2);
}
/**
* Appends a quadratic Bezier curve to the current path.
*
* @param x1 x coordinate of the control point
* @param y1 y coordinate of the control point
* @param x2 x coordinate of the curve endpoint.
* @param y2 y coordinate of the curve endpoint.
*/
public void quadTo(float x1, float y1, float x2, float y2) {
checkBuf(4, true);
types[typeSize++] = PathIterator.SEG_QUADTO;
points[pointSize++] = x1;
points[pointSize++] = y1;
points[pointSize++] = x2;
points[pointSize++] = y2;
dirty = true;
}
public void curveTo(double x1, double y1, double x2, double y2, double x3, double y3){
curveTo((float)x1, (float)y1, (float)x2, (float)y2, (float)x3, (float)y3);
}
/**
* Appends a cubic Bezier curve to the current path.
*
* @param x1 x coordinate of the first control point
* @param y1 y coordinate of the first control point
* @param x2 x coordinate of the second control point
* @param y2 y coordinate of the second control point
* @param x3 x coordinate of the curve endpoint.
* @param y3 y coordinate of the curve endpoint.
*/
public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) {
checkBuf(6, true);
types[typeSize++] = PathIterator.SEG_CUBICTO;
points[pointSize++] = x1;
points[pointSize++] = y1;
points[pointSize++] = x2;
points[pointSize++] = y2;
points[pointSize++] = x3;
points[pointSize++] = y3;
dirty = true;
}
/**
* Draws an elliptical arc on the path given the provided bounds.
* @param x Left x coord of bounding rect.
* @param y Top y coordof bounding rect.
* @param w Width of bounding rect.
* @param h Height of bounding rect.
* @param startAngle Start angle on ellipse in radians. Counter-clockwise from 3-o'clock.
* @param sweepAngle Sweep angle in radians. Counter-clockwise.
*/
public void arc(float x, float y, float w, float h, float startAngle, float sweepAngle) {
arc(x, y, w, h, startAngle, sweepAngle, false);
}
/**
* Draws an elliptical arc on the path given the provided bounds.
* @param x Left x coord of bounding rect.
* @param y Top y coordof bounding rect.
* @param w Width of bounding rect.
* @param h Height of bounding rect.
* @param startAngle Start angle on ellipse in radians. Counter-clockwise from 3-o'clock.
* @param sweepAngle Sweep angle in radians. Counter-clockwise.
* @param joinPath If true, then this will join the arc to the existing path with a line.
*/
public void arc(float x, float y, float w, float h, float startAngle, float sweepAngle, boolean joinPath) {
Ellipse e = new Ellipse();
Ellipse.initWithBounds(e, x, y, w, h);
e.addToPath(this, -startAngle, -sweepAngle, joinPath);
}
/**
* Draws an elliptical arc on the path given the provided bounds.
* @param x Left x coord of bounding rect.
* @param y Top y coordof bounding rect.
* @param w Width of bounding rect.
* @param h Height of bounding rect.
* @param startAngle Start angle on ellipse in radians. Counter-clockwise from 3-o'clock.
* @param sweepAngle Sweep angle in radians. Counter-clockwise.
*/
public void arc(double x, double y, double w, double h, double startAngle, double sweepAngle) {
arc(x, y, w, h, startAngle, sweepAngle, false);
}
/**
* Draws an elliptical arc on the path given the provided bounds.
* @param x Left x coord of bounding rect.
* @param y Top y coordof bounding rect.
* @param w Width of bounding rect.
* @param h Height of bounding rect.
* @param startAngle Start angle on ellipse in radians. Counter-clockwise from 3-o'clock.
* @param sweepAngle Sweep angle in radians. Counter-clockwise.
* @param joinPath If true then this will join the arc to the existing path with a line.
*/
public void arc(double x, double y, double w, double h, double startAngle, double sweepAngle, boolean joinPath) {
arc((float)x, (float)y, (float)w, (float)h, (float)startAngle, (float)sweepAngle, joinPath);
}
// private static void addBezierArcToPath(GeneralPath path, double cx, double cy,
// double startX, double startY, double endX, double endY) {
// addBezierArcToPath(path, cx, cy, startX, startY, endX, endY, false);
// }
/**
*
* @param path
* @param cx
* @param cy
* @param startX
* @param startY
* @param endX
* @param endY
* @param clockwise
*/
// private static void addBezierArcToPath(GeneralPath path, double cx, double cy,
// double startX, double startY, double endX, double endY, boolean clockwise) {
// if ( startX != endX || startY != endY ){
// double ax = startX - cx;
// double ay = startY - cy;
// double bx = endX - cx;
// double by = endY- cy;
//
// final double r1s = ax * ax + ay * ay;
// final double r2s = bx * bx + by * by;
// double ellipseScaleY = 0;
// if (Math.abs(r1s - r2s) > 2) {
// // This is not a circle
// // Let's get the arc for the circle
// ellipseScaleY = Math.sqrt(((ax*ax) - (bx*bx)) / (by*by - ay*ay));
// startY = cy + ellipseScaleY * (startY-cy);
// endY = cy + ellipseScaleY * (endY-cy);
//
// ay = startY - cy;
// by = endY - cy;
// } else {
// double startAngle = MathUtil.atan2(ay, ax);
// double endAngle = MathUtil.atan2(by, bx);
//
// double dist = Math.abs(endAngle - startAngle);
// if (clockwise) {
// if (startAngle > endAngle) {
// dist = Math.PI*2-dist;
// }
// } else {
// if (startAngle < endAngle) {
// dist = Math.PI*2-dist;
// }
// }
//
// //System.out.println("dist: "+dist+" startAngle: "+startAngle+" endAngle: "+endAngle);
// if (dist > Math.PI/3) {
// // We bisect
// double r = Math.sqrt(r1s);
// double bisectAngle = (startAngle + endAngle)/2;
// if (clockwise) {
// if (startAngle > endAngle) {
// bisectAngle += Math.PI;
// }
// } else {
// if (startAngle < endAngle) {
// bisectAngle += Math.PI;
// }
// }
// double bisectX = cx + r * Math.cos(bisectAngle);
// double bisectY = cy + r * Math.sin(bisectAngle);
// addBezierArcToPath(path, cx, cy, startX, startY, bisectX, bisectY, clockwise);
// addBezierArcToPath(path, cx, cy, bisectX, bisectY, endX, endY, clockwise);
// return;
// }
//
//
// }
//
// final double q1 = r1s;//ax * ax + ay * ay;
// final double q2 = q1 + ax * bx + ay * by;
// final double k2 = 4d / 3d * (Math.sqrt(2d * q1 * q2) - q2) / (ax * by - ay * bx);
// final float x2 = (float)(cx + ax - k2 * ay);
// float y2 = (float)(cy + ay + k2 * ax);
// final float x3 = (float)(cx + bx + k2 * by);
// float y3 = (float)(cy + by - k2 * bx);
// if (ellipseScaleY != 0) {
// y2 = (float)(cy + (y2-cy)/ellipseScaleY);
// y3 = (float)(cy + (y3-cy)/ellipseScaleY);
// endY = (float)(cy + (endY-cy)/ellipseScaleY);
// }
// path.curveTo(x2, y2, x3, y3, endX, endY);
//
// }
// }
static class Ellipse {
private double a;
private double b;
private double cx;
private double cy;
private EPoint _tmp1=new EPoint();
static void initWithBounds(Ellipse e, double x, double y, double w, double h) {
e.cx = x+w/2;
e.cy = y+h/2;
e.a = w/2;
e.b = h/2;
}
static void initWithPerimeterPoints(Ellipse e, double cx, double cy, double p1x, double p1y, double p2x, double p2y) {
/*
e.cx = cx;
e.cy = cy;
double x1 = p1x-cx;
double y1 = p1y-cy;
double x2 = p2x-cx;
double y2 = p2y-cy;
double x1s = x1*x1;
double x2s = x2*x2;
double y1s = y1*y1;
double y2s = y2*y2;
if (Math.abs(x1s-x2s) < 0.001 ||Math.abs(y1s-y2s) < 0.001) {
a = b = Math.max(Math.sqrt(Math.abs(y2)));
}
if (Math.abs(x1s-x2s) > 0.001) {
e.b = Math.sqrt((x1s*y2s-x2s-y1s)/(x1s-x2s));
double bs = e.b*e.b;
e.a = Math.sqrt(x1s*bs/(bs-y1s));
} else {
e.a = Math.sqrt((y1s*x2s-y2s-x1s)/(y1s-y2s));
double as = e.a*e.a;
e.b = Math.sqrt(y1s*as/(as-x1s));
}
*/
}
@Override
public String toString() {
return "Ellipse center=("+cx+","+cy+") a="+a+", b="+b+")";
}
void getPointAtAngle(double theta, EPoint out) {
double tanTheta = Math.tan(theta);
double tanThetas = tanTheta*tanTheta;
double bs = b*b;
double as = a*a;
double x = a*b/Math.sqrt(bs+as*tanThetas);
if (Math.cos(theta)<0) {
x = -x;
}
double y = a*b/Math.sqrt(as+bs/tanThetas);
if (Math.sin(theta)<0) {
y = -y;
}
out.x = x + cx;
out.y = y + cy;
}
double getAngleAtPoint(double px, double py) {
px -= cx;
py -= cy;
return MathUtil.atan2(py, px);
}
void addToPath(GeneralPath p, double startAngle, double sweepAngle, boolean join) {
getPointAtAngle(startAngle, _tmp1);
if (join) {
p.lineTo(_tmp1.x, _tmp1.y);
} else {
p.moveTo(_tmp1.x, _tmp1.y);
}
_addToPath(p, startAngle, sweepAngle);
if (!join && Math.abs(Math.abs(sweepAngle)-Math.PI*2) < 0.001) {
p.closePath();
}
}
private void _addToPath(GeneralPath p, double startAngle, double sweepAngle) {
//double _2pi = Math.PI*2;
double absSweepAngle = Math.abs(sweepAngle);
if (absSweepAngle < 0.0001) {
// Basically zero sweep angle so we won't draw anytything here.
// IOS seemed to choke when we tried to draw too small an arc
return;
}
if (absSweepAngle > Math.PI/4) {
//double halfAngle = sweepAngle/2;
double diff = Math.PI/4;
if (sweepAngle < 0) {
diff = -diff;
}
_addToPath(p, startAngle, diff);
_addToPath(p, startAngle+diff, sweepAngle-diff);
} else {
getPointAtAngle(startAngle+sweepAngle, _tmp1);
//System.out.println("Line to "+_tmp1.x+", "+_tmp1.y);
EPoint controlPoint = new EPoint();
calculateBezierControlPoint(startAngle, sweepAngle, controlPoint);
p.quadTo(controlPoint.x, controlPoint.y, _tmp1.x, _tmp1.y);
//p.lineTo(_tmp1.x, _tmp1.y);
}
}
private void calculateBezierControlPoint(double startAngle, double sweepAngle, EPoint point) {
EPoint p1 = new EPoint();
getPointAtAngle(startAngle, p1);
p1.x-= cx;
p1.y -= cy;
EPoint p2 = new EPoint();
getPointAtAngle(startAngle+sweepAngle, p2);
p2.x -= cx;
p2.y -= cy;
//System.out.println("p1: "+p1.x+", "+p1.y+", p2:"+p2.x+","+p2.y);
double x1s = p1.x*p1.x;
double y1s = p1.y*p1.y;
double x2s = p2.x*p2.x;
double y2s = p2.y*p2.y;
double as = a*a;
double bs = b*b;
//point.x = (x2s*bs/(p2.y*as) + p2.y - x1s*bs/(p1.y*as) - p1.y) / (-p1.x*bs/(p1.y*as) + p2.x*bs/(p2.y*as));
//point.y = (-p1.x*bs/(p1.y*as))*point.x + x1s*bs/(p1.y*as) + p1.y;
point.x = -(p1.y*(-as*y2s-bs*x2s)+as*y1s*p2.y+bs*x1s*p2.y)/(bs*p2.x*p1.y-bs*p1.x*p2.y);
point.y = (p1.x*(-as*y2s-bs*x2s)+as*p2.x*y1s+bs*x1s*p2.x)/(as*p2.x*p1.y-as*p1.x*p2.y);
point.x += cx;
point.y += cy;
//System.out.println("control: "+point.x+","+point.y);
}
}
static class EPoint {
double x;
double y;
}
/**
* Adds a circular arc to the given path by approximating it through a cubic Bezier curve, splitting it if
* necessary. The precision of the approximation can be adjusted through {@code pointsOnCircle} and
* {@code overlapPoints} parameters.
* <p>
* <strong>Example:</strong> imagine an arc starting from 0? and sweeping 100? with a value of
* {@code pointsOnCircle} equal to 12 (threshold -> 360? / 12 = 30?):
* <ul>
* <li>if {@code overlapPoints} is {@code true}, it will be split as following:
* <ul>
* <li>from 0? to 30? (sweep 30?)</li>
* <li>from 30? to 60? (sweep 30?)</li>
* <li>from 60? to 90? (sweep 30?)</li>
* <li>from 90? to 100? (sweep 10?)</li>
* </ul>
* </li>
* <li>if {@code overlapPoints} is {@code false}, it will be split into 4 equal arcs:
* <ul>
* <li>from 0? to 25? (sweep 25?)</li>
* <li>from 25? to 50? (sweep 25?)</li>
* <li>from 50? to 75? (sweep 25?)</li>
* <li>from 75? to 100? (sweep 25?)</li>
* </ul>
* </li>
* </ul>
* </p>
* <p/>
* For a technical explanation:
* <a href="http://hansmuller-flex.blogspot.de/2011/10/more-about-approximating-circular-arcs.html">
* http://hansmuller-flex.blogspot.de/2011/10/more-about-approximating-circular-arcs.html
* </a>
*
* @param center The center of the circle.
* @param radius The radius of the circle.
* @param startAngleRadians The starting angle on the circle (in radians).
* @param sweepAngleRadians How long to make the total arc (in radians).
* @param pointsOnCircle Defines a <i>threshold</i> (360? /{@code pointsOnCircle}) to split the Bezier arc to
* better approximate a circular arc, depending also on the value of {@code overlapPoints}.
* The suggested number to have a reasonable approximation of a circle is at least 4 (90?).
* Less than 1 will be ignored (the arc will not be split).
* @param overlapPoints Given the <i>threshold</i> defined through {@code pointsOnCircle}:
* <ul>
* <li>if {@code true}, split the arc on every angle which is a multiple of the
* <i>threshold</i> (yields better results if drawing precision is required,
* especially when stacking multiple arcs, but can potentially use more points)</li>
* <li>if {@code false}, split the arc equally so that each part is shorter than
* the <i>threshold</i></li>
* </ul>
* @param addToPath An existing path where to add the arc to, or {@code null} to create a new path.
*
*
* @see #createBezierArcDegrees(android.graphics.PointF, float, float, float, int, boolean, android.graphics.Path)
*/
// private static void createBezierArcRadians(float cx, float cy, float radiusX, float radiusY, double startAngleRadians,
// double sweepAngleRadians, int pointsOnCircle, boolean overlapPoints,
// GeneralPath addToPath, boolean joinPath)
// {
// final GeneralPath path = addToPath;
// if (sweepAngleRadians == 0d) { return; }
//
// float radius = radiusX;
// float yScale = radiusY / radius;
//
// if (pointsOnCircle >= 1)
// {
// final double threshold = Math.PI * 2d / pointsOnCircle;
// if (Math.abs(sweepAngleRadians) > threshold)
// {
// double angle = normalizeRadians(startAngleRadians);
// //PointF end, start = pointFromAngleRadians(center, radius, angle);
// double endX, endY;
// double startX = cx + radius * Math.cos(angle);
// double startY = cy + radius * Math.sin(angle) * yScale;
// if (joinPath) {
// path.lineTo(startX, startY);
// } else {
// path.moveTo(startX, startY);
// }
// if (overlapPoints)
// {
// final boolean cw = sweepAngleRadians > 0; // clockwise?
// final double angleEnd = angle + sweepAngleRadians;
// while (true)
// {
// double next = (cw ? Math.ceil(angle / threshold) : Math.floor(angle / threshold)) * threshold;
// if (angle == next) { next += threshold * (cw ? 1d : -1d); }
// final boolean isEnd = cw ? angleEnd <= next : angleEnd >= next;
// //end = pointFromAngleRadians(center, radius, isEnd ? angleEnd : next);
// endX = cx + radius * Math.cos(isEnd ? angleEnd : next);
// endY = cy + radius * Math.sin(isEnd ? angleEnd : next) *yScale;
// addBezierArcToPath(path, cx, cy, startX, startY, endX, endY);
// if (isEnd) { break; }
// angle = next;
// startX = endX;
// startY = endY;
// }
// }
// else
// {
// final int n = Math.abs((int)Math.ceil(sweepAngleRadians / threshold));
// final double sweep = sweepAngleRadians / (double)n;
// for (int i = 0;
// i < n;
// i++, startX = endX, startY = endY)
// {
// angle += sweep;
// //end = pointFromAngleRadians(center, radius, angle);
// endX = cx + radius * Math.cos(angle);
// endY = cy + radius * Math.sin(angle) * yScale;
// addBezierArcToPath(path, cx, cy, startX, startY, endX, endY);
// }
//
// }
// return;
// }
// }
//
// startAngleRadians = normalizeRadians(startAngleRadians);
// double startX = cx + radius * Math.cos(startAngleRadians);
// double startY = cy + radius * Math.sin(startAngleRadians) * yScale;
//
// double endX = cx + radius * Math.cos(startAngleRadians + sweepAngleRadians);
// double endY = cy + radius * Math.sin(startAngleRadians + sweepAngleRadians) * yScale;
// if (joinPath) {
// path.lineTo(startX, startY);
// } else {
// path.moveTo(startX, startY);
// }
// addBezierArcToPath(path, cx, cy, startX, startY, endX, endY);
//
// }
/**
* Normalize the input radians in the range 360? > x >= 0?.
*
* @param radians The angle to normalize (in radians).
*
* @return The angle normalized in the range 360? > x >= 0?.
*/
// private static double normalizeRadians(double radians)
// {
// double PI2 = Math.PI*2d;
// radians %= PI2;
// if (radians < 0d) { radians += PI2; }
// if (radians == PI2) { radians = 0d; }
// return radians;
// }
/**
* Adds an arc to the path. This method uses an approximation of an arc using
* a cubic path. It is not a precise arc.
*
* <p>Note: The arc is drawn counter-clockwise around the center point. See {@link #arcTo(double, double, double, double, boolean) }
* to draw clockwise.</p>
*
* @param cX The x-coordinate of the oval center.
* @param cY The y-coordinate of the oval center.
* @param endX The end X coordinate.
* @param endY The end Y coordinate.
*/
public void arcTo(float cX, float cY, float endX, float endY) {
arcTo(cX, cY, endX, endY, false);
}
/**
* Adds an arc to the path. This method uses an approximation of an arc using
* a cubic path. It is not a precise arc.
* @param cX The x-coordinate of the oval center.
* @param cY The y-coordinate of the oval center.
* @param endX The end X coordinate.
* @param endY The end Y coordinate.
* @param clockwise If true, the arc is drawn clockwise around the center point.
*/
public void arcTo(float cX, float cY, float endX, float endY, boolean clockwise){
if ( pointSize < 2 ){
throw new RuntimeException("Cannot add arc to path if it doesn't already have a starting point.");
}
float startX = points[pointSize-2];
float startY = points[pointSize-1];
float dx = endX-cX;
float dy = endY-cY;
double r2 = Math.sqrt(dx*dx+dy*dy);
double dx1 = startX-cX;
double dy1 = startY-cY;
double r1 = Math.sqrt(dx1*dx1+dy1*dy1);
if (Math.abs(r1-r2) > 1) {
Log.e(new RuntimeException("arcTo() called with start and end points that don't lie on the same arc r1="+r1+", r2="+r2));
}
Ellipse e = new Ellipse();
Ellipse.initWithBounds(e, cX-r2, cY-r2, r2*2, r2*2);
double startAngle = e.getAngleAtPoint(startX, startY);
double endAngle = e.getAngleAtPoint(endX, endY);
double sweepAngle = endAngle-startAngle;
if (clockwise && sweepAngle > 0) {
sweepAngle = -sweepAngle;
} else if (!clockwise && sweepAngle > 0) {
sweepAngle = 2*Math.PI-sweepAngle;
}
arc(cX-r2, cY-r2, r2*2, r2*2, -startAngle, sweepAngle, true);
lineTo(endX, endY);
}
/**
* Adds an arc to the path. This method uses an approximation of an arc using
* a cubic path. It is not a precise arc.
* <p>Note: The arc is drawn counter-clockwise around the center point. See {@link #arcTo(double, double, double, double, boolean) }
* to draw clockwise.</p>
* @param cX The x-coordinate of the oval center.
* @param cY The y-coordinate of the oval center.
* @param endX The end X coordinate.
* @param endY The end Y coordinate.
*/
public void arcTo(double cX, double cY, double endX, double endY) {
arcTo(cX, cY, endX, endY, false);
}
/**
* Adds an arc to the path. This method uses an approximation of an arc using
* a cubic path. It is not a precise arc.
* @param cX The x-coordinate of the oval center.
* @param cY The y-coordinate of the oval center.
* @param endX The end X coordinate.
* @param endY The end Y coordinate.
* @param clockwise If true, the arc is drawn clockwise around the center point.
*
*/
public void arcTo(double cX, double cY, double endX, double endY, boolean clockwise){
arcTo((float)cX, (float)cY, (float)endX, (float)endY, clockwise);
}
/**
* Closes the current subpath by drawing a line back to the point of the
* last moveTo, unless the path is already closed.
*/
public void closePath() {
if (typeSize == 0 || types[typeSize - 1] != PathIterator.SEG_CLOSE) {
checkBuf(0, true);
types[typeSize++] = PathIterator.SEG_CLOSE;
dirty = true;
}
}
/**
* Appends the segments of a Shape to the path. If connect is
* {@literal true}, the new path segments are connected to the existing one
* with a line. The winding rule of the Shape is ignored.
*
* @param shape the shape (null not permitted).
* @param connect whether to connect the new shape to the existing path.
*/
public void append(Shape shape, boolean connect) {
if (shape.getClass() == GeneralPath.class) {
Iterator it = createIteratorFromPool((GeneralPath)shape, null);
try {
append(it, connect);
} finally {
recycle(it);
}
} else {
PathIterator p = shape.getPathIterator();
append(p, connect);
}
dirty = true;
}
/**
* Appends the segments of a PathIterator to this GeneralPath. Optionally,
* the initial {@link PathIterator#SEG_MOVETO} segment of the appended path
* is changed into a {@link PathIterator#SEG_LINETO} segment.
*
* @param path the PathIterator specifying which segments shall be appended
* (null not permitted).
* @param connect {@literal true} for substituting the initial
* {@link PathIterator#SEG_MOVETO} segment by a
* {@link PathIterator#SEG_LINETO}, or false for not performing any
* substitution. If this {@code GeneralPath} is currently empty, connect is
* assumed to be {@literal false}, thus leaving the initial
* {@link PathIterator#SEG_MOVETO} unchanged.
*/
public void append(PathIterator path, boolean connect) {
float coords[] = createFloatArrayFromPool(6);//new float[6];
append(path, connect, coords);
recycle(coords);
}
private void append(PathIterator path, boolean connect, float[] tmpCoordsBuf) {
float coords[] = tmpCoordsBuf;
while (!path.isDone()) {
switch (path.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
if (!connect || typeSize == 0) {
moveTo(coords[0], coords[1]);
break;
}
if (types[typeSize - 1] != PathIterator.SEG_CLOSE
&& points[pointSize - 2] == coords[0]
&& points[pointSize - 1] == coords[1]) {
break;
}
// NO BREAK;
case PathIterator.SEG_LINETO:
lineTo(coords[0], coords[1]);
break;
case PathIterator.SEG_QUADTO:
quadTo(coords[0], coords[1], coords[2], coords[3]);
break;
case PathIterator.SEG_CUBICTO:
curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
break;
case PathIterator.SEG_CLOSE:
closePath();
break;
}
path.next();
connect = false;
}
dirty = true;
}
/**
* Returns the current appending point of the path.
*
* @return 2-element array of the form {@code [x,y]} representing {@code x}
* and {@code y} coordinate of the current appending point of the path..
*/
public float[] getCurrentPoint() {
if (typeSize == 0) {
return null;
}
float[] out = new float[2];
getCurrentPoint(out);
return out;
}
/**
* Sets the coordinates of the given point to the current point in the path.
*
* @param point Out parameter. Will be filled with the coords of the current point.
*/
public void getCurrentPoint(float[] point) {
if (typeSize == 0) {
throw new RuntimeException("Cannot get point because the size of this command is 0");
}
int j = pointSize - 2;
if (types[typeSize - 1] == PathIterator.SEG_CLOSE) {
for (int i = typeSize - 2; i > 0; i--) {
int type = types[i];
if (type == PathIterator.SEG_MOVETO) {
break;
}
j -= pointShift[type];
}
}
point[0] = points[j];
point[1] = points[j+1];
//return new float[]{points[j], points[j + 1]};
}
/**
* Resets the path. All points and segments are destroyed.
*/
public void reset() {
typeSize = 0;
pointSize = 0;
dirty = true;
}
/**
* Returns the path's bounding box, in float precision.
*
* @return 4-element array of the form {@code [x, y, width, height]}.
*/
public float[] getBounds2D() {
float[] out = new float[4];
getBounds2D(out);
return out;
}
/**
* Sets the 4-element array to the bounding box coordinates of the path. x, y, width, height.
* @param out 4-element float[] array.
*/
public void getBounds2D(float[] out) {
float rx1, ry1, rx2, ry2;
if (pointSize == 0) {
rx1 = ry1 = rx2 = ry2 = 0.0f;
} else {
int i = pointSize - 1;
ry1 = ry2 = points[i--];
rx1 = rx2 = points[i--];
while (i > 0) {
float y = points[i--];
float x = points[i--];
if (x < rx1) {
rx1 = x;
} else if (x > rx2) {
rx2 = x;
}
if (y < ry1) {
ry1 = y;
} else if (y > ry2) {
ry2 = y;
}
}
}
out[0] = rx1;
out[1] = ry1;
out[2] = rx2-rx1;
out[3] = ry2-ry1;
}
/**
* Returns the path's bounding box.
*
* @return The bounding box of the path.
*/
public Rectangle getBounds() {
float[] r = getBounds2D();
int x1 = (int)Math.floor(r[0]);
int y1 = (int)Math.floor(r[1]);
int x2 = (int)Math.ceil(r[0]+r[2]);
int y2 = (int)Math.ceil(r[1]+r[3]);
return new Rectangle(x1, y1, x2-x1, y2-y1);
/*
float[] r = getBounds2D();
return new Rectangle((int)r[0], (int)r[1], (int)r[2], (int)r[3]);*/
}
/**
* Sets the coordinates of the provided rectangle to the bounding box of this path.
* @param out
*/
public void getBounds(Rectangle out) {
float rx1, ry1, rx2, ry2;
if (pointSize == 0) {
rx1 = ry1 = rx2 = ry2 = 0.0f;
} else {
int i = pointSize - 1;
ry1 = ry2 = points[i--];
rx1 = rx2 = points[i--];
while (i > 0) {
float y = points[i--];
float x = points[i--];
if (x < rx1) {
rx1 = x;
} else if (x > rx2) {
rx2 = x;
}
if (y < ry1) {
ry1 = y;
} else if (y > ry2) {
ry2 = y;
}
}
}
int x1 = (int)Math.floor(rx1);
int y1 = (int)Math.floor(ry1);
int x2 = (int)Math.ceil(rx2);
int y2 = (int)Math.ceil(ry2);
out.setX(x1);
out.setY(y1);
out.setWidth(x2-x1);
out.setHeight(y2-y1);
}
/**
* Checks to see if this path is a rectangle.
* @return True if this path forms a rectangle. False otherwise.
*/
public boolean isRectangle() {
float[] tmpPointsBuf = createFloatArrayFromPool(6);
boolean[] tmpCornersBuf = createBoolArrayFromPool(4);
Iterator it = createIteratorFromPool(this, null);
Rectangle bounds = createRectFromPool();
try {
getBounds(bounds);
if (tmpPointsBuf.length != 6) {
throw new RuntimeException("points buffer must be length 6");
}
float[] buf = tmpPointsBuf;
if (tmpCornersBuf.length != 4) {
throw new RuntimeException("corners buffer must be length 4");
}
boolean[] corners = tmpCornersBuf;
int prevX = 0;
int prevY = 0;
while ( !it.isDone() ){
int type = it.currentSegment(buf);
// Rectangulars only support moves, lines, and closes
if ( type != PathIterator.SEG_CLOSE && type != PathIterator.SEG_LINETO && type != PathIterator.SEG_MOVETO ){
return false;
}
// Get the current point
int x = (int)buf[0];
int y = (int)buf[1];
// Make sure there are no diagonal lines
if ( type == PathIterator.SEG_LINETO && !(x == prevX || y == prevY )){
return false;
}
// Make sure point is on the perimeter.
if ( x != bounds.getX() && y != bounds.getY() && x != bounds.getX()+bounds.getWidth() && y != bounds.getY()+bounds.getHeight() ){
return false;
}
// Make sure that all corners are accounted for.
for ( int i=0; i<4; i++){
if ( corners[i] ){
continue;
}
switch (i){
case 0:
corners[i] = (x == bounds.getX() && y == bounds.getY());
break;
case 1:
corners[i] = (x == bounds.getX()+bounds.getWidth() && y == bounds.getY());
break;
case 2:
corners[i] = (x == bounds.getX()+bounds.getWidth() && y == bounds.getY() + bounds.getHeight());
break;
case 3:
corners[i] = (x== bounds.getX() && y == bounds.getY()+bounds.getHeight());
break;
}
}
prevX = x;
prevY = y;
it.next();
}
return corners[0] && corners[1] && corners[2] && corners[3];
} finally {
recycle(tmpPointsBuf);
recycle(tmpCornersBuf);
recycle(it);
recycle(bounds);
}
}
/**
* {{@inheritDoc}}
*/
public PathIterator getPathIterator() {
return new Iterator(this);
}
/**
* {{@inheritDoc}}
*/
public PathIterator getPathIterator(Transform m) {
Iterator out = (Iterator) getPathIterator();
out.transform = m;
return out;
}
/**
* Returns a shape formed by transforming the current shape with the provided
* transform.
* <p>Note: If {@link com.codename1.ui.Transform#isSupported} is false, this may throw a RuntimeException.</p>
* @param m The transform to be used to transform the shape.
* @return The transformed shape.
*/
public Shape createTransformedShape(Transform m){
GeneralPath out = new GeneralPath();
out.setPath(this, m);
return out;
}
/**
* Sets this path to be identical to the provided path {@code p} with the given
* Transform {@code t} applied to it.
* @param p The path to copy.
* @param t The transform to apply to all points in the path.
*/
public void setPath(GeneralPath p, Transform t) {
dirty = true;
typeSize = p.typeSize;
pointSize = p.pointSize;
rule = p.rule;
if (points == null || points.length < pointSize) {
points = new float[pointSize];
}
if (types == null || types.length < typeSize) {
types = new byte[typeSize];
}
System.arraycopy(p.types, 0, types, 0, typeSize);
if (t == null || t.isIdentity()) {
System.arraycopy(p.points, 0, points, 0, pointSize);
} else {
t.transformPoints(2, p.points, 0, points, 0, pointSize / 2);
}
}
/**
* Sets this path to be a rectangle with the provided bounds, but with
* the given transform applied to it.
* @param r Rectangle to copy.
* @param t The transform to apply to the points in in {@code r}.
*/
public void setRect(Rectangle r, Transform t) {
reset();
int x = r.getX();
int y = r.getY();
Dimension size = r.getSize();
int w = size.getWidth();
int h = size.getHeight();
if (t == null) {
moveTo(x, y);
lineTo(x + w, y);
lineTo(x + w, y + h);
lineTo(x, y+ h);
closePath();
} else {
float[] pointBuffer = createFloatArrayFromPool(6);
try {
pointBuffer[0] = x;
pointBuffer[1] = y;
pointBuffer[2] = 0;
t.transformPoint(pointBuffer, pointBuffer);
moveTo(pointBuffer[0], pointBuffer[1]);
pointBuffer[0] = x+w;
pointBuffer[1] = y;
pointBuffer[2] = 0;
t.transformPoint(pointBuffer, pointBuffer);
lineTo(pointBuffer[0], pointBuffer[1]);
pointBuffer[0] = x+w;
pointBuffer[1] = y+h;
pointBuffer[2] = 0;
t.transformPoint(pointBuffer, pointBuffer);
lineTo(pointBuffer[0], pointBuffer[1]);
pointBuffer[0] = x;
pointBuffer[1] = y+h;
pointBuffer[2] = 0;
t.transformPoint(pointBuffer, pointBuffer);
lineTo(pointBuffer[0], pointBuffer[1]);
closePath();
} finally {
recycle(pointBuffer);
}
}
}
/**
* Sets this path to be a copy of the provided shape, but with the provided
* transform applied to it.
* @param s The shape to copy.
* @param t The transform to apply to all points in the shape.
*/
public void setShape(Shape s, Transform t) {
if (s.getClass() == GeneralPath.class) {
setPath((GeneralPath)s, t);
} else if (s.getClass() == Rectangle.class) {
setRect((Rectangle)s, t);
} else {
reset();
append(s.getPathIterator(t), false);
}
}
/**
* Sets the current path to the intersection of itself and the provided rectangle.
* @param rect The rectangle to intersect with this path.
* @return True if {@code rect} intersects the current path. False otherwise. If there is no intersection, the
* path will be reset to be empty.
*/
public boolean intersect(Rectangle rect) {
GeneralPath intersectionScratchPath = createPathFromPool();
try {
Shape result = ShapeUtil.intersection(rect, this, intersectionScratchPath);
if (result != null) {
this.setPath(intersectionScratchPath, null);
return true;
}
reset();
return false;
} finally {
recycle(intersectionScratchPath);
}
}
public boolean intersect(int x, int y, int w, int h) {
Rectangle r = createRectFromPool();
try {
r.setBounds(x, y, w, h);
return intersect(r);
} finally {
recycle(r);
}
}
/**
* Transforms the current path in place using the given transform.
* @param m The transform to apply to the path.
*/
public void transform(Transform m) {
if (m != null && !m.isIdentity()) {
m.transformPoints(2, points, 0, points, 0, pointSize / 2 );
}
}
/**
* Resets this path to be the intersection of itself with the given shape. Note that only
* {@link com.codename1.ui.geom.Rectangle}s are current supported. If you pass any other
* shape, it will throw a RuntimeException.
* <p>Note: If {@link com.codename1.ui.TransformisSupported} is false, this will throw a Runtime Exception</p>
* @param shape The shape to intersect with the current shape.
*/
public void intersect(Shape shape) {
//Log.p("Start intersect");
if ( !(shape instanceof Rectangle) ){
throw new RuntimeException("GeneralPath.intersect() only supports Rectangles");
}
intersect((Rectangle)shape);
}
/**
* {{@inheritDoc}}
*/
public Shape intersection(Rectangle rect){
Shape out = ShapeUtil.intersection(rect, this);
if (out == null) {
return new Rectangle(rect.getX(), rect.getY(), 0, 0);
}
return out;
}
/**
* Checks cross count according to path rule to define is it point inside shape or not.
* @param cross - the point cross count
* @return true if point is inside path, or false otherwise
*/
boolean isInside(int cross) {
if (rule == WIND_NON_ZERO) {
return ShapeUtil.isInsideNonZero(cross);
}
return ShapeUtil.isInsideEvenOdd(cross);
}
/**
* Checks if the given point is contained in the current shape.
* @param x The x coordinate to check
* @param y The y coordinate to check
* @return True if the point is inside the shape.
*/
public boolean contains(float x, float y) {
return isInside(ShapeUtil.crossShape(this, x, y));
}
/**
* {{@inheritDoc}}
*/
public boolean contains(int x, int y){
return contains((float)x, (float)y);
}
/**
*
* @author shannah
*/
private static class ShapeUtil {
/**
* Generates the intersection of a given shape and a given rectangle. Only supported convex polygons.
*
* @param r A rectangle.
* @param s A shape
* @return The shape that is the intersected area of the shape and
* rectangle.
*/
static Shape intersection(Rectangle r, Shape s) {
return intersection(r, s, new GeneralPath());
}
private static Shape intersection(Rectangle r, Shape s, GeneralPath out) {
Shape segmentedShape = segmentShape(r, s);
Iterator it = createIteratorFromPool((GeneralPath)segmentedShape, null);
//GeneralPath out = new GeneralPath();
float[] buf = createFloatArrayFromPool(6);//new float[6];
try {
boolean started = false;
float x1 = r.getX();
float x2 = r.getX() + r.getWidth();
float y1 = r.getY();
float y2 = r.getY() + r.getHeight();
float minX = -1;
float minY = -1;
float maxX = -1;
float maxY = -1;
float prevX=0;
float prevY=0;
while (!it.isDone()) {
int type = it.currentSegment(buf);
switch (type) {
case PathIterator.SEG_CLOSE:
//System.out.println("Closing path");
out.closePath();
break;
case PathIterator.SEG_MOVETO:
case PathIterator.SEG_LINETO:
if (buf[0] < x1) {
buf[0] = x1;
} else if (buf[0] > x2) {
buf[0] = x2;
}
if (buf[1] < y1) {
buf[1] = y1;
} else if (buf[1] > y2) {
buf[1] = y2;
}
if (!started || (buf[0] < minX)) {
minX = buf[0];
}
if (!started || (buf[0] > maxX)) {
maxX = buf[0];
}
if (!started || (buf[1] < minY)) {
minY = buf[1];
}
if (!started || (buf[1] > maxY)) {
maxY = buf[1];
}
if (type == PathIterator.SEG_MOVETO) {
//System.out.println("Moving to "+buf[0]+","+buf[1]);
out.moveTo(buf[0], buf[1]);
} else { // type == PathITerator.SEG_LINETO
if ( prevX != buf[0] || prevY != buf[1]){
//System.out.println("Line to "+buf[0]+","+buf[1]);
out.lineTo(buf[0], buf[1]);
}
}
prevX = buf[0];
prevY = buf[1];
started = true;
//count++;
break;
default:
throw new RuntimeException("Intersection only supports polygons currently");
}
it.next();
}
if (maxX - minX <= 1f || maxY - minY <= 1f) {
return null;
}
return out;
} finally {
recycle(it);
recycle(buf);
}
}
/**
* Segments a given shape so that all points of the shape that intersect the
* provided rectangle edges are nodes of the shape path. This operation
* makes it easier to form the intersection.
*
* Only supports convex polygons.
*
* @param r A rectangle.
* @param s A shape
* @return A shape that is identical to the input shape except that it may
* include additional path segments so that all points of intersection are
* start/end points of a segment.
*/
static Shape segmentShape(Rectangle r, Shape s) {
return segmentShape(r, s, new GeneralPath());
}
private static GeneralPath segmentShape(Rectangle r, Shape s, GeneralPath out) {
GeneralPath tmpGeneralPath = null;
if (s.getClass() != GeneralPath.class) {
tmpGeneralPath = createPathFromPool();
tmpGeneralPath.setShape(s, null);
s = tmpGeneralPath;
}
Iterator it = createIteratorFromPool((GeneralPath)s, null);
//GeneralPath out = new GeneralPath();
float[] buf = createFloatArrayFromPool(6); // buffer to hold segment coordinates from PathIterator.currentSegment
float[] curr = createFloatArrayFromPool(2); // Placeholder for current point
float[] prev = createFloatArrayFromPool(2); // Placeholder for previous point
float[] mark = createFloatArrayFromPool(2); // Placeholder for the moveTo point
//float[] buf4 = new float[4]; // Reusable buffer to hold two points.
float[] intersects = createFloatArrayFromPool(9);
try {
float prevX = -1; // Placeholder for previous X coord.
float prevY = -1; // Placeholder for previous Y coord.
float currX = 0; // Placeholder for current X coord.
float currY = 0; // Placeholder for current Y coord.
//float[] intersects = null; // Placeholder for intersection points
while (!it.isDone()) {
int type = it.currentSegment(buf);
switch (type) {
case PathIterator.SEG_MOVETO:
// Move to segment is transferred straight through
prevX = prev[0] = mark[0] = buf[0];
prevY = prev[1] = mark[1] = buf[1];
out.moveTo(prevX, prevY);
//System.out.println("Moving to "+prevX+","+prevY);
break;
case PathIterator.SEG_LINETO:
// Line Segment may need to be partitioned if it crosses
// an edge of the rectangle.
currX = curr[0] = buf[0];
currY = curr[1] = buf[1];
// Check if line intersects rectangle
intersectLineWithRectAsHash(prevX, prevY, currX, currY, r, intersects);
//System.out.println("Looking for intersections between "+prevX+","+prevY+" and "+currX+","+currY);
//System.out.println("Intersects: "+intersects[0]+", "+intersects[1]+" "+intersects[2]+","+intersects[3]);
if (intersects[8] >= 1) {
int num = (int)intersects[8];
int len = num*2;
for ( int i=0; i<len; i+=2){
out.lineTo(intersects[i], intersects[i+1]);
}
}
//System.out.println("Line to "+currX+","+currY);
out.lineTo(currX, currY);
// Set current position to prev for next iteration.
prevX = currX;
prevY = currY;
float[] tmp = curr;
curr = prev;
prev = tmp;
break;
case PathIterator.SEG_CLOSE:
// Closing the path. Need to check if there is an intersection
// on this last closing path.
currX = curr[0] = mark[0];
currY = curr[1] = mark[1];
intersectLineWithRectAsHash(prevX, prevY, currX, currY, r, intersects);
if (intersects[8] >= 1) {
int num = (int)intersects[8];
int len = num*2;
for ( int i=0; i<len; i+=2){
out.lineTo(intersects[i], intersects[i+1]);
}
}
out.closePath();
break;
default:
throw new RuntimeException("Shape segmentation only supported for polygons");
}
it.next();
}
return out;
} finally {
recycle(it);
recycle(buf);
recycle(curr);
recycle(prev);
recycle(mark);
recycle(intersects);
recycle(tmpGeneralPath);
}
}
private static float[] intersectLineWithRectAsHash(float x1, float y1, float x2, float y2, Rectangle rect,
float[] out
){
//float[] out = new float[9]; // max 4 points here
float[] x = createFloatArrayFromPool(4);
try {
//float[] y = new float[4];
float rx1 = rect.getX();
float ry1 = rect.getY();
float rx2 = rect.getX()+rect.getWidth();
float ry2 = rect.getY()+rect.getHeight();
float dx = x2-x1;
float dy = y2-y1;
int num=0;
float minY = Math.min(y1,y2);
float maxY = Math.max(y1,y2);
float minX = Math.min(x1, x2);
float maxX = Math.max(x1, x2);
int i = 0;
if ( dx == 0 ){
if ( ry1 > minY && ry1 < maxY ){
num++;
x[i++] = ry1;
//out[i++] = ry1;
}
if ( ry2 > minY && ry2 < maxY ){
num++;
x[i++] = ry2;
//out[i++] = ry2;
}
Arrays.sort(x, 0, num);
if ( y1 <= y2 ){
for ( i=0; i<num; i++){
int j = 2*i;
out[j] = x1;
out[j+1] = x[i];
}
} else {
for ( i=0; i<num; i++){
int j = 2*(num-i-1);
out[j] = x1;
out[j+1] = x[i];
}
}
out[8] = num;
} else if ( dy == 0 ){
if ( rx1 > minX && rx1 < maxX ){
num++;
x[i++] = rx1;
//out[i++] = y1;
}
if ( rx2 > minX && rx2 < maxX ){
num++;
x[i++] = rx2;
//out[i++] = y1;
}
Arrays.sort(x, 0, num);
if ( x1 <= x2 ){
for ( i=0; i<num; i++){
int j = 2*i;
out[j] = x[i];
out[j+1] = y1;
}
} else {
for ( i=0; i<num; i++){
int j = 2*(num-i-1);
out[j] = x[i];
out[j+1] = y1;
}
}
out[8] = num;
} else {
float m = dy/dx;
if ( rx1 > minX && rx1 < maxX ){
num++;
x[i] = rx1;
//y[i] = y1+(rx1-x1)*m;
i++;
}
if ( rx2 > minX && rx2 < maxX ){
num++;
x[i] = rx2;
//y[i++] = y1+(rx2-x1)*m;
i++;
}
if ( ry1 > minY && ry1 < maxY ){
num++;
x[i] = x1+(ry1-y1)/m;
//out[i++] = ry1;
i++;
}
if ( ry2 > minY && ry2 < maxY ){
num++;
x[i] = x1+(ry2-y1)/m;
//out[i++] = ry2;
i++;
}
Arrays.sort(x, 0, num);
if ( x1 < x2 ){
for ( i=0; i<num; i++){
int j = 2*i;
out[j] = x[i];
out[j+1] = y1 + (x[i]-x1)*m;
}
} else {
for ( i=0; i<num; i++){
int j = 2*(num-i-1);
out[j] = x[i];
out[j+1] = y1 + (x[i]-x1)*m;
}
}
out[8] = num;
}
return out;
} finally {
recycle(x);
}
}
/**
* Allowable tolerance for bounds comparison
*/
static final double DELTA = 1E-5;
/**
* If roots have distance less then <code>ROOT_DELTA</code> they are double
*/
static final double ROOT_DELTA = 1E-10;
/**
* Rectangle cross segment
*/
public static final int CROSSING = 255;
/**
* Unknown crossing result
*/
static final int UNKNOWN = 254;
/**
* Solves quadratic equation
* @param eqn - the coefficients of the equation
* @param res - the roots of the equation
* @return a number of roots
*/
public static int solveQuad(double eqn[], double res[]) {
double a = eqn[2];
double b = eqn[1];
double c = eqn[0];
int rc = 0;
if (a == 0.0) {
if (b == 0.0) {
return -1;
}
res[rc++] = -c / b;
} else {
double d = b * b - 4.0 * a * c;
// d < 0.0
if (d < 0.0) {
return 0;
}
d = Math.sqrt(d);
res[rc++] = (- b + d) / (a * 2.0);
// d != 0.0
if (d != 0.0) {
res[rc++] = (- b - d) / (a * 2.0);
}
}
return fixRoots(res, rc);
}
/**
* Solves cubic equation
* @param eqn - the coefficients of the equation
* @param res - the roots of the equation
* @return a number of roots
*/
public static int solveCubic(double eqn[], double res[]) {
double d = eqn[3];
if (d == 0) {
return solveQuad(eqn, res);
}
double a = eqn[2] / d;
double b = eqn[1] / d;
double c = eqn[0] / d;
int rc = 0;
double Q = (a * a - 3.0 * b) / 9.0;
double R = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0;
double Q3 = Q * Q * Q;
double R2 = R * R;
double n = - a / 3.0;
if (R2 < Q3) {
double t = MathUtil.acos(R / Math.sqrt(Q3)) / 3.0;
double p = 2.0 * Math.PI / 3.0;
double m = -2.0 * Math.sqrt(Q);
res[rc++] = m * Math.cos(t) + n;
res[rc++] = m * Math.cos(t + p) + n;
res[rc++] = m * Math.cos(t - p) + n;
} else {
// Debug.println("R2 >= Q3 (" + R2 + "/" + Q3 + ")");
double A = MathUtil.pow(Math.abs(R) + Math.sqrt(R2 - Q3), 1.0 / 3.0);
if (R > 0.0) {
A = -A;
}
// if (A == 0.0) {
if (-ROOT_DELTA < A && A < ROOT_DELTA) {
res[rc++] = n;
} else {
double B = Q / A;
res[rc++] = A + B + n;
// if (R2 == Q3) {
double delta = R2 - Q3;
if (-ROOT_DELTA < delta && delta < ROOT_DELTA) {
res[rc++] = - (A + B) / 2.0 + n;
}
}
}
return fixRoots(res, rc);
}
/**
* Excludes double roots. Roots are double if they lies enough close with each other.
* @param res - the roots
* @param rc - the roots count
* @return new roots count
*/
static int fixRoots(double res[], int rc) {
int tc = 0;
for(int i = 0; i < rc; i++) {
out: {
for(int j = i + 1; j < rc; j++) {
if (isZero(res[i] - res[j])) {
break out;
}
}
res[tc++] = res[i];
}
}
return tc;
}
/**
* QuadCurve class provides basic functionality to find curve crossing and calculating bounds
*/
public static class QuadCurve {
double ax, ay, bx, by;
double Ax, Ay, Bx, By;
public QuadCurve(double x1, double y1, double cx, double cy, double x2, double y2) {
ax = x2 - x1;
ay = y2 - y1;
bx = cx - x1;
by = cy - y1;
Bx = bx + bx; // Bx = 2.0 * bx
Ax = ax - Bx; // Ax = ax - 2.0 * bx
By = by + by; // By = 2.0 * by
Ay = ay - By; // Ay = ay - 2.0 * by
}
int cross(double res[], int rc, double py1, double py2) {
int cross = 0;
for (int i = 0; i < rc; i++) {
double t = res[i];
// CURVE-OUTSIDE
if (t < -DELTA || t > 1 + DELTA) {
continue;
}
// CURVE-START
if (t < DELTA) {
if (py1 < 0.0 && (bx != 0.0 ? bx : ax - bx) < 0.0) {
cross--;
}
continue;
}
// CURVE-END
if (t > 1 - DELTA) {
if (py1 < ay && (ax != bx ? ax - bx : bx) > 0.0) {
cross++;
}
continue;
}
// CURVE-INSIDE
double ry = t * (t * Ay + By);
// ry = t * t * Ay + t * By
if (ry > py2) {
double rxt = t * Ax + bx;
// rxt = 2.0 * t * Ax + Bx = 2.0 * t * Ax + 2.0 * bx
if (rxt > -DELTA && rxt < DELTA) {
continue;
}
cross += rxt > 0.0 ? 1 : -1;
}
} // for
return cross;
}
int solvePoint(double res[], double px) {
double eqn[] = {-px, Bx, Ax};
return solveQuad(eqn, res);
}
int solveExtrem(double res[]) {
int rc = 0;
if (Ax != 0.0) {
res[rc++] = - Bx / (Ax + Ax);
}
if (Ay != 0.0) {
res[rc++] = - By / (Ay + Ay);
}
return rc;
}
int addBound(double bound[], int bc, double res[], int rc, double minX, double maxX, boolean changeId, int id) {
for(int i = 0; i < rc; i++) {
double t = res[i];
if (t > -DELTA && t < 1 + DELTA) {
double rx = t * (t * Ax + Bx);
if (minX <= rx && rx <= maxX) {
bound[bc++] = t;
bound[bc++] = rx;
bound[bc++] = t * (t * Ay + By);
bound[bc++] = id;
if (changeId) {
id++;
}
}
}
}
return bc;
}
}
/**
* CubicCurve class provides basic functionality to find curve crossing and calculating bounds
*/
public static class CubicCurve {
double ax, ay, bx, by, cx, cy;
double Ax, Ay, Bx, By, Cx, Cy;
double Ax3, Bx2;
public CubicCurve(double x1, double y1, double cx1, double cy1, double cx2, double cy2, double x2, double y2) {
ax = x2 - x1;
ay = y2 - y1;
bx = cx1 - x1;
by = cy1 - y1;
cx = cx2 - x1;
cy = cy2 - y1;
Cx = bx + bx + bx; // Cx = 3.0 * bx
Bx = cx + cx + cx - Cx - Cx; // Bx = 3.0 * cx - 6.0 * bx
Ax = ax - Bx - Cx; // Ax = ax - 3.0 * cx + 3.0 * bx
Cy = by + by + by; // Cy = 3.0 * by
By = cy + cy + cy - Cy - Cy; // By = 3.0 * cy - 6.0 * by
Ay = ay - By - Cy; // Ay = ay - 3.0 * cy + 3.0 * by
Ax3 = Ax + Ax + Ax;
Bx2 = Bx + Bx;
}
int cross(double res[], int rc, double py1, double py2) {
int cross = 0;
for (int i = 0; i < rc; i++) {
double t = res[i];
// CURVE-OUTSIDE
if (t < -DELTA || t > 1 + DELTA) {
continue;
}
// CURVE-START
if (t < DELTA) {
if (py1 < 0.0 && (bx != 0.0 ? bx : (cx != bx ? cx - bx : ax - cx)) < 0.0) {
cross--;
}
continue;
}
// CURVE-END
if (t > 1 - DELTA) {
if (py1 < ay && (ax != cx ? ax - cx : (cx != bx ? cx - bx : bx)) > 0.0) {
cross++;
}
continue;
}
// CURVE-INSIDE
double ry = t * (t * (t * Ay + By) + Cy);
// ry = t * t * t * Ay + t * t * By + t * Cy
if (ry > py2) {
double rxt = t * (t * Ax3 + Bx2) + Cx;
// rxt = 3.0 * t * t * Ax + 2.0 * t * Bx + Cx
if (rxt > -DELTA && rxt < DELTA) {
rxt = t * (Ax3 + Ax3) + Bx2;
// rxt = 6.0 * t * Ax + 2.0 * Bx
if (rxt < -DELTA || rxt > DELTA) {
// Inflection point
continue;
}
rxt = ax;
}
cross += rxt > 0.0 ? 1 : -1;
}
} //for
return cross;
}
int solvePoint(double res[], double px) {
double eqn[] = {-px, Cx, Bx, Ax};
return solveCubic(eqn, res);
}
int solveExtremX(double res[]) {
double eqn[] = {Cx, Bx2, Ax3};
return solveQuad(eqn, res);
}
int solveExtremY(double res[]) {
double eqn[] = {Cy, By + By, Ay + Ay + Ay};
return solveQuad(eqn, res);
}
int addBound(double bound[], int bc, double res[], int rc, double minX, double maxX, boolean changeId, int id) {
for(int i = 0; i < rc; i++) {
double t = res[i];
if (t > -DELTA && t < 1 + DELTA) {
double rx = t * (t * (t * Ax + Bx) + Cx);
if (minX <= rx && rx <= maxX) {
bound[bc++] = t;
bound[bc++] = rx;
bound[bc++] = t * (t * (t * Ay + By) + Cy);
bound[bc++] = id;
if (changeId) {
id++;
}
}
}
}
return bc;
}
}
/**
* Returns how many times ray from point (x,y) cross line.
*/
public static int crossLine(double x1, double y1, double x2, double y2, double x, double y) {
// LEFT/RIGHT/UP/EMPTY
if ((x < x1 && x < x2) ||
(x > x1 && x > x2) ||
(y > y1 && y > y2) ||
(x1 == x2))
{
return 0;
}
// DOWN
if (y < y1 && y < y2) {
} else {
// INSIDE
if ((y2 - y1) * (x - x1) / (x2 - x1) <= y - y1) {
// INSIDE-UP
return 0;
}
}
// START
if (x == x1) {
return x1 < x2 ? 0 : -1;
}
// END
if (x == x2) {
return x1 < x2 ? 1 : 0;
}
// INSIDE-DOWN
return x1 < x2 ? 1 : -1;
}
/**
* Returns how many times ray from point (x,y) cross quard curve
*/
public static int crossQuad(double x1, double y1, double cx, double cy, double x2, double y2, double x, double y) {
// LEFT/RIGHT/UP/EMPTY
if ((x < x1 && x < cx && x < x2) ||
(x > x1 && x > cx && x > x2) ||
(y > y1 && y > cy && y > y2) ||
(x1 == cx && cx == x2))
{
return 0;
}
// DOWN
if (y < y1 && y < cy && y < y2 && x != x1 && x != x2) {
if (x1 < x2) {
return x1 < x && x < x2 ? 1 : 0;
}
return x2 < x && x < x1 ? -1 : 0;
}
// INSIDE
QuadCurve c = new QuadCurve(x1, y1, cx, cy, x2, y2);
double px = x - x1;
double py = y - y1;
double res[] = new double[3];
int rc = c.solvePoint(res, px);
return c.cross(res, rc, py, py);
}
/**
* Returns how many times ray from point (x,y) cross cubic curve
*/
public static int crossCubic(double x1, double y1, double cx1, double cy1, double cx2, double cy2, double x2, double y2, double x, double y) {
// LEFT/RIGHT/UP/EMPTY
if ((x < x1 && x < cx1 && x < cx2 && x < x2) ||
(x > x1 && x > cx1 && x > cx2 && x > x2) ||
(y > y1 && y > cy1 && y > cy2 && y > y2) ||
(x1 == cx1 && cx1 == cx2 && cx2 == x2))
{
return 0;
}
// DOWN
if (y < y1 && y < cy1 && y < cy2 && y < y2 && x != x1 && x != x2) {
if (x1 < x2) {
return x1 < x && x < x2 ? 1 : 0;
}
return x2 < x && x < x1 ? -1 : 0;
}
// INSIDE
CubicCurve c = new CubicCurve(x1, y1, cx1, cy1, cx2, cy2, x2, y2);
double px = x - x1;
double py = y - y1;
double res[] = new double[3];
int rc = c.solvePoint(res, px);
return c.cross(res, rc, py, py);
}
/**
* Returns how many times ray from point (x,y) cross path
*/
public static int crossPath(PathIterator p, double x, double y) {
int cross = 0;
double mx, my, cx, cy;
mx = my = cx = cy = 0.0;
double coords[] = new double[6];
while (!p.isDone()) {
switch (p.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
if (cx != mx || cy != my) {
cross += crossLine(cx, cy, mx, my, x, y);
}
mx = cx = coords[0];
my = cy = coords[1];
break;
case PathIterator.SEG_LINETO:
cross += crossLine(cx, cy, cx = coords[0], cy = coords[1], x, y);
break;
case PathIterator.SEG_QUADTO:
cross += crossQuad(cx, cy, coords[0], coords[1], cx = coords[2], cy = coords[3], x, y);
break;
case PathIterator.SEG_CUBICTO:
cross += crossCubic(cx, cy, coords[0], coords[1], coords[2], coords[3], cx = coords[4], cy = coords[5], x, y);
break;
case PathIterator.SEG_CLOSE:
if (cy != my || cx != mx) {
cross += crossLine(cx, cy, cx = mx, cy = my, x, y);
}
break;
}
// checks if the point (x,y) is the vertex of shape with PathIterator p
if (x == cx && y == cy) {
cross = 0;
cy = my;
break;
}
p.next();
}
if (cy != my) {
cross += crossLine(cx, cy, mx, my, x, y);
}
return cross;
}
/**
* Returns how many times ray from point (x,y) cross shape
*/
public static int crossShape(Shape s, double x, double y) {
if (!s.getBounds().contains((int)x, (int)y)) {
return 0;
}
return crossPath(s.getPathIterator(null), x, y);
}
/**
* Returns true if value enough small
*/
public static boolean isZero(double val) {
return -DELTA < val && val < DELTA;
}
/**
* Sort bound array
*/
static void sortBound(double bound[], int bc) {
for(int i = 0; i < bc - 4; i += 4) {
int k = i;
for(int j = i + 4; j < bc; j += 4) {
if (bound[k] > bound[j]) {
k = j;
}
}
if (k != i) {
double tmp = bound[i];
bound[i] = bound[k];
bound[k] = tmp;
tmp = bound[i + 1];
bound[i + 1] = bound[k + 1];
bound[k + 1] = tmp;
tmp = bound[i + 2];
bound[i + 2] = bound[k + 2];
bound[k + 2] = tmp;
tmp = bound[i + 3];
bound[i + 3] = bound[k + 3];
bound[k + 3] = tmp;
}
}
}
/**
* Returns are bounds intersect or not intersect rectangle
*/
static int crossBound(double bound[], int bc, double py1, double py2) {
// LEFT/RIGHT
if (bc == 0) {
return 0;
}
// Check Y coordinate
int up = 0;
int down = 0;
for(int i = 2; i < bc; i += 4) {
if (bound[i] < py1) {
up++;
continue;
}
if (bound[i] > py2) {
down++;
continue;
}
return CROSSING;
}
// UP
if (down == 0) {
return 0;
}
if (up != 0) {
// bc >= 2
sortBound(bound, bc);
boolean sign = bound[2] > py2;
for(int i = 6; i < bc; i += 4) {
boolean sign2 = bound[i] > py2;
if (sign != sign2 && bound[i + 1] != bound[i - 3]) {
return CROSSING;
}
sign = sign2;
}
}
return UNKNOWN;
}
/**
* Returns how many times rectangle stripe cross line or the are intersect
*/
public static int intersectLine(double x1, double y1, double x2, double y2, double rx1, double ry1, double rx2, double ry2) {
// LEFT/RIGHT/UP
if ((rx2 < x1 && rx2 < x2) ||
(rx1 > x1 && rx1 > x2) ||
(ry1 > y1 && ry1 > y2))
{
return 0;
}
// DOWN
if (ry2 < y1 && ry2 < y2) {
} else {
// INSIDE
if (x1 == x2) {
return CROSSING;
}
// Build bound
double bx1, bx2;
if (x1 < x2) {
bx1 = x1 < rx1 ? rx1 : x1;
bx2 = x2 < rx2 ? x2 : rx2;
} else {
bx1 = x2 < rx1 ? rx1 : x2;
bx2 = x1 < rx2 ? x1 : rx2;
}
double k = (y2 - y1) / (x2 - x1);
double by1 = k * (bx1 - x1) + y1;
double by2 = k * (bx2 - x1) + y1;
// BOUND-UP
if (by1 < ry1 && by2 < ry1) {
return 0;
}
// BOUND-DOWN
if (by1 > ry2 && by2 > ry2) {
} else {
return CROSSING;
}
}
// EMPTY
if (x1 == x2) {
return 0;
}
// CURVE-START
if (rx1 == x1) {
return x1 < x2 ? 0 : -1;
}
// CURVE-END
if (rx1 == x2) {
return x1 < x2 ? 1 : 0;
}
if (x1 < x2) {
return x1 < rx1 && rx1 < x2 ? 1 : 0;
}
return x2 < rx1 && rx1 < x1 ? -1 : 0;
}
/**
* Returns how many times rectangle stripe cross quad curve or the are intersect
*/
public static int intersectQuad(double x1, double y1, double cx, double cy, double x2, double y2, double rx1, double ry1, double rx2, double ry2) {
// LEFT/RIGHT/UP ------------------------------------------------------
if ((rx2 < x1 && rx2 < cx && rx2 < x2) ||
(rx1 > x1 && rx1 > cx && rx1 > x2) ||
(ry1 > y1 && ry1 > cy && ry1 > y2))
{
return 0;
}
// DOWN ---------------------------------------------------------------
if (ry2 < y1 && ry2 < cy && ry2 < y2 && rx1 != x1 && rx1 != x2) {
if (x1 < x2) {
return x1 < rx1 && rx1 < x2 ? 1 : 0;
}
return x2 < rx1 && rx1 < x1 ? -1 : 0;
}
// INSIDE -------------------------------------------------------------
QuadCurve c = new QuadCurve(x1, y1, cx, cy, x2, y2);
double px1 = rx1 - x1;
double py1 = ry1 - y1;
double px2 = rx2 - x1;
double py2 = ry2 - y1;
double res1[] = new double[3];
double res2[] = new double[3];
int rc1 = c.solvePoint(res1, px1);
int rc2 = c.solvePoint(res2, px2);
// INSIDE-LEFT/RIGHT
if (rc1 == 0 && rc2 == 0) {
return 0;
}
// Build bound --------------------------------------------------------
double minX = px1 - DELTA;
double maxX = px2 + DELTA;
double bound[] = new double[28];
int bc = 0;
// Add roots
bc = c.addBound(bound, bc, res1, rc1, minX, maxX, false, 0);
bc = c.addBound(bound, bc, res2, rc2, minX, maxX, false, 1);
// Add extremal points`
rc2 = c.solveExtrem(res2);
bc = c.addBound(bound, bc, res2, rc2, minX, maxX, true, 2);
// Add start and end
if (rx1 < x1 && x1 < rx2) {
bound[bc++] = 0.0;
bound[bc++] = 0.0;
bound[bc++] = 0.0;
bound[bc++] = 4;
}
if (rx1 < x2 && x2 < rx2) {
bound[bc++] = 1.0;
bound[bc++] = c.ax;
bound[bc++] = c.ay;
bound[bc++] = 5;
}
// End build bound ----------------------------------------------------
int cross = crossBound(bound, bc, py1, py2);
if (cross != UNKNOWN) {
return cross;
}
return c.cross(res1, rc1, py1, py2);
}
/**
* Returns how many times rectangle stripe cross cubic curve or the are intersect
*/
public static int intersectCubic(double x1, double y1, double cx1, double cy1, double cx2, double cy2, double x2, double y2, double rx1, double ry1, double rx2, double ry2) {
// LEFT/RIGHT/UP
if ((rx2 < x1 && rx2 < cx1 && rx2 < cx2 && rx2 < x2) ||
(rx1 > x1 && rx1 > cx1 && rx1 > cx2 && rx1 > x2) ||
(ry1 > y1 && ry1 > cy1 && ry1 > cy2 && ry1 > y2))
{
return 0;
}
// DOWN
if (ry2 < y1 && ry2 < cy1 && ry2 < cy2 && ry2 < y2 && rx1 != x1 && rx1 != x2) {
if (x1 < x2) {
return x1 < rx1 && rx1 < x2 ? 1 : 0;
}
return x2 < rx1 && rx1 < x1 ? -1 : 0;
}
// INSIDE
CubicCurve c = new CubicCurve(x1, y1, cx1, cy1, cx2, cy2, x2, y2);
double px1 = rx1 - x1;
double py1 = ry1 - y1;
double px2 = rx2 - x1;
double py2 = ry2 - y1;
double res1[] = new double[3];
double res2[] = new double[3];
int rc1 = c.solvePoint(res1, px1);
int rc2 = c.solvePoint(res2, px2);
// LEFT/RIGHT
if (rc1 == 0 && rc2 == 0) {
return 0;
}
double minX = px1 - DELTA;
double maxX = px2 + DELTA;
// Build bound --------------------------------------------------------
double bound[] = new double[40];
int bc = 0;
// Add roots
bc = c.addBound(bound, bc, res1, rc1, minX, maxX, false, 0);
bc = c.addBound(bound, bc, res2, rc2, minX, maxX, false, 1);
// Add extrimal points
rc2 = c.solveExtremX(res2);
bc = c.addBound(bound, bc, res2, rc2, minX, maxX, true, 2);
rc2 = c.solveExtremY(res2);
bc = c.addBound(bound, bc, res2, rc2, minX, maxX, true, 4);
// Add start and end
if (rx1 < x1 && x1 < rx2) {
bound[bc++] = 0.0;
bound[bc++] = 0.0;
bound[bc++] = 0.0;
bound[bc++] = 6;
}
if (rx1 < x2 && x2 < rx2) {
bound[bc++] = 1.0;
bound[bc++] = c.ax;
bound[bc++] = c.ay;
bound[bc++] = 7;
}
// End build bound ----------------------------------------------------
int cross = crossBound(bound, bc, py1, py2);
if (cross != UNKNOWN) {
return cross;
}
return c.cross(res1, rc1, py1, py2);
}
/**
* Returns how many times rectangle stripe cross path or the are intersect
*/
public static int intersectPath(PathIterator p, double x, double y, double w, double h) {
int cross = 0;
int count;
double mx, my, cx, cy;
mx = my = cx = cy = 0.0;
double coords[] = new double[6];
double rx1 = x;
double ry1 = y;
double rx2 = x + w;
double ry2 = y + h;
while (!p.isDone()) {
count = 0;
switch (p.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
if (cx != mx || cy != my) {
count = intersectLine(cx, cy, mx, my, rx1, ry1, rx2, ry2);
}
mx = cx = coords[0];
my = cy = coords[1];
break;
case PathIterator.SEG_LINETO:
count = intersectLine(cx, cy, cx = coords[0], cy = coords[1], rx1, ry1, rx2, ry2);
break;
case PathIterator.SEG_QUADTO:
count = intersectQuad(cx, cy, coords[0], coords[1], cx = coords[2], cy = coords[3], rx1, ry1, rx2, ry2);
break;
case PathIterator.SEG_CUBICTO:
count = intersectCubic(cx, cy, coords[0], coords[1], coords[2], coords[3], cx = coords[4], cy = coords[5], rx1, ry1, rx2, ry2);
break;
case PathIterator.SEG_CLOSE:
if (cy != my || cx != mx) {
count = intersectLine(cx, cy, mx, my, rx1, ry1, rx2, ry2);
}
cx = mx;
cy = my;
break;
}
if (count == CROSSING) {
return CROSSING;
}
cross += count;
p.next();
}
if (cy != my) {
count = intersectLine(cx, cy, mx, my, rx1, ry1, rx2, ry2);
if (count == CROSSING) {
return CROSSING;
}
cross += count;
}
return cross;
}
/**
* Returns how many times rectangle stripe cross shape or the are intersect
*/
public static int intersectShape(Shape s, double x, double y, double w, double h) {
if (!s.getBounds().intersects((int)x, (int)y, (int)w, (int)h)) {
return 0;
}
return intersectPath(s.getPathIterator(null), x, y, w, h);
}
/**
* Returns true if cross count correspond inside location for non zero path rule
*/
public static boolean isInsideNonZero(int cross) {
return cross != 0;
}
/**
* Returns true if cross count correspond inside location for even-odd path rule
*/
public static boolean isInsideEvenOdd(int cross) {
return (cross & 1) != 0;
}
//}
}
}