aboutsummaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/com/jogamp/graph/geom/plane/Path2F.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jogl/classes/com/jogamp/graph/geom/plane/Path2F.java')
-rw-r--r--src/jogl/classes/com/jogamp/graph/geom/plane/Path2F.java603
1 files changed, 603 insertions, 0 deletions
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();
+ }
+ }
+}
+