diff options
Diffstat (limited to 'src/jogl/classes/com/jogamp/graph')
6 files changed, 2041 insertions, 0 deletions
diff --git a/src/jogl/classes/com/jogamp/graph/geom/plane/AffineTransform.java b/src/jogl/classes/com/jogamp/graph/geom/plane/AffineTransform.java new file mode 100644 index 000000000..a9978abe5 --- /dev/null +++ b/src/jogl/classes/com/jogamp/graph/geom/plane/AffineTransform.java @@ -0,0 +1,595 @@ +/* + * 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 jogamp.graph.geom.plane; + +// import jogamp.opengl.util.HashCode; + +import com.jogamp.graph.geom.Vertex; +import com.jogamp.opengl.math.FloatUtil; +import com.jogamp.opengl.math.geom.AABBox; + +public class AffineTransform implements Cloneable { + + static final String determinantIsZero = "Determinant is zero"; + + public static final int TYPE_IDENTITY = 0; + public static final int TYPE_TRANSLATION = 1; + public static final int TYPE_UNIFORM_SCALE = 2; + public static final int TYPE_GENERAL_SCALE = 4; + public static final int TYPE_QUADRANT_ROTATION = 8; + public static final int TYPE_GENERAL_ROTATION = 16; + public static final int TYPE_GENERAL_TRANSFORM = 32; + public static final int TYPE_FLIP = 64; + public static final int TYPE_MASK_SCALE = TYPE_UNIFORM_SCALE | TYPE_GENERAL_SCALE; + public static final int TYPE_MASK_ROTATION = TYPE_QUADRANT_ROTATION | TYPE_GENERAL_ROTATION; + + /** + * The <code>TYPE_UNKNOWN</code> is an initial type value + */ + static final int TYPE_UNKNOWN = -1; + + /** + * The min value equivalent to zero. If absolute value less then ZERO it considered as zero. + */ + static final float ZERO = (float) 1E-10; + + /** + * The values of transformation matrix + */ + float m00; + float m10; + float m01; + float m11; + float m02; + float m12; + + /** + * The transformation <code>type</code> + */ + transient int type; + + public AffineTransform() { + setToIdentity(); + } + + public AffineTransform(final AffineTransform t) { + this.type = t.type; + this.m00 = t.m00; + this.m10 = t.m10; + this.m01 = t.m01; + this.m11 = t.m11; + this.m02 = t.m02; + this.m12 = t.m12; + } + + public AffineTransform(final float m00, final float m10, final float m01, final float m11, final float m02, final float m12) { + this.type = TYPE_UNKNOWN; + this.m00 = m00; + this.m10 = m10; + this.m01 = m01; + this.m11 = m11; + this.m02 = m02; + this.m12 = m12; + } + + public AffineTransform(final float[] matrix) { + this.type = TYPE_UNKNOWN; + m00 = matrix[0]; + m10 = matrix[1]; + m01 = matrix[2]; + m11 = matrix[3]; + if (matrix.length > 4) { + m02 = matrix[4]; + m12 = matrix[5]; + } + } + + /* + * Method returns type of affine transformation. + * + * Transform matrix is + * m00 m01 m02 + * m10 m11 m12 + * + * According analytic geometry new basis vectors are (m00, m01) and (m10, m11), + * translation vector is (m02, m12). Original basis vectors are (1, 0) and (0, 1). + * Type transformations classification: + * TYPE_IDENTITY - new basis equals original one and zero translation + * TYPE_TRANSLATION - translation vector isn't zero + * TYPE_UNIFORM_SCALE - vectors length of new basis equals + * TYPE_GENERAL_SCALE - vectors length of new basis doesn't equal + * TYPE_FLIP - new basis vector orientation differ from original one + * TYPE_QUADRANT_ROTATION - new basis is rotated by 90, 180, 270, or 360 degrees + * TYPE_GENERAL_ROTATION - new basis is rotated by arbitrary angle + * TYPE_GENERAL_TRANSFORM - transformation can't be inversed + */ + public int getType() { + if (type != TYPE_UNKNOWN) { + return type; + } + + int type = 0; + + if (m00 * m01 + m10 * m11 != 0.0) { + type |= TYPE_GENERAL_TRANSFORM; + return type; + } + + if (m02 != 0.0 || m12 != 0.0) { + type |= TYPE_TRANSLATION; + } else + if (m00 == 1.0 && m11 == 1.0 && m01 == 0.0 && m10 == 0.0) { + type = TYPE_IDENTITY; + return type; + } + + if (m00 * m11 - m01 * m10 < 0.0) { + type |= TYPE_FLIP; + } + + final float dx = m00 * m00 + m10 * m10; + final float dy = m01 * m01 + m11 * m11; + if (dx != dy) { + type |= TYPE_GENERAL_SCALE; + } else + if (dx != 1.0) { + type |= TYPE_UNIFORM_SCALE; + } + + if ((m00 == 0.0 && m11 == 0.0) || + (m10 == 0.0 && m01 == 0.0 && (m00 < 0.0 || m11 < 0.0))) + { + type |= TYPE_QUADRANT_ROTATION; + } else + if (m01 != 0.0 || m10 != 0.0) { + type |= TYPE_GENERAL_ROTATION; + } + + return type; + } + + public final float getScaleX() { + return m00; + } + + public final float getScaleY() { + return m11; + } + + public final float getShearX() { + return m01; + } + + public final float getShearY() { + return m10; + } + + public final float getTranslateX() { + return m02; + } + + public final float getTranslateY() { + return m12; + } + + public final boolean isIdentity() { + return getType() == TYPE_IDENTITY; + } + + public final void getMatrix(final float[] matrix) { + matrix[0] = m00; + matrix[1] = m10; + matrix[2] = m01; + matrix[3] = m11; + if (matrix.length > 4) { + matrix[4] = m02; + matrix[5] = m12; + } + } + + public final float getDeterminant() { + return m00 * m11 - m01 * m10; + } + + public final AffineTransform setTransform(final float m00, final float m10, final float m01, final float m11, final float m02, final float m12) { + this.type = TYPE_UNKNOWN; + this.m00 = m00; + this.m10 = m10; + this.m01 = m01; + this.m11 = m11; + this.m02 = m02; + this.m12 = m12; + return this; + } + + public final AffineTransform setTransform(final AffineTransform t) { + type = t.type; + setTransform(t.m00, t.m10, t.m01, t.m11, t.m02, t.m12); + return this; + } + + public final AffineTransform setToIdentity() { + type = TYPE_IDENTITY; + m00 = m11 = 1.0f; + m10 = m01 = m02 = m12 = 0.0f; + return this; + } + + public final AffineTransform setToTranslation(final float mx, final float my) { + m00 = m11 = 1.0f; + m01 = m10 = 0.0f; + m02 = mx; + m12 = my; + if (mx == 0.0f && my == 0.0f) { + type = TYPE_IDENTITY; + } else { + type = TYPE_TRANSLATION; + } + return this; + } + + public final AffineTransform setToScale(final float scx, final float scy) { + m00 = scx; + m11 = scy; + m10 = m01 = m02 = m12 = 0.0f; + if (scx != 1.0f || scy != 1.0f) { + type = TYPE_UNKNOWN; + } else { + type = TYPE_IDENTITY; + } + return this; + } + + public final AffineTransform setToShear(final float shx, final float shy) { + m00 = m11 = 1.0f; + m02 = m12 = 0.0f; + m01 = shx; + m10 = shy; + if (shx != 0.0f || shy != 0.0f) { + type = TYPE_UNKNOWN; + } else { + type = TYPE_IDENTITY; + } + return this; + } + + public final AffineTransform setToRotation(final float angle) { + float sin = FloatUtil.sin(angle); + float cos = FloatUtil.cos(angle); + if (FloatUtil.abs(cos) < ZERO) { + cos = 0.0f; + sin = sin > 0.0f ? 1.0f : -1.0f; + } else + if (FloatUtil.abs(sin) < ZERO) { + sin = 0.0f; + cos = cos > 0.0f ? 1.0f : -1.0f; + } + m00 = m11 = cos; + m01 = -sin; + m10 = sin; + m02 = m12 = 0.0f; + type = TYPE_UNKNOWN; + return this; + } + + public final AffineTransform setToRotation(final float angle, final float px, final float py) { + setToRotation(angle); + m02 = px * (1.0f - m00) + py * m10; + m12 = py * (1.0f - m00) - px * m10; + type = TYPE_UNKNOWN; + return this; + } + + public final AffineTransform translate(final float mx, final float my, final AffineTransform tmp) { + return concatenate(tmp.setToTranslation(mx, my)); + } + + public final AffineTransform scale(final float scx, final float scy, final AffineTransform tmp) { + return concatenate(tmp.setToScale(scx, scy)); + } + + public final AffineTransform shear(final float shx, final float shy, final AffineTransform tmp) { + return concatenate(tmp.setToShear(shx, shy)); + } + + public final AffineTransform rotate(final float angle, final AffineTransform tmp) { + return concatenate(tmp.setToRotation(angle)); + } + + public final AffineTransform rotate(final float angle, final float px, final float py, final AffineTransform tmp) { + return concatenate(tmp.setToRotation(angle, px, py)); + } + + /** + * Multiply matrix of two AffineTransform objects. + * @param tL - the AffineTransform object is a multiplicand (left argument) + * @param tR - the AffineTransform object is a multiplier (right argument) + * + * @return A new AffineTransform object containing the result of [tL] X [tR]. + */ + public final static AffineTransform multiply(final AffineTransform tL, final AffineTransform tR) { + return new AffineTransform( + tR.m00 * tL.m00 + tR.m10 * tL.m01, // m00 + tR.m00 * tL.m10 + tR.m10 * tL.m11, // m10 + tR.m01 * tL.m00 + tR.m11 * tL.m01, // m01 + tR.m01 * tL.m10 + tR.m11 * tL.m11, // m11 + tR.m02 * tL.m00 + tR.m12 * tL.m01 + tL.m02, // m02 + tR.m02 * tL.m10 + tR.m12 * tL.m11 + tL.m12);// m12 + } + + /** + * Concatenates the given matrix to this. + * <p> + * Implementations performs the matrix multiplication: + * <pre> + * [this] = [this] X [tR] + * </pre> + * </p> + * @param tR the right-argument of the matrix multiplication + * @return this transform for chaining + */ + public final AffineTransform concatenate(final AffineTransform tR) { + // setTransform(multiply(this, tR)); + type = TYPE_UNKNOWN; + setTransform( + tR.m00 * m00 + tR.m10 * m01, // m00 + tR.m00 * m10 + tR.m10 * m11, // m10 + tR.m01 * m00 + tR.m11 * m01, // m01 + tR.m01 * m10 + tR.m11 * m11, // m11 + tR.m02 * m00 + tR.m12 * m01 + m02, // m02 + tR.m02 * m10 + tR.m12 * m11 + m12);// m12 + return this; + } + + /** + * Pre-concatenates the given matrix to this. + * <p> + * Implementations performs the matrix multiplication: + * <pre> + * [this] = [tL] X [this] + * </pre> + * </p> + * @param tL the left-argument of the matrix multiplication + * @return this transform for chaining + */ + public final AffineTransform preConcatenate(final AffineTransform tL) { + // setTransform(multiply(tL, this)); + type = TYPE_UNKNOWN; + setTransform( + m00 * tL.m00 + m10 * tL.m01, // m00 + m00 * tL.m10 + m10 * tL.m11, // m10 + m01 * tL.m00 + m11 * tL.m01, // m01 + m01 * tL.m10 + m11 * tL.m11, // m11 + m02 * tL.m00 + m12 * tL.m01 + tL.m02, // m02 + m02 * tL.m10 + m12 * tL.m11 + tL.m12);// m12 + return this; + } + + public final AffineTransform createInverse() throws NoninvertibleTransformException { + final float det = getDeterminant(); + if (FloatUtil.abs(det) < ZERO) { + throw new NoninvertibleTransformException(determinantIsZero); + } + return new AffineTransform( + m11 / det, // m00 + -m10 / det, // m10 + -m01 / det, // m01 + m00 / det, // m11 + (m01 * m12 - m11 * m02) / det, // m02 + (m10 * m02 - m00 * m12) / det // m12 + ); + } + + /** + * + * @param src + * @param dst + * @return dst for chaining + */ + public final AABBox transform(final AABBox src, final AABBox dst) { + final float[] srcLo = src.getLow(); + final float[] srcHi = src.getHigh(); + dst.setSize(srcLo[0] * m00 + srcLo[1] * m01 + m02, srcLo[0] * m10 + srcLo[1] * m11 + m12, srcLo[2], + srcHi[0] * m00 + srcHi[1] * m01 + m02, srcHi[0] * m10 + srcHi[1] * m11 + m12, srcHi[2]); + return dst; + } + + /** + * @param src + * @param dst + * @return dst for chaining + */ + public final Vertex transform(final Vertex src, final Vertex dst) { + final float x = src.getX(); + final float y = src.getY(); + dst.setCoord(x * m00 + y * m01 + m02, x * m10 + y * m11 + m12, src.getZ()); + return dst; + } + + public final void transform(final Vertex[] src, int srcOff, final Vertex[] dst, int dstOff, int length) { + while (--length >= 0) { + final Vertex srcPoint = src[srcOff++]; + final Vertex dstPoint = dst[dstOff]; + if (dstPoint == null) { + throw new IllegalArgumentException("dst["+dstOff+"] is null"); + } + final float x = srcPoint.getX(); + final float y = srcPoint.getY(); + dstPoint.setCoord(x * m00 + y * m01 + m02, x * m10 + y * m11 + m12, srcPoint.getZ()); + dst[dstOff++] = dstPoint; + } + } + + /** + * @param src float[2] source of transformation + * @param dst float[2] destination of transformation, maybe be equal to <code>src</code> + * @return dst for chaining + */ + public final float[] transform(final float[] src, final float[] dst) { + final float x = src[0]; + final float y = src[1]; + dst[0] = x * m00 + y * m01 + m02; + dst[1] = x * m10 + y * m11 + m12; + return dst; + } + + public final void transform(final float[] src, final int srcOff, final float[] dst, final int dstOff) { + final float x = src[srcOff + 0]; + final float y = src[srcOff + 1]; + dst[dstOff + 0] = x * m00 + y * m01 + m02; + dst[dstOff + 1] = x * m10 + y * m11 + m12; + } + + public final void transform(final float[] src, int srcOff, final float[] dst, int dstOff, int length) { + int step = 2; + if (src == dst && srcOff < dstOff && dstOff < srcOff + length * 2) { + srcOff = srcOff + length * 2 - 2; + dstOff = dstOff + length * 2 - 2; + step = -2; + } + while (--length >= 0) { + final float x = src[srcOff + 0]; + final float y = src[srcOff + 1]; + dst[dstOff + 0] = x * m00 + y * m01 + m02; + dst[dstOff + 1] = x * m10 + y * m11 + m12; + srcOff += step; + dstOff += step; + } + } + + /** + * + * @param src + * @param dst + * @return return dst for chaining + */ + public final Vertex deltaTransform(final Vertex src, final Vertex dst) { + final float x = src.getX(); + final float y = src.getY(); + dst.setCoord(x * m00 + y * m01, x * m10 + y * m11, src.getZ()); + return dst; + } + + public final void deltaTransform(final float[] src, int srcOff, final float[] dst, int dstOff, int length) { + while (--length >= 0) { + final float x = src[srcOff++]; + final float y = src[srcOff++]; + dst[dstOff++] = x * m00 + y * m01; + dst[dstOff++] = x * m10 + y * m11; + } + } + + /** + * + * @param src + * @param dst + * @return return dst for chaining + * @throws NoninvertibleTransformException + */ + public final Vertex inverseTransform(final Vertex src, final Vertex dst) throws NoninvertibleTransformException { + final float det = getDeterminant(); + if (FloatUtil.abs(det) < ZERO) { + throw new NoninvertibleTransformException(determinantIsZero); + } + final float x = src.getX() - m02; + final float y = src.getY() - m12; + dst.setCoord((x * m11 - y * m01) / det, (y * m00 - x * m10) / det, src.getZ()); + return dst; + } + + public final void inverseTransform(final float[] src, int srcOff, final float[] dst, int dstOff, int length) + throws NoninvertibleTransformException + { + final float det = getDeterminant(); + if (FloatUtil.abs(det) < ZERO) { + throw new NoninvertibleTransformException(determinantIsZero); + } + + while (--length >= 0) { + final float x = src[srcOff++] - m02; + final float y = src[srcOff++] - m12; + dst[dstOff++] = (x * m11 - y * m01) / det; + dst[dstOff++] = (y * m00 - x * m10) / det; + } + } + + public final Path2D createTransformedShape(final Path2D src) { + if (src == null) { + return null; + } + return src.createTransformedShape(this); + /** + * If !(src instanceof Path2D): (but here it always is) + final PathIterator path = src.iterator(this); + final Path2D dst = new Path2D(path.getWindingRule()); + dst.append(path, false); + return dst; + */ + } + + @Override + public final String toString() { + return + getClass().getName() + + "[[" + m00 + ", " + m01 + ", " + m02 + "], [" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + + m10 + ", " + m11 + ", " + m12 + "]]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + @Override + public final AffineTransform clone() { + try { + return (AffineTransform) super.clone(); + } catch (final CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** @Override + public int hashCode() { + HashCode hash = new HashCode(); + hash.append(m00); + hash.append(m01); + hash.append(m02); + hash.append(m10); + hash.append(m11); + hash.append(m12); + return hash.hashCode(); + } */ + + @Override + public final boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof AffineTransform) { + final AffineTransform t = (AffineTransform)obj; + return + m00 == t.m00 && m01 == t.m01 && + m02 == t.m02 && m10 == t.m10 && + m11 == t.m11 && m12 == t.m12; + } + return false; + } + @Override + public final int hashCode() { + throw new InternalError("hashCode not designed"); + } +} + diff --git a/src/jogl/classes/com/jogamp/graph/geom/plane/Crossing.java b/src/jogl/classes/com/jogamp/graph/geom/plane/Crossing.java new file mode 100644 index 000000000..2ae9d1867 --- /dev/null +++ b/src/jogl/classes/com/jogamp/graph/geom/plane/Crossing.java @@ -0,0 +1,904 @@ +/* + * 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 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 <code>ROOT_DELTA</code> 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/IllegalPathStateException.java b/src/jogl/classes/com/jogamp/graph/geom/plane/IllegalPathStateException.java new file mode 100644 index 000000000..2c18e0834 --- /dev/null +++ b/src/jogl/classes/com/jogamp/graph/geom/plane/IllegalPathStateException.java @@ -0,0 +1,34 @@ +/* + * 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 jogamp.graph.geom.plane; + +public class IllegalPathStateException extends RuntimeException { + + private static final long serialVersionUID = -5158084205220481094L; + + public IllegalPathStateException() { + } + + public IllegalPathStateException(final String s) { + super(s); + } + +} + diff --git a/src/jogl/classes/com/jogamp/graph/geom/plane/NoninvertibleTransformException.java b/src/jogl/classes/com/jogamp/graph/geom/plane/NoninvertibleTransformException.java new file mode 100644 index 000000000..6e49607b7 --- /dev/null +++ b/src/jogl/classes/com/jogamp/graph/geom/plane/NoninvertibleTransformException.java @@ -0,0 +1,31 @@ +/* + * 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 jogamp.graph.geom.plane; + +public class NoninvertibleTransformException extends java.lang.Exception { + + private static final long serialVersionUID = 6137225240503990466L; + + public NoninvertibleTransformException(final String s) { + super(s); + } + +} + diff --git a/src/jogl/classes/com/jogamp/graph/geom/plane/Path2D.java b/src/jogl/classes/com/jogamp/graph/geom/plane/Path2D.java new file mode 100644 index 000000000..191b191b5 --- /dev/null +++ b/src/jogl/classes/com/jogamp/graph/geom/plane/Path2D.java @@ -0,0 +1,435 @@ +/* + * 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 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[] types; + + /** + * The points buffer + */ + float[] points; + + /** + * The point's type buffer size + */ + int typeSize; + + /** + * The points buffer size + */ + int pointSize; + + /** + * The path rule + */ + int rule; + + /** + * The space amount in points buffer for different segmenet's types + */ + 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 boolean isDone() { + return typeIndex >= p.typeSize; + } + + @Override + public void next() { + typeIndex++; + } + + @Override + public int currentSegment(final float[] coords) { + if (isDone()) { + throw new NoSuchElementException(iteratorOutOfBounds); + } + final int type = p.types[typeIndex]; + final int count = Path2D.pointShift[type]; + System.arraycopy(p.points, pointIndex, coords, 0, count); + if (t != null) { + t.transform(coords, 0, coords, 0, count / 2); + } + pointIndex += count; + return 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); + types = new byte[initialCapacity]; + 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.rule = rule; + } + + public int getWindingRule() { + return rule; + } + + /** + * Checks points and types buffer size to add pointCount points. If necessary realloc buffers to enlarge size. + * @param pointCount - the point count to be added in buffer + */ + void checkBuf(final int pointCount, final boolean checkMove) { + if (checkMove && typeSize == 0) { + throw new IllegalPathStateException("First segment should be SEG_MOVETO type"); + } + if (typeSize == types.length) { + final byte tmp[] = new byte[typeSize + BUFFER_CAPACITY]; + System.arraycopy(types, 0, tmp, 0, typeSize); + types = tmp; + } + if (pointSize + pointCount > points.length) { + final float tmp[] = new float[pointSize + Math.max(BUFFER_CAPACITY * 2, pointCount)]; + System.arraycopy(points, 0, tmp, 0, pointSize); + points = tmp; + } + } + + public void moveTo(final float x, final float y) { + if (typeSize > 0 && types[typeSize - 1] == PathIterator.SEG_MOVETO) { + points[pointSize - 2] = x; + points[pointSize - 1] = y; + } else { + checkBuf(2, false); + types[typeSize++] = PathIterator.SEG_MOVETO; + points[pointSize++] = x; + points[pointSize++] = y; + } + } + + public void lineTo(final float x, final float y) { + checkBuf(2, true); + types[typeSize++] = PathIterator.SEG_LINETO; + points[pointSize++] = x; + points[pointSize++] = y; + } + + public void quadTo(final float x1, final float y1, final float x2, final float y2) { + checkBuf(4, true); + types[typeSize++] = PathIterator.SEG_QUADTO; + points[pointSize++] = x1; + points[pointSize++] = y1; + points[pointSize++] = x2; + points[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); + types[typeSize++] = PathIterator.SEG_CUBICTO; + points[pointSize++] = x1; + points[pointSize++] = y1; + points[pointSize++] = x2; + points[pointSize++] = y2; + points[pointSize++] = x3; + points[pointSize++] = y3; + } + + final public int size() { + return typeSize; + } + + final public boolean isClosed() { + return typeSize > 0 && types[typeSize - 1] == PathIterator.SEG_CLOSE ; + } + + public void closePath() { + if (!isClosed()) { + checkBuf(0, true); + types[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); + } + + public void append(final PathIterator path, boolean connect) { + while (!path.isDone()) { + final float coords[] = new float[6]; + final int segmentType = path.currentSegment(coords); + switch (segmentType) { + case PathIterator.SEG_MOVETO: + if (!connect || typeSize == 0) { + moveTo(coords[0], coords[1]); + break; + } + if (types[typeSize - 1] != PathIterator.SEG_CLOSE && + points[pointSize - 2] == coords[0] && + points[pointSize - 1] == coords[1]) + { + break; + } + // NO BREAK; + case PathIterator.SEG_LINETO: + lineTo(coords[0], coords[1]); + break; + case PathIterator.SEG_QUADTO: + quadTo(coords[0], coords[1], coords[2], coords[3]); + break; + case PathIterator.SEG_CUBICTO: + curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); + break; + case PathIterator.SEG_CLOSE: + closePath(); + break; + default: + throw new IllegalArgumentException("Unhandled Segment Type: "+segmentType); + } + path.next(); + connect = false; + } + } + + public SVertex getCurrentPoint() { + if (typeSize == 0) { + return null; + } + int j = pointSize - 2; + if (types[typeSize - 1] == PathIterator.SEG_CLOSE) { + + for (int i = typeSize - 2; i > 0; i--) { + final int type = types[i]; + if (type == PathIterator.SEG_MOVETO) { + break; + } + j -= pointShift[type]; + } + } + return new SVertex(points[j], points[j + 1], 0f, true); + } + + public void reset() { + typeSize = 0; + pointSize = 0; + } + + public void transform(final AffineTransform t) { + t.transform(points, 0, points, 0, 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 (pointSize == 0) { + rx1 = ry1 = rx2 = ry2 = 0.0f; + } else { + int i = pointSize - 1; + ry1 = ry2 = points[i--]; + rx1 = rx2 = points[i--]; + while (i > 0) { + final float y = points[i--]; + final float x = points[i--]; + if (x < rx1) { + rx1 = x; + } else + if (x > rx2) { + rx2 = x; + } + if (y < ry1) { + ry1 = y; + } else + if (y > ry2) { + ry2 = y; + } + } + } + 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 (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.types = types.clone(); + p.points = 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 new file mode 100644 index 000000000..8868a8c58 --- /dev/null +++ b/src/jogl/classes/com/jogamp/graph/geom/plane/PathIterator.java @@ -0,0 +1,42 @@ +/* + * 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 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; + + public int getWindingRule(); + + public boolean isDone(); + + public void next(); + + public int currentSegment(float[] coords); + +} + |