From 18fc81fab7ba11ae3a4cdd1e94c199371f7c2e91 Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Mon, 6 Feb 2023 02:31:14 +0100 Subject: Graph: Path2D -> self-contained Path2D (w/ Iterator) fixed; OutlineShape: Add Path2F addPath(..), emphasize required Winding.CW GPURegionGLListener01 used by TestRegionRendererNEWT01 covers Path2F CCW and CW (reverse add) methods. --- .../com/jogamp/graph/curve/OutlineShape.java | 257 +++++- .../classes/com/jogamp/graph/geom/Outline.java | 1 - .../jogamp/graph/geom/plane/AffineTransform.java | 2 +- .../com/jogamp/graph/geom/plane/Crossing.java | 904 --------------------- .../com/jogamp/graph/geom/plane/Crossing2F.java | 898 ++++++++++++++++++++ .../com/jogamp/graph/geom/plane/Path2D.java | 454 ----------- .../com/jogamp/graph/geom/plane/Path2F.java | 603 ++++++++++++++ .../com/jogamp/graph/geom/plane/PathIterator.java | 60 -- .../com/jogamp/graph/geom/plane/Winding.java | 18 + .../com/jogamp/graph/geom/plane/WindingRule.java | 28 + 10 files changed, 1804 insertions(+), 1421 deletions(-) delete mode 100644 src/jogl/classes/com/jogamp/graph/geom/plane/Crossing.java create mode 100644 src/jogl/classes/com/jogamp/graph/geom/plane/Crossing2F.java delete mode 100644 src/jogl/classes/com/jogamp/graph/geom/plane/Path2D.java create mode 100644 src/jogl/classes/com/jogamp/graph/geom/plane/Path2F.java delete mode 100644 src/jogl/classes/com/jogamp/graph/geom/plane/PathIterator.java create mode 100644 src/jogl/classes/com/jogamp/graph/geom/plane/Winding.java create mode 100644 src/jogl/classes/com/jogamp/graph/geom/plane/WindingRule.java (limited to 'src/jogl/classes/com/jogamp/graph') diff --git a/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java b/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java index 4bc1d0820..be5a1d1bf 100644 --- a/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java +++ b/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java @@ -37,6 +37,8 @@ import com.jogamp.graph.geom.Outline; import com.jogamp.graph.geom.Triangle; import com.jogamp.graph.geom.Vertex; import com.jogamp.graph.geom.plane.AffineTransform; +import com.jogamp.graph.geom.plane.Path2F; +import com.jogamp.graph.geom.plane.Winding; import com.jogamp.opengl.math.FloatUtil; import com.jogamp.opengl.math.VectorUtil; import com.jogamp.opengl.math.geom.AABBox; @@ -91,6 +93,7 @@ import com.jogamp.opengl.math.geom.AABBox; * * * @see Outline @@ -342,6 +345,9 @@ public final class OutlineShape implements Comparable { /** * Adds a vertex to the last open outline to the shape's tail. + * + * The constructed shape should be {@link Winding#CCW}. + * * @param v the vertex to be added to the OutlineShape */ public final void addVertex(final Vertex v) { @@ -356,6 +362,9 @@ public final class OutlineShape implements Comparable { /** * Adds a vertex to the last open outline to the shape at {@code position} + * + * The constructed shape should be {@link Winding#CCW}. + * * @param position index within the last open outline, at which the vertex will be added * @param v the vertex to be added to the OutlineShape */ @@ -372,6 +381,8 @@ public final class OutlineShape implements Comparable { * Add a 2D {@link Vertex} to the last open outline to the shape's tail. * The 2D vertex will be represented as Z=0. * + * The constructed shape should be {@link Winding#CCW}. + * * @param x the x coordinate * @param y the y coordniate * @param onCurve flag if this vertex is on the final curve or defines a curved region @@ -385,6 +396,8 @@ public final class OutlineShape implements Comparable { * Add a 2D {@link Vertex} to the last open outline to the shape at {@code position}. * The 2D vertex will be represented as Z=0. * + * The constructed shape should be {@link Winding#CCW}. + * * @param position index within the last open outline, at which the vertex will be added * @param x the x coordinate * @param y the y coordniate @@ -397,6 +410,9 @@ public final class OutlineShape implements Comparable { /** * Add a 3D {@link Vertex} to the last open outline to the shape's tail. + * + * The constructed shape should be {@link Winding#CCW}. + * * @param x the x coordinate * @param y the y coordinate * @param z the z coordinate @@ -410,6 +426,8 @@ public final class OutlineShape implements Comparable { /** * Add a 3D {@link Vertex} to the last open outline to the shape at {@code position}. * + * The constructed shape should be {@link Winding#CCW}. + * * @param position index within the last open outline, at which the vertex will be added * @param x the x coordinate * @param y the y coordniate @@ -424,6 +442,8 @@ public final class OutlineShape implements Comparable { /** * Add a vertex to the last open outline to the shape's tail. * + * The constructed shape should be {@link Winding#CCW}. + * * The vertex is passed as a float array and its offset where its attributes are located. * The attributes should be continuous (stride = 0). * Attributes which value are not set (when length less than 3) @@ -441,6 +461,8 @@ public final class OutlineShape implements Comparable { /** * Add a vertex to the last open outline to the shape at {@code position}. * + * The constructed shape should be {@link Winding#CCW}. + * * The vertex is passed as a float array and its offset where its attributes are located. * The attributes should be continuous (stride = 0). * Attributes which value are not set (when length less than 3) @@ -467,11 +489,244 @@ public final class OutlineShape implements Comparable { * otherwise a clone of the last vertex will be prepended. */ public final void closeLastOutline(final boolean closeTail) { - if( getLastOutline().setClosed(true) ) { + if( getLastOutline().setClosed( closeTail ) ) { dirtyBits |= DIRTY_TRIANGLES | DIRTY_VERTICES; } } + /** + * Append the given path geometry to this outline shape. + * + * The given path geometry should be {@link Winding#CCW}. + * + * If the given path geometry is {@link Winding#CW}, use {@link #addPathRev(Path2F, boolean)}. + * + * @param path the {@link Path2F} to append to this outline shape, should be {@link Winding#CCW}. + * @param connect pass true to turn an initial moveTo segment into a lineTo segment to connect the new geometry to the existing path, otherwise pass false. + * @see Path2F#getWinding() + */ + public void addPath(final Path2F path, final boolean connect) { + addPath(path.iterator(null), connect); + } + + /** + * Add the given {@link Path2F.Iterator} to this outline shape. + * + * The given path geometry should be {@link Winding#CCW}. + * + * If the given path geometry is {@link Winding#CW}, use {@link #addPathRev(Path2F.Iterator, boolean). + * + * @param pathI the {@link Path2F.Iterator} to append to this outline shape, should be {@link Winding#CCW}. + * @param connect pass true to turn an initial moveTo segment into a lineTo segment to connect the new geometry to the existing path, otherwise pass false. + * @see Path2F.Iterator#getWinding() + */ + public final void addPath(final Path2F.Iterator pathI, boolean connect) { + final float[] points = pathI.points(); + while ( pathI.hasNext() ) { + final int idx = pathI.index(); + final Path2F.SegmentType type = pathI.next(); + switch(type) { + case MOVETO: + final Outline lo = this.getLastOutline(); + final int lo_sz = lo.getVertexCount(); + if ( 0 == lo_sz ) { + addVertex(points, idx, 2, true); + break; + } else if ( !connect ) { + closeLastOutline(false); + addEmptyOutline(); + addVertex(points, idx, 2, true); + break; + } + { + // Skip if last vertex in last outline matching this point -> already connected. + final float[] llc = lo.getVertex(lo_sz-1).getCoord(); + if( llc[0] == points[idx+0] && + llc[1] == points[idx+1] ) { + break; + } + } + // fallthrough: MOVETO -> LINETO + case LINETO: + addVertex(points, idx, 2, true); + break; + case QUADTO: + addVertex(points, idx, 2, false); + addVertex(points, idx+2, 2, true); + break; + case CUBICTO: + addVertex(points, idx, 2, false); + addVertex(points, idx+2, 2, false); + addVertex(points, idx+4, 2, true); + break; + case CLOSE: + closeLastOutline(true); + addEmptyOutline(); + break; + default: + throw new IllegalArgumentException("Unhandled Segment Type: "+type); + } + connect = false; + } + } + + /** + * Append the given path geometry to this outline shape in reverse order. + * + * The given path geometry should be {@link Winding#CW}. + * + * If the given path geometry is {@link Winding#CCW}, use {@link #addPath(Path2F, boolean)}. + * + * @param path the {@link Path2F} to append to this outline shape, should be {@link Winding#CW}. + * @param connect pass true to turn an initial moveTo segment into a lineTo segment to connect the new geometry to the existing path, otherwise pass false. + */ + public void addPathRev(final Path2F path, final boolean connect) { + addPathRev(path.iterator(null), connect); + } + + /** + * Add the given {@link Path2F.Iterator} to this outline shape in reverse order. + * + * The given path geometry should be {@link Winding#CW}. + * + * If the given path geometry is {@link Winding#CCW}, use {@link #addPath(Path2F.Iterator, boolean). + * + * @param pathI the {@link Path2F.Iterator} to append to this outline shape, should be {@link Winding#CW}. + * @param connect pass true to turn an initial moveTo segment into a lineTo segment to connect the new geometry to the existing path, otherwise pass false. + */ + public final void addPathRev(final Path2F.Iterator pathI, boolean connect) { + final float[] points = pathI.points(); + while ( pathI.hasNext() ) { + final int idx = pathI.index(); + final Path2F.SegmentType type = pathI.next(); + switch(type) { + case MOVETO: + final Outline lo = this.getLastOutline(); + final int lo_sz = lo.getVertexCount(); + if ( 0 == lo_sz ) { + addVertex(0, points, idx, 2, true); + break; + } else if ( !connect ) { + closeLastOutline(false); + addEmptyOutline(); + addVertex(0, points, idx, 2, true); + break; + } + { + // Skip if last vertex in last outline matching this point -> already connected. + final float[] llc = lo.getVertex(0).getCoord(); + if( llc[0] == points[idx+0] && + llc[1] == points[idx+1] ) { + break; + } + } + // fallthrough: MOVETO -> LINETO + case LINETO: + addVertex(0, points, idx, 2, true); + break; + case QUADTO: + addVertex(0, points, idx, 2, false); + addVertex(0, points, idx+2, 2, true); + break; + case CUBICTO: + addVertex(0, points, idx, 2, false); + addVertex(0, points, idx+2, 2, false); + addVertex(0, points, idx+4, 2, true); + break; + case CLOSE: + closeLastOutline(true); + addEmptyOutline(); + break; + default: + throw new IllegalArgumentException("Unhandled Segment Type: "+type); + } + connect = false; + } + } + + /** + * Start a new position for the next line segment at given point x/y (P1). + * + * The constructed shape should be {@link Winding#CCW}. + * + * @param x point (P1) + * @param y point (P1) + * @see Path2F#moveTo(float, float) + * @see #addPath(com.jogamp.graph.geom.plane.Path2F.Iterator, boolean) + */ + public final void moveTo(final float x, final float y) { + if ( 0 == getLastOutline().getVertexCount() ) { + addVertex(x, y, true); + } else { + closeLastOutline(false); + addEmptyOutline(); + addVertex(x, y, true); + } + } + + /** + * Add a line segment, intersecting the last point and the given point x/y (P1). + * + * The constructed shape should be {@link Winding#CCW}. + * + * @param x final point (P1) + * @param y final point (P1) + * @see Path2F#lineTo(float, float) + * @see #addPath(com.jogamp.graph.geom.plane.Path2F.Iterator, boolean) + */ + public final void lineTo(final float x, final float y) { + addVertex(x, y, true); + } + + /** + * Add a quadratic curve segment, intersecting the last point and the second given point x2/y2 (P2). + * + * The constructed shape should be {@link Winding#CCW}. + * + * @param x1 quadratic parametric control point (P1) + * @param y1 quadratic parametric control point (P1) + * @param x2 final interpolated control point (P2) + * @param y2 final interpolated control point (P2) + * @see Path2F#quadTo(float, float, float, float) + * @see #addPath(com.jogamp.graph.geom.plane.Path2F.Iterator, boolean) + */ + public final void quadTo(final float x1, final float y1, final float x2, final float y2) { + addVertex(x1, y1, false); + addVertex(x2, y2, true); + } + + /** + * Add a cubic Bézier curve segment, intersecting the last point and the second given point x3/y3 (P3). + * + * The constructed shape should be {@link Winding#CCW}. + * + * @param x1 Bézier control point (P1) + * @param y1 Bézier control point (P1) + * @param x2 Bézier control point (P2) + * @param y2 Bézier control point (P2) + * @param x3 final interpolated control point (P3) + * @param y3 final interpolated control point (P3) + * @see Path2F#cubicTo(float, float, float, float, float, float) + * @see #addPath(com.jogamp.graph.geom.plane.Path2F.Iterator, boolean) + */ + public final void cubicTo(final float x1, final float y1, final float x2, final float y2, final float x3, final float y3) { + addVertex(x1, y1, false); + addVertex(x2, y2, false); + addVertex(x3, y3, true); + } + + /** + * Closes the current sub-path segment by drawing a straight line back to the coordinates of the last moveTo. If the path is already closed then this method has no effect. + * @see Path2F#closePath() + * @see #addPath(com.jogamp.graph.geom.plane.Path2F.Iterator, boolean) + */ + public final void closePath() { + if ( 0 < getLastOutline().getVertexCount() ) { + closeLastOutline(true); + addEmptyOutline(); + } + } + /** * Return the outline's vertices state, {@link OutlineShape.VerticesState} */ diff --git a/src/jogl/classes/com/jogamp/graph/geom/Outline.java b/src/jogl/classes/com/jogamp/graph/geom/Outline.java index 486e787a2..b18d51849 100644 --- a/src/jogl/classes/com/jogamp/graph/geom/Outline.java +++ b/src/jogl/classes/com/jogamp/graph/geom/Outline.java @@ -29,7 +29,6 @@ package com.jogamp.graph.geom; import java.util.ArrayList; -import com.jogamp.graph.geom.Vertex; import com.jogamp.graph.geom.plane.AffineTransform; import com.jogamp.graph.curve.OutlineShape; import com.jogamp.graph.curve.Region; diff --git a/src/jogl/classes/com/jogamp/graph/geom/plane/AffineTransform.java b/src/jogl/classes/com/jogamp/graph/geom/plane/AffineTransform.java index a5b3cac93..62cda0322 100644 --- a/src/jogl/classes/com/jogamp/graph/geom/plane/AffineTransform.java +++ b/src/jogl/classes/com/jogamp/graph/geom/plane/AffineTransform.java @@ -530,7 +530,7 @@ public class AffineTransform implements Cloneable { } } - public final Path2D createTransformedShape(final Path2D src) { + public final Path2F createTransformedShape(final Path2F src) { if (src == null) { return null; } diff --git a/src/jogl/classes/com/jogamp/graph/geom/plane/Crossing.java b/src/jogl/classes/com/jogamp/graph/geom/plane/Crossing.java deleted file mode 100644 index 173f1d4b4..000000000 --- a/src/jogl/classes/com/jogamp/graph/geom/plane/Crossing.java +++ /dev/null @@ -1,904 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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. - */ -/** - * @author Denis M. Kishenko - */ -package com.jogamp.graph.geom.plane; - -import com.jogamp.opengl.math.FloatUtil; - - - -public class Crossing { - - /** - * Allowable tolerance for bounds comparison - */ - static final float DELTA = (float) 1E-5; - - /** - * If roots have distance less then ROOT_DELTA they are double - */ - static final float ROOT_DELTA = (float) 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(final float eqn[], final float res[]) { - final float a = eqn[2]; - final float b = eqn[1]; - final float c = eqn[0]; - int rc = 0; - if (a == 0.0) { - if (b == 0.0) { - return -1; - } - res[rc++] = -c / b; - } else { - float d = b * b - 4.0f * a * c; - // d < 0.0 - if (d < 0.0) { - return 0; - } - d = FloatUtil.sqrt(d); - res[rc++] = (- b + d) / (a * 2.0f); - // d != 0.0 - if (d != 0.0) { - res[rc++] = (- b - d) / (a * 2.0f); - } - } - 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(final float eqn[], final float res[]) { - final float d = eqn[3]; - if (d == 0) { - return solveQuad(eqn, res); - } - final float a = eqn[2] / d; - final float b = eqn[1] / d; - final float c = eqn[0] / d; - int rc = 0; - - final float Q = (a * a - 3.0f * b) / 9.0f; - final float R = (2.0f * a * a * a - 9.0f * a * b + 27.0f * c) / 54.0f; - final float Q3 = Q * Q * Q; - final float R2 = R * R; - final float n = - a / 3.0f; - - if (R2 < Q3) { - final float t = FloatUtil.acos(R / FloatUtil.sqrt(Q3)) / 3.0f; - final float p = 2.0f * FloatUtil.PI / 3.0f; - final float m = -2.0f * FloatUtil.sqrt(Q); - res[rc++] = m * FloatUtil.cos(t) + n; - res[rc++] = m * FloatUtil.cos(t + p) + n; - res[rc++] = m * FloatUtil.cos(t - p) + n; - } else { -// Debug.println("R2 >= Q3 (" + R2 + "/" + Q3 + ")"); - float A = FloatUtil.pow(FloatUtil.abs(R) + FloatUtil.sqrt(R2 - Q3), 1.0f / 3.0f); - if (R > 0.0) { - A = -A; - } -// if (A == 0.0) { - if (-ROOT_DELTA < A && A < ROOT_DELTA) { - res[rc++] = n; - } else { - final float B = Q / A; - res[rc++] = A + B + n; -// if (R2 == Q3) { - final float delta = R2 - Q3; - if (-ROOT_DELTA < delta && delta < ROOT_DELTA) { - res[rc++] = - (A + B) / 2.0f + n; - } - } - - } - return fixRoots(res, rc); - } - - /** - * Excludes float roots. Roots are float if they lies enough close with each other. - * @param res - the roots - * @param rc - the roots count - * @return new roots count - */ - static int fixRoots(final float res[], final 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 { - - float ax, ay, bx, by; - float Ax, Ay, Bx, By; - - public QuadCurve(final float x1, final float y1, final float cx, final float cy, final float x2, final float 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(final float res[], final int rc, final float py1, final float py2) { - int cross = 0; - - for (int i = 0; i < rc; i++) { - final float 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) { - // FIXME: consider using FloatUtil.isEqual(ax, bx, epsilon), ... - if (py1 < ay && (ax != bx ? ax - bx : bx) > 0.0) { - cross++; - } - continue; - } - // CURVE-INSIDE - final float ry = t * (t * Ay + By); - // ry = t * t * Ay + t * By - if (ry > py2) { - final float 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(final float res[], final float px) { - final float eqn[] = {-px, Bx, Ax}; - return solveQuad(eqn, res); - } - - int solveExtrem(final float 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(final float bound[], int bc, final float res[], final int rc, final float minX, final float maxX, final boolean changeId, int id) { - for(int i = 0; i < rc; i++) { - final float t = res[i]; - if (t > -DELTA && t < 1 + DELTA) { - final float 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 { - - float ax, ay, bx, by, cx, cy; - float Ax, Ay, Bx, By, Cx, Cy; - float Ax3, Bx2; - - public CubicCurve(final float x1, final float y1, final float cx1, final float cy1, final float cx2, final float cy2, final float x2, final float 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(final float res[], final int rc, final float py1, final float py2) { - int cross = 0; - for (int i = 0; i < rc; i++) { - final float t = res[i]; - - // CURVE-OUTSIDE - if (t < -DELTA || t > 1 + DELTA) { - continue; - } - // CURVE-START - if (t < DELTA) { - // FIXME: consider using FloatUtil.isZero(bx, epsilon), ... - 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 - final float ry = t * (t * (t * Ay + By) + Cy); - // ry = t * t * t * Ay + t * t * By + t * Cy - if (ry > py2) { - float 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(final float res[], final float px) { - final float eqn[] = {-px, Cx, Bx, Ax}; - return solveCubic(eqn, res); - } - - int solveExtremX(final float res[]) { - final float eqn[] = {Cx, Bx2, Ax3}; - return solveQuad(eqn, res); - } - - int solveExtremY(final float res[]) { - final float eqn[] = {Cy, By + By, Ay + Ay + Ay}; - return solveQuad(eqn, res); - } - - int addBound(final float bound[], int bc, final float res[], final int rc, final float minX, final float maxX, final boolean changeId, int id) { - for(int i = 0; i < rc; i++) { - final float t = res[i]; - if (t > -DELTA && t < 1 + DELTA) { - final float 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(final float x1, final float y1, final float x2, final float y2, final float x, final float 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(final float x1, final float y1, final float cx, final float cy, final float x2, final float y2, final float x, final float 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 - final QuadCurve c = new QuadCurve(x1, y1, cx, cy, x2, y2); - final float px = x - x1; - final float py = y - y1; - final float res[] = new float[3]; - final 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(final float x1, final float y1, final float cx1, final float cy1, final float cx2, final float cy2, final float x2, final float y2, final float x, final float 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 - final CubicCurve c = new CubicCurve(x1, y1, cx1, cy1, cx2, cy2, x2, y2); - final float px = x - x1; - final float py = y - y1; - final float res[] = new float[3]; - final 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(final PathIterator p, final float x, final float y) { - int cross = 0; - float mx, my, cx, cy; - mx = my = cx = cy = 0.0f; - final float coords[] = new float[6]; - - while (!p.isDone()) { - final int segmentType = p.currentSegment(coords); - switch (segmentType) { - 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; - default: - throw new IllegalArgumentException("Unhandled Segment Type: "+segmentType); - } - - // 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(final Path2D s, final float x, final float y) { - if (!s.getBounds2D().contains(x, y)) { - return 0; - } - return crossPath(s.iterator(null), x, y); - } - - /** - * Returns true if value enough small - */ - public static boolean isZero(final float val) { - return -DELTA < val && val < DELTA; - } - - /** - * Sort bound array - */ - static void sortBound(final float bound[], final 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) { - float 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(final float bound[], final int bc, final float py1, final float 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) { - final 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(final float x1, final float y1, final float x2, final float y2, final float rx1, final float ry1, final float rx2, final float 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 - float 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; - } - final float k = (y2 - y1) / (x2 - x1); - final float by1 = k * (bx1 - x1) + y1; - final float 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(final float x1, final float y1, final float cx, final float cy, final float x2, final float y2, final float rx1, final float ry1, final float rx2, final float 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 ------------------------------------------------------------- - final QuadCurve c = new QuadCurve(x1, y1, cx, cy, x2, y2); - final float px1 = rx1 - x1; - final float py1 = ry1 - y1; - final float px2 = rx2 - x1; - final float py2 = ry2 - y1; - - final float res1[] = new float[3]; - final float res2[] = new float[3]; - final int rc1 = c.solvePoint(res1, px1); - int rc2 = c.solvePoint(res2, px2); - - // INSIDE-LEFT/RIGHT - if (rc1 == 0 && rc2 == 0) { - return 0; - } - - // Build bound -------------------------------------------------------- - final float minX = px1 - DELTA; - final float maxX = px2 + DELTA; - final float bound[] = new float[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.0f; - bound[bc++] = 0.0f; - bound[bc++] = 0.0f; - bound[bc++] = 4; - } - if (rx1 < x2 && x2 < rx2) { - bound[bc++] = 1.0f; - bound[bc++] = c.ax; - bound[bc++] = c.ay; - bound[bc++] = 5; - } - // End build bound ---------------------------------------------------- - - final 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(final float x1, final float y1, final float cx1, final float cy1, final float cx2, final float cy2, final float x2, final float y2, final float rx1, final float ry1, final float rx2, final float 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 - final CubicCurve c = new CubicCurve(x1, y1, cx1, cy1, cx2, cy2, x2, y2); - final float px1 = rx1 - x1; - final float py1 = ry1 - y1; - final float px2 = rx2 - x1; - final float py2 = ry2 - y1; - - final float res1[] = new float[3]; - final float res2[] = new float[3]; - final int rc1 = c.solvePoint(res1, px1); - int rc2 = c.solvePoint(res2, px2); - - // LEFT/RIGHT - if (rc1 == 0 && rc2 == 0) { - return 0; - } - - final float minX = px1 - DELTA; - final float maxX = px2 + DELTA; - - // Build bound -------------------------------------------------------- - final float bound[] = new float[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.0f; - bound[bc++] = 0.0f; - bound[bc++] = 0.0f; - bound[bc++] = 6; - } - if (rx1 < x2 && x2 < rx2) { - bound[bc++] = 1.0f; - bound[bc++] = c.ax; - bound[bc++] = c.ay; - bound[bc++] = 7; - } - // End build bound ---------------------------------------------------- - - final 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(final PathIterator p, final float x, final float y, final float w, final float h) { - - int cross = 0; - int count; - float mx, my, cx, cy; - mx = my = cx = cy = 0.0f; - final float coords[] = new float[6]; - - final float rx1 = x; - final float ry1 = y; - final float rx2 = x + w; - final float ry2 = y + h; - - while (!p.isDone()) { - count = 0; - final int segmentType = p.currentSegment(coords); - switch (segmentType) { - 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; - default: - throw new IllegalArgumentException("Unhandled Segment Type: "+segmentType); - } - 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(final Path2D s, final float x, final float y, final float w, final float h) { - if (!s.getBounds2D().intersects2DRegion(x, y, w, h)) { - return 0; - } - return intersectPath(s.iterator(null), x, y, w, h); - } - - /** - * Returns true if cross count correspond inside location for non zero path rule - */ - public static boolean isInsideNonZero(final int cross) { - return cross != 0; - } - - /** - * Returns true if cross count correspond inside location for even-odd path rule - */ - public static boolean isInsideEvenOdd(final int cross) { - return (cross & 1) != 0; - } -} diff --git a/src/jogl/classes/com/jogamp/graph/geom/plane/Crossing2F.java b/src/jogl/classes/com/jogamp/graph/geom/plane/Crossing2F.java new file mode 100644 index 000000000..0cd4b66ff --- /dev/null +++ b/src/jogl/classes/com/jogamp/graph/geom/plane/Crossing2F.java @@ -0,0 +1,898 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +/** + * @author Denis M. Kishenko + */ +package com.jogamp.graph.geom.plane; + +import com.jogamp.opengl.math.FloatUtil; + +/* pp */ class Crossing2F { + + /** + * Allowable tolerance for bounds comparison + */ + static final float DELTA = (float) 1E-5; + + /** + * If roots have distance less then ROOT_DELTA they are double + */ + static final float ROOT_DELTA = (float) 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(final float eqn[], final float res[]) { + final float a = eqn[2]; + final float b = eqn[1]; + final float c = eqn[0]; + int rc = 0; + if (a == 0.0) { + if (b == 0.0) { + return -1; + } + res[rc++] = -c / b; + } else { + float d = b * b - 4.0f * a * c; + // d < 0.0 + if (d < 0.0) { + return 0; + } + d = FloatUtil.sqrt(d); + res[rc++] = (- b + d) / (a * 2.0f); + // d != 0.0 + if (d != 0.0) { + res[rc++] = (- b - d) / (a * 2.0f); + } + } + 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(final float eqn[], final float res[]) { + final float d = eqn[3]; + if (d == 0) { + return solveQuad(eqn, res); + } + final float a = eqn[2] / d; + final float b = eqn[1] / d; + final float c = eqn[0] / d; + int rc = 0; + + final float Q = (a * a - 3.0f * b) / 9.0f; + final float R = (2.0f * a * a * a - 9.0f * a * b + 27.0f * c) / 54.0f; + final float Q3 = Q * Q * Q; + final float R2 = R * R; + final float n = - a / 3.0f; + + if (R2 < Q3) { + final float t = FloatUtil.acos(R / FloatUtil.sqrt(Q3)) / 3.0f; + final float p = 2.0f * FloatUtil.PI / 3.0f; + final float m = -2.0f * FloatUtil.sqrt(Q); + res[rc++] = m * FloatUtil.cos(t) + n; + res[rc++] = m * FloatUtil.cos(t + p) + n; + res[rc++] = m * FloatUtil.cos(t - p) + n; + } else { +// Debug.println("R2 >= Q3 (" + R2 + "/" + Q3 + ")"); + float A = FloatUtil.pow(FloatUtil.abs(R) + FloatUtil.sqrt(R2 - Q3), 1.0f / 3.0f); + if (R > 0.0) { + A = -A; + } +// if (A == 0.0) { + if (-ROOT_DELTA < A && A < ROOT_DELTA) { + res[rc++] = n; + } else { + final float B = Q / A; + res[rc++] = A + B + n; +// if (R2 == Q3) { + final float delta = R2 - Q3; + if (-ROOT_DELTA < delta && delta < ROOT_DELTA) { + res[rc++] = - (A + B) / 2.0f + n; + } + } + + } + return fixRoots(res, rc); + } + + /** + * Excludes float roots. Roots are float if they lies enough close with each other. + * @param res - the roots + * @param rc - the roots count + * @return new roots count + */ + static int fixRoots(final float res[], final 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 { + + float ax, ay, bx, by; + float Ax, Ay, Bx, By; + + public QuadCurve(final float x1, final float y1, final float cx, final float cy, final float x2, final float 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(final float res[], final int rc, final float py1, final float py2) { + int cross = 0; + + for (int i = 0; i < rc; i++) { + final float 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) { + // FIXME: consider using FloatUtil.isEqual(ax, bx, epsilon), ... + if (py1 < ay && (ax != bx ? ax - bx : bx) > 0.0) { + cross++; + } + continue; + } + // CURVE-INSIDE + final float ry = t * (t * Ay + By); + // ry = t * t * Ay + t * By + if (ry > py2) { + final float 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(final float res[], final float px) { + final float eqn[] = {-px, Bx, Ax}; + return solveQuad(eqn, res); + } + + int solveExtrem(final float 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(final float bound[], int bc, final float res[], final int rc, final float minX, final float maxX, final boolean changeId, int id) { + for(int i = 0; i < rc; i++) { + final float t = res[i]; + if (t > -DELTA && t < 1 + DELTA) { + final float 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 { + + float ax, ay, bx, by, cx, cy; + float Ax, Ay, Bx, By, Cx, Cy; + float Ax3, Bx2; + + public CubicCurve(final float x1, final float y1, final float cx1, final float cy1, final float cx2, final float cy2, final float x2, final float 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(final float res[], final int rc, final float py1, final float py2) { + int cross = 0; + for (int i = 0; i < rc; i++) { + final float t = res[i]; + + // CURVE-OUTSIDE + if (t < -DELTA || t > 1 + DELTA) { + continue; + } + // CURVE-START + if (t < DELTA) { + // FIXME: consider using FloatUtil.isZero(bx, epsilon), ... + 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 + final float ry = t * (t * (t * Ay + By) + Cy); + // ry = t * t * t * Ay + t * t * By + t * Cy + if (ry > py2) { + float 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(final float res[], final float px) { + final float eqn[] = {-px, Cx, Bx, Ax}; + return solveCubic(eqn, res); + } + + int solveExtremX(final float res[]) { + final float eqn[] = {Cx, Bx2, Ax3}; + return solveQuad(eqn, res); + } + + int solveExtremY(final float res[]) { + final float eqn[] = {Cy, By + By, Ay + Ay + Ay}; + return solveQuad(eqn, res); + } + + int addBound(final float bound[], int bc, final float res[], final int rc, final float minX, final float maxX, final boolean changeId, int id) { + for(int i = 0; i < rc; i++) { + final float t = res[i]; + if (t > -DELTA && t < 1 + DELTA) { + final float 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(final float x1, final float y1, final float x2, final float y2, final float x, final float 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(final float x1, final float y1, final float cx, final float cy, final float x2, final float y2, final float x, final float 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 + final QuadCurve c = new QuadCurve(x1, y1, cx, cy, x2, y2); + final float px = x - x1; + final float py = y - y1; + final float res[] = new float[3]; + final 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(final float x1, final float y1, final float cx1, final float cy1, final float cx2, final float cy2, final float x2, final float y2, final float x, final float 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 + final CubicCurve c = new CubicCurve(x1, y1, cx1, cy1, cx2, cy2, x2, y2); + final float px = x - x1; + final float py = y - y1; + final float res[] = new float[3]; + final 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(final Path2F.Iterator p, final float x, final float y) { + int cross = 0; + float mx, my, cx, cy; + mx = my = cx = cy = 0.0f; + final float[] points = p.points(); + while ( p.hasNext() ) { + final int idx = p.index(); + final Path2F.SegmentType segmentType = p.next(); + switch (segmentType) { + case MOVETO: + if (cx != mx || cy != my) { + cross += crossLine(cx, cy, mx, my, x, y); + } + mx = cx = points[idx+0]; + my = cy = points[idx+1]; + break; + case LINETO: + cross += crossLine(cx, cy, cx = points[idx+0], cy = points[idx+1], x, y); + break; + case QUADTO: + cross += crossQuad(cx, cy, points[idx+0], points[idx+1], cx = points[idx+2], cy = points[idx+3], x, y); + break; + case CUBICTO: + cross += crossCubic(cx, cy, points[idx+0], points[idx+1], points[idx+2], points[idx+3], cx = points[idx+4], cy = points[idx+5], x, y); + break; + case 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; + } + } + 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(final Path2F s, final float x, final float y) { + if (!s.getBounds2D().contains(x, y)) { + return 0; + } + return crossPath(s.iterator(null), x, y); + } + + /** + * Returns true if value enough small + */ + public static boolean isZero(final float val) { + return -DELTA < val && val < DELTA; + } + + /** + * Sort bound array + */ + static void sortBound(final float bound[], final 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) { + float 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(final float bound[], final int bc, final float py1, final float 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) { + final 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(final float x1, final float y1, final float x2, final float y2, final float rx1, final float ry1, final float rx2, final float 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 + float 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; + } + final float k = (y2 - y1) / (x2 - x1); + final float by1 = k * (bx1 - x1) + y1; + final float 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(final float x1, final float y1, final float cx, final float cy, final float x2, final float y2, final float rx1, final float ry1, final float rx2, final float 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 ------------------------------------------------------------- + final QuadCurve c = new QuadCurve(x1, y1, cx, cy, x2, y2); + final float px1 = rx1 - x1; + final float py1 = ry1 - y1; + final float px2 = rx2 - x1; + final float py2 = ry2 - y1; + + final float res1[] = new float[3]; + final float res2[] = new float[3]; + final int rc1 = c.solvePoint(res1, px1); + int rc2 = c.solvePoint(res2, px2); + + // INSIDE-LEFT/RIGHT + if (rc1 == 0 && rc2 == 0) { + return 0; + } + + // Build bound -------------------------------------------------------- + final float minX = px1 - DELTA; + final float maxX = px2 + DELTA; + final float bound[] = new float[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.0f; + bound[bc++] = 0.0f; + bound[bc++] = 0.0f; + bound[bc++] = 4; + } + if (rx1 < x2 && x2 < rx2) { + bound[bc++] = 1.0f; + bound[bc++] = c.ax; + bound[bc++] = c.ay; + bound[bc++] = 5; + } + // End build bound ---------------------------------------------------- + + final 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(final float x1, final float y1, final float cx1, final float cy1, final float cx2, final float cy2, final float x2, final float y2, final float rx1, final float ry1, final float rx2, final float 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 + final CubicCurve c = new CubicCurve(x1, y1, cx1, cy1, cx2, cy2, x2, y2); + final float px1 = rx1 - x1; + final float py1 = ry1 - y1; + final float px2 = rx2 - x1; + final float py2 = ry2 - y1; + + final float res1[] = new float[3]; + final float res2[] = new float[3]; + final int rc1 = c.solvePoint(res1, px1); + int rc2 = c.solvePoint(res2, px2); + + // LEFT/RIGHT + if (rc1 == 0 && rc2 == 0) { + return 0; + } + + final float minX = px1 - DELTA; + final float maxX = px2 + DELTA; + + // Build bound -------------------------------------------------------- + final float bound[] = new float[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.0f; + bound[bc++] = 0.0f; + bound[bc++] = 0.0f; + bound[bc++] = 6; + } + if (rx1 < x2 && x2 < rx2) { + bound[bc++] = 1.0f; + bound[bc++] = c.ax; + bound[bc++] = c.ay; + bound[bc++] = 7; + } + // End build bound ---------------------------------------------------- + + final 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(final Path2F.Iterator p, final float x, final float y, final float w, final float h) { + + int cross = 0; + int count; + float mx, my, cx, cy; + mx = my = cx = cy = 0.0f; + + final float rx1 = x; + final float ry1 = y; + final float rx2 = x + w; + final float ry2 = y + h; + + final float[] points = p.points(); + + while ( p.hasNext() ) { + final int idx = p.index(); + final Path2F.SegmentType segmentType = p.next(); + count = 0; + switch (segmentType) { + case MOVETO: + if (cx != mx || cy != my) { + count = intersectLine(cx, cy, mx, my, rx1, ry1, rx2, ry2); + } + mx = cx = points[idx+0]; + my = cy = points[idx+1]; + break; + case LINETO: + count = intersectLine(cx, cy, cx = points[idx+0], cy = points[idx+1], rx1, ry1, rx2, ry2); + break; + case QUADTO: + count = intersectQuad(cx, cy, points[idx+0], points[idx+1], cx = points[idx+2], cy = points[idx+3], rx1, ry1, rx2, ry2); + break; + case CUBICTO: + count = intersectCubic(cx, cy, points[idx+0], points[idx+1], points[idx+2], points[idx+3], cx = points[idx+4], cy = points[idx+5], rx1, ry1, rx2, ry2); + break; + case 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; + } + 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(final Path2F s, final float x, final float y, final float w, final float h) { + if (!s.getBounds2D().intersects2DRegion(x, y, w, h)) { + return 0; + } + return intersectPath(s.iterator(null), x, y, w, h); + } + + /** + * Returns true if cross count correspond inside location for non zero path rule + */ + public static boolean isInsideNonZero(final int cross) { + return cross != 0; + } + + /** + * Returns true if cross count correspond inside location for even-odd path rule + */ + public static boolean isInsideEvenOdd(final int cross) { + return (cross & 1) != 0; + } +} diff --git a/src/jogl/classes/com/jogamp/graph/geom/plane/Path2D.java b/src/jogl/classes/com/jogamp/graph/geom/plane/Path2D.java deleted file mode 100644 index 8dbc5fd21..000000000 --- a/src/jogl/classes/com/jogamp/graph/geom/plane/Path2D.java +++ /dev/null @@ -1,454 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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. - */ -/** - * @author Denis M. Kishenko - * @author Sven Gothel - */ -package com.jogamp.graph.geom.plane; - -import java.util.NoSuchElementException; - -import com.jogamp.graph.geom.SVertex; -import com.jogamp.graph.geom.Vertex; -import com.jogamp.opengl.math.geom.AABBox; - - -public final class Path2D implements Cloneable { - - public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD; - public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO; - - static final String invalidWindingRuleValue = "Invalid winding rule value"; - static final String iteratorOutOfBounds = "Iterator out of bounds"; - - /** - * The buffers size - */ - private static final int BUFFER_SIZE = 10; - - /** - * The buffers capacity - */ - private static final int BUFFER_CAPACITY = 10; - - /** - * The point's types buffer - */ - byte[] m_types; - - /** - * The points buffer - */ - float[] m_points; - - /** - * The point's type buffer size - */ - int m_typeSize; - - /** - * The points buffer size - */ - int m_pointSize; - - /** - * The path rule - */ - int m_rule; - - /** - * The space amount in points buffer for different segmenet's types - */ - static int pointShift[] = { - 2, // MOVETO - 2, // LINETO - 4, // QUADTO - 6, // CUBICTO - 0}; // CLOSE - - /* - * GeneralPath path iterator - */ - static 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 - */ - Path2D p; - - /** - * The path iterator transformation - */ - AffineTransform t; - - /** - * Constructs a new GeneralPath.Iterator for given general path - * @param path - the source GeneralPath object - */ - Iterator(final Path2D path) { - this(path, null); - } - - /** - * Constructs a new GeneralPath.Iterator for given general path and transformation - * @param path - the source GeneralPath object - * @param at - the AffineTransform object to apply rectangle path - */ - Iterator(final Path2D path, final AffineTransform at) { - this.p = path; - this.t = at; - } - - @Override - public int getWindingRule() { - return p.getWindingRule(); - } - - @Override - public int index() { return typeIndex; } - - @Override - public float[] points() { return p.m_points; } - - @Override - public int getType(final int idx) { return p.m_types[idx]; } - - @Override - public boolean isDone() { - return typeIndex >= p.m_typeSize; - } - - @Override - public void next() { - typeIndex++; - } - - @Override - public int currentSegment(final float[] coords) { - if (isDone()) { - throw new NoSuchElementException(iteratorOutOfBounds); - } - final int type = p.m_types[typeIndex]; - final int count = Path2D.pointShift[type]; - System.arraycopy(p.m_points, pointIndex, coords, 0, count); - if (t != null) { - t.transform(coords, 0, coords, 0, count / 2); - } - pointIndex += count; - return type; - } - - } - - public static int getPointCount(final int type) { return pointShift[type]; } - - public Path2D() { - this(WIND_NON_ZERO, BUFFER_SIZE); - } - - public Path2D(final int rule) { - this(rule, BUFFER_SIZE); - } - - public Path2D(final int rule, final int initialCapacity) { - setWindingRule(rule); - m_types = new byte[initialCapacity]; - m_points = new float[initialCapacity * 2]; - } - - public Path2D(final Path2D path) { - this(WIND_NON_ZERO, BUFFER_SIZE); - final PathIterator p = path.iterator(null); - setWindingRule(p.getWindingRule()); - append(p, false); - } - - public void setWindingRule(final int rule) { - if (rule != WIND_EVEN_ODD && rule != WIND_NON_ZERO) { - throw new NoSuchElementException(invalidWindingRuleValue); - } - this.m_rule = rule; - } - - public int getWindingRule() { - return m_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 - */ - void checkBuf(final int pointCount, final boolean checkMove) { - if (checkMove && m_typeSize == 0) { - throw new IllegalPathStateException("First segment should be SEG_MOVETO type"); - } - if (m_typeSize == m_types.length) { - final byte tmp[] = new byte[m_typeSize + BUFFER_CAPACITY]; - System.arraycopy(m_types, 0, tmp, 0, m_typeSize); - m_types = tmp; - } - if (m_pointSize + pointCount > m_points.length) { - final float tmp[] = new float[m_pointSize + Math.max(BUFFER_CAPACITY * 2, pointCount)]; - System.arraycopy(m_points, 0, tmp, 0, m_pointSize); - m_points = tmp; - } - } - - public void moveTo(final float x, final float y) { - if (m_typeSize > 0 && m_types[m_typeSize - 1] == PathIterator.SEG_MOVETO) { - m_points[m_pointSize - 2] = x; - m_points[m_pointSize - 1] = y; - } else { - checkBuf(2, false); - m_types[m_typeSize++] = PathIterator.SEG_MOVETO; - m_points[m_pointSize++] = x; - m_points[m_pointSize++] = y; - } - } - - public void lineTo(final float x, final float y) { - checkBuf(2, true); - m_types[m_typeSize++] = PathIterator.SEG_LINETO; - m_points[m_pointSize++] = x; - m_points[m_pointSize++] = y; - } - - public void quadTo(final float x1, final float y1, final float x2, final float y2) { - checkBuf(4, true); - m_types[m_typeSize++] = PathIterator.SEG_QUADTO; - m_points[m_pointSize++] = x1; - m_points[m_pointSize++] = y1; - m_points[m_pointSize++] = x2; - m_points[m_pointSize++] = y2; - } - - public void curveTo(final float x1, final float y1, final float x2, final float y2, final float x3, final float y3) { - checkBuf(6, true); - m_types[m_typeSize++] = PathIterator.SEG_CUBICTO; - m_points[m_pointSize++] = x1; - m_points[m_pointSize++] = y1; - m_points[m_pointSize++] = x2; - m_points[m_pointSize++] = y2; - m_points[m_pointSize++] = x3; - m_points[m_pointSize++] = y3; - } - - final public int size() { - return m_typeSize; - } - - final public boolean isClosed() { - return m_typeSize > 0 && m_types[m_typeSize - 1] == PathIterator.SEG_CLOSE ; - } - - public void closePath() { - if (!isClosed()) { - checkBuf(0, true); - m_types[m_typeSize++] = PathIterator.SEG_CLOSE; - } - } - - @Override - public String toString() { - return "[size "+size()+", closed "+isClosed()+"]"; - } - - public void append(final Path2D path, final boolean connect) { - final PathIterator p = path.iterator(null); - append(p, connect); - } - - /** - * Append the given path geometry to this instance - * @param path the {@link PathIterator} to append to this {@link Path2D} - * @param connect pass true to turn an initial moveTo segment into a lineTo segment to connect the new geometry to the existing path, otherwise pass false. - */ - public void append(final PathIterator path, boolean connect) { - final float[] points = path.points(); - while ( !path.isDone() ) { - final int index = path.index(); - final int type = path.getType(index); - switch ( type ) { - case PathIterator.SEG_MOVETO: - if ( !connect || 0 == m_typeSize ) { - moveTo(points[index+0], points[index+1]); - break; - } - if ( m_types[m_typeSize - 1] != PathIterator.SEG_CLOSE && - m_points[m_pointSize - 2] == points[index+0] && - m_points[m_pointSize - 1] == points[index+1] - ) - { - break; - } - // fallthrough: MOVETO -> LINETO - case PathIterator.SEG_LINETO: - lineTo(points[index+0], points[index+1]); - break; - case PathIterator.SEG_QUADTO: - quadTo(points[index+0], points[index+1], points[index+2], points[index+3]); - break; - case PathIterator.SEG_CUBICTO: - curveTo(points[index+0], points[index+1], points[index+2], points[index+3], points[index+4], points[index+5]); - break; - case PathIterator.SEG_CLOSE: - closePath(); - break; - default: - throw new IllegalArgumentException("Unhandled Segment Type: "+type); - } - path.next(); - connect = false; - } - } - - public SVertex getCurrentPoint() { - if (m_typeSize == 0) { - return null; - } - int j = m_pointSize - 2; - if (m_types[m_typeSize - 1] == PathIterator.SEG_CLOSE) { - - for (int i = m_typeSize - 2; i > 0; i--) { - final int type = m_types[i]; - if (type == PathIterator.SEG_MOVETO) { - break; - } - j -= pointShift[type]; - } - } - return new SVertex(m_points[j], m_points[j + 1], 0f, true); - } - - public void reset() { - m_typeSize = 0; - m_pointSize = 0; - } - - public void transform(final AffineTransform t) { - t.transform(m_points, 0, m_points, 0, m_pointSize / 2); - } - - public Path2D createTransformedShape(final AffineTransform t) { - final Path2D p = (Path2D)clone(); - if (t != null) { - p.transform(t); - } - return p; - } - - public final synchronized AABBox getBounds2D() { - float rx1, ry1, rx2, ry2; - if (m_pointSize == 0) { - rx1 = ry1 = rx2 = ry2 = 0.0f; - } else { - int i = m_pointSize - 1; - ry1 = ry2 = m_points[i--]; - rx1 = rx2 = m_points[i--]; - while (i > 0) { - final float y = m_points[i--]; - final float x = m_points[i--]; - if (x < rx1) { - rx1 = x; - } else - if (x > rx2) { - rx2 = x; - } - if (y < ry1) { - ry1 = y; - } else - if (y > ry2) { - ry2 = y; - } - } - } - return new AABBox(rx1, ry1, 0f, rx2, ry2, 0f); - } - - /** - * 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(final int cross) { - if (m_rule == WIND_NON_ZERO) { - return Crossing.isInsideNonZero(cross); - } - return Crossing.isInsideEvenOdd(cross); - } - - public boolean contains(final float px, final float py) { - return isInside(Crossing.crossShape(this, px, py)); - } - - public boolean contains(final float rx, final float ry, final float rw, final float rh) { - final int cross = Crossing.intersectShape(this, rx, ry, rw, rh); - return cross != Crossing.CROSSING && isInside(cross); - } - - public boolean intersects(final float rx, final float ry, final float rw, final float rh) { - final int cross = Crossing.intersectShape(this, rx, ry, rw, rh); - return cross == Crossing.CROSSING || isInside(cross); - } - - public boolean contains(final Vertex p) { - return contains(p.getX(), p.getY()); - } - - public boolean contains(final AABBox r) { - return contains(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight()); - } - - public boolean intersects(final AABBox r) { - return intersects(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight()); - } - - public PathIterator iterator() { - return new Iterator(this); - } - - public PathIterator iterator(final AffineTransform t) { - return new Iterator(this, t); - } - - /* public PathIterator getPathIterator(AffineTransform t, float flatness) { - return new FlatteningPathIterator(getPathIterator(t), flatness); - } */ - - @Override - public Object clone() { - try { - final Path2D p = (Path2D) super.clone(); - p.m_types = m_types.clone(); - p.m_points = m_points.clone(); - return p; - } catch (final CloneNotSupportedException e) { - throw new InternalError(); - } - } -} - diff --git a/src/jogl/classes/com/jogamp/graph/geom/plane/Path2F.java b/src/jogl/classes/com/jogamp/graph/geom/plane/Path2F.java new file mode 100644 index 000000000..588232d9a --- /dev/null +++ b/src/jogl/classes/com/jogamp/graph/geom/plane/Path2F.java @@ -0,0 +1,603 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +/** + * @author Denis M. Kishenko + * @author Sven Gothel + */ +package com.jogamp.graph.geom.plane; + +import java.io.PrintStream; +import java.util.NoSuchElementException; + +import com.jogamp.opengl.math.geom.AABBox; + +/** + * Path2F represents and provides construction method for a 2D shape using float[2] points. + */ +public final class Path2F implements Cloneable { + static final String invalidWindingRuleValue = "Invalid winding rule value"; + static final String iteratorOutOfBounds = "Iterator out of bounds"; + + /** A Path2D segment type */ + public static enum SegmentType { + MOVETO(1), + LINETO(1), + QUADTO(2), + CUBICTO(3), + CLOSE(0); + + /** Number of points associated with this segment type */ + public final int point_count; + + /** Return the integer segment type value as a byte */ + public byte integer() { + return (byte) this.ordinal(); + } + + /** Return the SegmentType associated with the integer segment type */ + public static SegmentType valueOf(final int type) { + switch( type ) { + case 0: return MOVETO; + case 1: return LINETO; + case 2: return QUADTO; + case 3: return CUBICTO; + case 4: return CLOSE; + default: + throw new IllegalArgumentException("Unhandled Segment Type: "+type); + } + } + + /** Return the number of points associated with the integer segment type */ + public static int getPointCount(final int type) { + switch( type ) { + case 0: return MOVETO.point_count; + case 1: return LINETO.point_count; + case 2: return QUADTO.point_count; + case 3: return CUBICTO.point_count; + case 4: return CLOSE.point_count; + default: + throw new IllegalArgumentException("Unhandled Segment Type: "+type); + } + } + + SegmentType(final int v) { + this.point_count = v; + } + } + + /** + * The buffers size + */ + private static final int BUFFER_SIZE = 10; + + /** + * The buffers capacity + */ + private static final int BUFFER_CAPACITY = 10; + + /** + * The point's types buffer + */ + private byte[] m_types; + + /** + * The points buffer + */ + private float[] m_points; + + /** + * The point's type buffer size + */ + private int m_typeSize; + + /** + * The points buffer size + */ + private int m_pointSize; + + /** + * The winding path rule + */ + private WindingRule m_rule; + + /* + * GeneralPath path iterator + */ + public static final class Iterator { + + /** + * The source GeneralPath object + */ + private final Path2F p; + + /** + * The path iterator transformation + */ + private final AffineTransform t; + + /** + * The current cursor position in types buffer + */ + private int typeIndex; + + /** + * The current cursor position in points buffer + */ + private int pointIndex; + + /** + * Constructs a new GeneralPath.Iterator for given general path + * @param path - the source GeneralPath object + */ + Iterator(final Path2F path) { + this(path, null); + } + + /** + * Constructs a new GeneralPath.Iterator for given general path and transformation + * @param path - the source GeneralPath object + * @param at - the AffineTransform object to apply rectangle path + */ + public Iterator(final Path2F path, final AffineTransform at) { + this.p = path; + this.t = at; + reset(); + } + + private void reset() { + typeIndex = 0; + pointIndex = 0; + } + + /** Return the {@link WindingRule} set */ + public WindingRule getWindingRule() { + return p.getWindingRule(); + } + + /** + * Compute the general winding of the vertices + * @return CCW or CW {@link Winding} + */ + public Winding getWinding() { + return area() >= 0 ? Winding.CCW : Winding.CW ; + } + + /** Returns reference of the point array covering the whole iteration of Path2D, use {@link #index()} to access the current point. */ + public float[] points() { return p.m_points; } + + /** Return the {@link #points()} index for the current segment. */ + public int index() { return pointIndex; } + + /** Return current segment type */ + public SegmentType getType() { return SegmentType.valueOf( p.m_types[typeIndex] ); } + + /** + * Return the current segment type and copies the current segment's points to given storage + * @param coords storage for current segment's points + * @return current segment type + * @see #points() + * @see #type_index() + * @see #getType() + * @deprecated try to use {@link #index()}, {@link #points()} and {@link #next()} to avoid copying + */ + @Deprecated + public SegmentType currentSegment(final float[] coords) { + if (!hasNext()) { + throw new NoSuchElementException(iteratorOutOfBounds); + } + final SegmentType type = getType(); + final int count = type.point_count; + System.arraycopy(p.m_points, pointIndex, coords, 0, count*2); + if (t != null) { + t.transform(coords, 0, coords, 0, count); + } + return type; + } + + /** Returns true if the iteration has more elements. */ + public boolean hasNext() { + return typeIndex < p.m_typeSize; + } + + /** Returns the current segment type in the iteration, then moving to the next path segment. */ + public SegmentType next() { + final SegmentType t = getType(); + pointIndex += 2 * t.point_count; + ++typeIndex; + return t; + } + + /** + * Computes the area of the path to check if ccw + * @return positive area if ccw else negative area value + */ + private float area() { + float area = 0.0f; + final float[] points = points(); + final float[] pCoord = new float[2]; + while ( hasNext() ) { + final int idx = index(); + final SegmentType type = next(); + switch ( type ) { + case MOVETO: + pCoord[0] = points[idx+0]; + pCoord[1] = points[idx+1]; + break; + case LINETO: + area += pCoord[0] * points[idx+1] - points[idx+0] * pCoord[1]; + pCoord[0] = points[idx+0]; + pCoord[1] = points[idx+1]; + break; + case QUADTO: + area += pCoord[0] * points[idx+1] - points[idx+0] * pCoord[1]; + area += points[idx+0] * points[idx+3] - points[idx+2] * points[idx+1]; + pCoord[0] = points[idx+2]; + pCoord[1] = points[idx+3]; + break; + case CUBICTO: + area += pCoord[0] * points[idx+1] - points[idx+0] * pCoord[1]; + area += points[idx+0] * points[idx+3] - points[idx+2] * points[idx+1]; + area += points[idx+2] * points[idx+5] - points[idx+4] * points[idx+3]; + pCoord[0] = points[idx+4]; + pCoord[1] = points[idx+5]; + break; + case CLOSE: + break; + } + } + reset(); + return area; + } + } + + public Path2F() { + this(WindingRule.NON_ZERO, BUFFER_SIZE, BUFFER_SIZE); + } + + public Path2F(final WindingRule rule) { + this(rule, BUFFER_SIZE, BUFFER_SIZE); + } + + public Path2F(final WindingRule rule, final int initialCapacity) { + this(rule, initialCapacity, initialCapacity); + } + + public Path2F(final WindingRule rule, final int initialTypeCapacity, final int initialPointCapacity) { + setWindingRule(rule); + m_types = new byte[initialTypeCapacity]; + m_points = new float[initialPointCapacity * 2]; + } + + public Path2F(final Path2F path) { + this(WindingRule.NON_ZERO, BUFFER_SIZE); + final Iterator p = path.iterator(null); + setWindingRule(p.getWindingRule()); + append(p, false); + } + + /** Set the {@link WindingRule} set */ + public void setWindingRule(final WindingRule rule) { + this.m_rule = rule; + } + + /** Return the {@link WindingRule} set */ + public WindingRule getWindingRule() { + return m_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(final int pointCount, final boolean checkMove) { + if (checkMove && m_typeSize == 0) { + throw new IllegalPathStateException("First segment should be SEG_MOVETO type"); + } + if (m_typeSize == m_types.length) { + final byte tmp[] = new byte[m_typeSize + BUFFER_CAPACITY]; + System.arraycopy(m_types, 0, tmp, 0, m_typeSize); + m_types = tmp; + } + if (m_pointSize + pointCount > m_points.length) { + final float tmp[] = new float[m_pointSize + Math.max(BUFFER_CAPACITY * 2, pointCount)]; + System.arraycopy(m_points, 0, tmp, 0, m_pointSize); + m_points = tmp; + } + } + + /** + * Start a new position for the next line segment at given point x/y (P1). + * @param x point (P1) + * @param y point (P1) + */ + public void moveTo(final float x, final float y) { + if ( m_typeSize > 0 && m_types[m_typeSize - 1] == SegmentType.MOVETO.integer() ) { + m_points[m_pointSize - 2] = x; + m_points[m_pointSize - 1] = y; + } else { + checkBuf(2, false); + m_types[m_typeSize++] = SegmentType.MOVETO.integer(); + m_points[m_pointSize++] = x; + m_points[m_pointSize++] = y; + } + } + + /** + * Add a line segment, intersecting the last point and the given point x/y (P1). + * @param x final point (P1) + * @param y final point (P1) + */ + public void lineTo(final float x, final float y) { + checkBuf(2, true); + m_types[m_typeSize++] = SegmentType.LINETO.integer(); + m_points[m_pointSize++] = x; + m_points[m_pointSize++] = y; + } + + /** + * Add a quadratic curve segment, intersecting the last point and the second given point x2/y2 (P2). + * @param x1 quadratic parametric control point (P1) + * @param y1 quadratic parametric control point (P1) + * @param x2 final interpolated control point (P2) + * @param y2 final interpolated control point (P2) + */ + public void quadTo(final float x1, final float y1, final float x2, final float y2) { + checkBuf(4, true); + m_types[m_typeSize++] = SegmentType.QUADTO.integer(); + m_points[m_pointSize++] = x1; + m_points[m_pointSize++] = y1; + m_points[m_pointSize++] = x2; + m_points[m_pointSize++] = y2; + } + + /** + * Add a cubic Bézier curve segment, intersecting the last point and the second given point x3/y3 (P3). + * @param x1 Bézier control point (P1) + * @param y1 Bézier control point (P1) + * @param x2 Bézier control point (P2) + * @param y2 Bézier control point (P2) + * @param x3 final interpolated control point (P3) + * @param y3 final interpolated control point (P3) + */ + public void cubicTo(final float x1, final float y1, final float x2, final float y2, final float x3, final float y3) { + checkBuf(6, true); + m_types[m_typeSize++] = SegmentType.CUBICTO.integer(); + m_points[m_pointSize++] = x1; + m_points[m_pointSize++] = y1; + m_points[m_pointSize++] = x2; + m_points[m_pointSize++] = y2; + m_points[m_pointSize++] = x3; + m_points[m_pointSize++] = y3; + } + + /** + * Closes the current sub-path segment by drawing a straight line back to the coordinates of the last moveTo. If the path is already closed then this method has no effect. + */ + public void closePath() { + if (!isClosed()) { + checkBuf(0, true); + m_types[m_typeSize++] = SegmentType.CLOSE.integer(); + } + } + + final public int size() { + return m_typeSize; + } + + /** + * Returns true if the last sub-path is closed, otherwise false. + */ + final public boolean isClosed() { + return m_typeSize > 0 && m_types[m_typeSize - 1] == SegmentType.CLOSE.integer() ; + } + + /** + * Compute the general winding of the vertices + * @param vertices array of Vertices + * @return CCW or CW {@link Winding} + */ + public Winding getWinding() { + return iterator(null).getWinding(); + } + + @Override + public String toString() { + return "[size "+size()+", closed "+isClosed()+", winding[rule "+getWindingRule()+", "+getWinding()+"]]"; + } + + /** + * Append the given path geometry to this instance + * @param path the {@link Path2F} to append to this instance + * @param connect pass true to turn an initial moveTo segment into a lineTo segment to connect the new geometry to the existing path, otherwise pass false. + */ + public void append(final Path2F path, final boolean connect) { + append(path.iterator(null), connect); + } + + /** + * Append the given path geometry to this instance + * @param path the {@link Path2F.Iterator} to append to this instance + * @param connect pass true to turn an initial moveTo segment into a lineTo segment to connect the new geometry to the existing path, otherwise pass false. + */ + public void append(final Iterator path, boolean connect) { + final float[] points = path.points(); + while ( path.hasNext() ) { + final int idx = path.index(); + final SegmentType type = path.next(); + switch ( type ) { + case MOVETO: + if ( !connect || 0 == m_typeSize ) { + moveTo(points[idx+0], points[idx+1]); + break; + } + if ( m_types[m_typeSize - 1] != SegmentType.CLOSE.integer() && + m_points[m_pointSize - 2] == points[idx+0] && + m_points[m_pointSize - 1] == points[idx+1] + ) + { + break; + } + // fallthrough: MOVETO -> LINETO + case LINETO: + lineTo(points[idx+0], points[idx+1]); + break; + case QUADTO: + quadTo(points[idx+0], points[idx+1], points[idx+2], points[idx+3]); + break; + case CUBICTO: + cubicTo(points[idx+0], points[idx+1], points[idx+2], points[idx+3], points[idx+4], points[idx+5]); + break; + case CLOSE: + closePath(); + break; + } + connect = false; + } + } + + public void printSegments(final PrintStream out) { + final Iterator path = iterator(); + final float[] points = path.points(); + int i = 0; + while ( path.hasNext() ) { + final int idx = path.index(); + final SegmentType type = path.next(); + switch ( type ) { + case MOVETO: + out.printf("%2d: moveTo(%.4f/%.4f)%n", i, points[idx+0], points[idx+1]); + break; + case LINETO: + out.printf("%2d: lineTo(%.4f/%.4f)%n", i, points[idx+0], points[idx+1]); + break; + case QUADTO: + out.printf("%2d: quadTo(%.4f/%.4f, %.4f/%.4f)%n", i, points[idx+0], points[idx+1], points[idx+2], points[idx+3]); + break; + case CUBICTO: + out.printf("%2d: cubicTo(%.4f/%.4f, %.4f/%.4f, %.4f/%.4f)%n", i, points[idx+0], points[idx+1], points[idx+2], points[idx+3], points[idx+4], points[idx+5]); + break; + case CLOSE: + out.printf("%2d: closePath()%n", i); + break; + } + ++i; + } + } + + public void reset() { + m_typeSize = 0; + m_pointSize = 0; + } + + public void transform(final AffineTransform t) { + t.transform(m_points, 0, m_points, 0, m_pointSize / 2); + } + + public Path2F createTransformedShape(final AffineTransform t) { + final Path2F p = (Path2F)clone(); + if (t != null) { + p.transform(t); + } + return p; + } + + public final synchronized AABBox getBounds2D() { + float rx1, ry1, rx2, ry2; + if (m_pointSize == 0) { + rx1 = ry1 = rx2 = ry2 = 0.0f; + } else { + int i = m_pointSize - 1; + ry1 = ry2 = m_points[i--]; + rx1 = rx2 = m_points[i--]; + while (i > 0) { + final float y = m_points[i--]; + final float x = m_points[i--]; + if (x < rx1) { + rx1 = x; + } else + if (x > rx2) { + rx2 = x; + } + if (y < ry1) { + ry1 = y; + } else + if (y > ry2) { + ry2 = y; + } + } + } + return new AABBox(rx1, ry1, 0f, rx2, ry2, 0f); + } + + /** + * 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(final int cross) { + if (m_rule == WindingRule.NON_ZERO) { + return Crossing2F.isInsideNonZero(cross); + } + return Crossing2F.isInsideEvenOdd(cross); + } + + public boolean contains(final float px, final float py) { + return isInside(Crossing2F.crossShape(this, px, py)); + } + + public boolean contains(final float rx, final float ry, final float rw, final float rh) { + final int cross = Crossing2F.intersectShape(this, rx, ry, rw, rh); + return cross != Crossing2F.CROSSING && isInside(cross); + } + + public boolean intersects(final float rx, final float ry, final float rw, final float rh) { + final int cross = Crossing2F.intersectShape(this, rx, ry, rw, rh); + return cross == Crossing2F.CROSSING || isInside(cross); + } + + public boolean contains(final AABBox r) { + return contains(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight()); + } + + public boolean intersects(final AABBox r) { + return intersects(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight()); + } + + public Iterator iterator() { + return new Iterator(this); + } + + public Iterator iterator(final AffineTransform t) { + return new Iterator(this, t); + } + + /* public Path2F.Iterator getPathIterator(AffineTransform t, float flatness) { + return new FlatteningPathIterator(getPathIterator(t), flatness); + } */ + + @Override + public Object clone() { + try { + final Path2F p = (Path2F) super.clone(); + p.m_types = m_types.clone(); + p.m_points = m_points.clone(); + return p; + } catch (final CloneNotSupportedException e) { + throw new InternalError(); + } + } +} + diff --git a/src/jogl/classes/com/jogamp/graph/geom/plane/PathIterator.java b/src/jogl/classes/com/jogamp/graph/geom/plane/PathIterator.java deleted file mode 100644 index 3aae2a172..000000000 --- a/src/jogl/classes/com/jogamp/graph/geom/plane/PathIterator.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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. - */ -/** - * @author Denis M. Kishenko - * @author Sven Gothel - */ -package com.jogamp.graph.geom.plane; - -public interface PathIterator { - - public static final int WIND_EVEN_ODD = 0; - public static final int WIND_NON_ZERO = 1; - - public static final int SEG_MOVETO = 0; - public static final int SEG_LINETO = 1; - public static final int SEG_QUADTO = 2; - public static final int SEG_CUBICTO = 3; - public static final int SEG_CLOSE = 4; - - int getWindingRule(); - - /** Return the current {@link #points()} index for the current segment. */ - int index(); - - /** Returns reference of the point array for the whole Path2D */ - float[] points(); - - /** Return current segment type */ - int getType(final int idx); - - /** Returns true if completed */ - boolean isDone(); - - void next(); - - /** - * Return the path segment type and copies the current segment's points to given storage - * @param coords storage for current segment's points - * @return segment type - * @see #points() - * @see #index() - * @see #getType(int) - */ - int currentSegment(float[] coords); -} - diff --git a/src/jogl/classes/com/jogamp/graph/geom/plane/Winding.java b/src/jogl/classes/com/jogamp/graph/geom/plane/Winding.java new file mode 100644 index 000000000..bfa214c22 --- /dev/null +++ b/src/jogl/classes/com/jogamp/graph/geom/plane/Winding.java @@ -0,0 +1,18 @@ +package com.jogamp.graph.geom.plane; + +/** + * Winding direction, either clockwise (CW) or counter-clockwise (CCW). + */ +public enum Winding { + /** Clockwise (Cw) negative winding direction */ + CW(-1), + /** Counter-Clockwise (CCW) positive winding direction */ + CCW(1); + + /** The winding direction sign, i.e. positive 1 for CCW and negative -1 for CW. */ + public final int dir; + + Winding(final int dir) { + this.dir = dir; + } +} diff --git a/src/jogl/classes/com/jogamp/graph/geom/plane/WindingRule.java b/src/jogl/classes/com/jogamp/graph/geom/plane/WindingRule.java new file mode 100644 index 000000000..46ef167a3 --- /dev/null +++ b/src/jogl/classes/com/jogamp/graph/geom/plane/WindingRule.java @@ -0,0 +1,28 @@ +package com.jogamp.graph.geom.plane; + +/** + * Winding rule, either EVEN_ODD or NON_ZERO (like for TrueType fonts). + */ +public enum WindingRule { + /** + * The even-odd rule specifies that a point lies inside the path + * if a ray drawn in any direction from that point to infinity is crossed by path segments + * an odd number of times. + */ + EVEN_ODD(0), + + /** + * The non-zero rule specifies that a point lies inside the path + * if a ray drawn in any direction from that point to infinity is crossed by path segments + * a different number of times in the counter-clockwise direction than the clockwise direction. + * + * Non-zero winding rule is used by TrueType fonts. + */ + NON_ZERO(1); + + public final int value; + + WindingRule(final int v) { + this.value = v; + } +} -- cgit v1.2.3