All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.prism.impl.ps.AATesselatorImpl Maven / Gradle / Ivy

/*
 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.prism.impl.ps;

import com.sun.javafx.geom.Line2D;
import com.sun.javafx.geom.PathIterator;
import com.sun.javafx.geom.Shape;
import com.sun.javafx.geom.Vec3f;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.prism.impl.VertexBuffer;
import java.util.ArrayList;
import java.util.List;
import com.sun.prism.util.tess.Tess;
import com.sun.prism.util.tess.Tessellator;
import com.sun.prism.util.tess.TessellatorCallbackAdapter;
import static com.sun.prism.util.tess.Tess.*;

final class AATesselatorImpl {

    private static final float EPSILON = 0.0001f;

    private final AATessCallbacks listener;
    private final Tessellator tess;
    private final float[] coords = new float[6];

    AATesselatorImpl() {
        listener = new AATessCallbacks();
        tess = Tess.newTess();
        Tess.tessCallback(tess, TESS_BEGIN, listener);
        Tess.tessCallback(tess, TESS_VERTEX, listener);
        Tess.tessCallback(tess, TESS_END, listener);
        Tess.tessCallback(tess, TESS_COMBINE, listener);
        Tess.tessCallback(tess, TESS_ERROR, listener);
        // we don't care about the edge flag callback, but registering
        // it here ensures that we only get triangles (no fans or strips)
        Tess.tessCallback(tess, TESS_EDGE_FLAG, listener);
    }

    /**
     * If the shape could be successfully tesselated, returns an int array
     * with two elements (arr[0]==numSolidVerts, arr[1]==numCurveVerts);
     * otherwise returns null (and in which case the given VertexBuffers
     * are not modified).
     */
    int[] generate(Shape shape, VertexBuffer vbSolid, VertexBuffer vbCurve) {
        listener.setVertexBuffer(vbSolid);

        BaseTransform xform = BaseTransform.IDENTITY_TRANSFORM;
        PathIterator pi = shape.getPathIterator(xform);

        List segments = new ArrayList();
        int numCurveVerts = 0;
        float area = 0f;
        float movX = 0f;
        float movY = 0f;
        float curX = 0f;
        float curY = 0f;
        float prevX = 0f;
        float prevY = 0f;
        float tmpX = 0f;
        float tmpY = 0f;
        boolean started = false;
        while (!pi.isDone()) {
            int type = pi.currentSegment(coords);
            switch (type) {
            case PathIterator.SEG_MOVETO:
                if (started) {
                    // NOTE: multiple subpaths not yet supported; if we do
                    // support later we need to reset area calculations here...
                    return null;
                }
                started = true;
                movX = prevX = curX = coords[0];
                movY = prevY = curY = coords[1];
                segments.add(new MoveTo(curX, curY));
                break;
            case PathIterator.SEG_LINETO:
                if (!hasInfOrNaN(coords, 2)) {
                    started = true;
                    curX = coords[0];
                    curY = coords[1];
                    if (curX != prevX || curY != prevY) {
                        area += curX * prevY - prevX * curY;
                        segments.add(new LineTo(prevX, prevY, curX, curY));
                    }
                    prevX = curX;
                    prevY = curY;
                }
                break;
            case PathIterator.SEG_QUADTO:
                float ctrlx = coords[0];
                float ctrly = coords[1];
                coords[4] = coords[2];
                coords[5] = coords[3];
                coords[0] = (prevX     + 2 * ctrlx) / 3;
                coords[1] = (prevY     + 2 * ctrly) / 3;
                coords[2] = (coords[4] + 2 * ctrlx) / 3;
                coords[3] = (coords[5] + 2 * ctrly) / 3;
                if (!hasInfOrNaN(coords, 6)) {
                    started = true;
                    tmpX = prevX;
                    tmpY = prevY;
                    for (int i = 0; i < 6; i+=2) {
                        curX = coords[i];
                        curY = coords[i+1];
                        area += curX * prevY - prevX * curY;
                        prevX = curX;
                        prevY = curY;
                    }
                    segments.add(new CubicTo(tmpX, tmpY,
                                             coords[0], coords[1],
                                             coords[2], coords[3],
                                             curX, curY));
                }
                break;
            case PathIterator.SEG_CUBICTO:
                /*
                 * The area calculations here are borrowed from the
                 * ShapeEvaluator class.  In a nutshell:
                 * - We measure the area of the polygon formed by the
                 *   endpoints and control points of the curve.
                 * - This technique is based on the formula for calculating
                 *   the area of a polygon.  The standard formula is:
                 *     Area(Poly) = 1/2 * sum(x[i]*y[i+1] - x[i+1]y[i])
                 * - The returned area is negative if the polygon is
                 *   "mostly clockwise" and positive if the polygon is
                 *   "mostly counter-clockwise".
                 * - As we walk through the PathIterator for the overall
                 *   shape, we determine the clockwise-ness of each "local"
                 *   segment, and then we use the accumulated area of the
                 *   entire shape to determine the clockwise-ness of the
                 *   overall shape.  If the two disagree for a particular
                 *   segment, we flip the "convex" flag of that segment.
                 */
                if (!hasInfOrNaN(coords, 6)) {
                    started = true;
                    tmpX = prevX;
                    tmpY = prevY;
                    for (int i = 0; i < 6; i+=2) {
                        curX = coords[i];
                        curY = coords[i+1];
                        area += curX * prevY - prevX * curY;
                        prevX = curX;
                        prevY = curY;
                    }
                    segments.add(new CubicTo(tmpX, tmpY,
                                             coords[0], coords[1],
                                             coords[2], coords[3],
                                             curX, curY));
                }
                break;
            case PathIterator.SEG_CLOSE:
                if (started) {
                    started = false;
                    curX = movX;
                    curY = movY;
                    segments.add(new Close(prevX, prevY, movX, movY));
                }
                break;
            default:
                throw new InternalError("Unknown segment type");
            }
            pi.next();
        }
        if (started) {
            segments.add(new Close(prevX, prevY, movX, movY));
        }

        // NOTE: for now, punt if we detect any overlapping hulls
        for (int i = 0; i < segments.size(); i++) {
            Segment seg = segments.get(i);
            for (int j = i+1; j < segments.size(); j++) {
                Segment other = segments.get(j);
                if (seg.overlaps(other)) {
                    //System.err.println("overlap!");
                    return null;
                }
            }
        }

        //System.err.println("area: " + area);

        Tess.tessProperty(tess, TESS_WINDING_RULE,
                            (pi.getWindingRule() == PathIterator.WIND_NON_ZERO) ?
                            TESS_WINDING_NONZERO : TESS_WINDING_ODD);
        Tess.tessBeginPolygon(tess, null);
        for (int i = 0; i < segments.size(); i++) {
            Segment seg = segments.get(i);
            if (area < 0) {
                // reverse our earlier decision
                seg.convex = !seg.convex;
            }
            numCurveVerts += seg.emitVertices(tess, vbCurve);
        }
        Tess.tessEndPolygon(tess);

        int[] numVerts = new int[2];
        numVerts[0] = listener.getNumVerts();
        numVerts[1] = numCurveVerts;
        return numVerts;
    }

    private static abstract class Segment {
        enum Type { MOVETO, LINETO, QUADTO, CUBICTO, CLOSE }
        final Type type;
        boolean convex;
        protected Segment(Type type) {
            this.type = type;
        }

        abstract int getEdges(float[] edges);
        abstract int emitVertices(Tessellator tess, VertexBuffer vbCurve);

        private static final int[] triOffsets = {
            0, 2, 2, 4, 4, 0,
            6, 8, 8, 10, 10, 6,
        };
        private static final float[] tmpThis = new float[12];
        private static final float[] tmpThat = new float[12];
        boolean overlaps(Segment that) {
            int numThis = this.getEdges(tmpThis);
            int numThat = that.getEdges(tmpThat);

            if (numThis < 1 || numThat < 1) {
                return false;
            }

            if (numThis == 1 && numThat == 1) {
                // simple line-line intersection
                return linesCross(tmpThis[0], tmpThis[1], tmpThis[2], tmpThis[3],
                                  tmpThat[0], tmpThat[1], tmpThat[2], tmpThat[3]);
            }

            // foreach tri in this
            //   foreach line in tri
            //     this line intersects that line?
            //   this tri inside that tri?
            //   that tri inside this tri?
            int maxThis = numThis*2;
            int maxThat = numThat*2;

            // check to see if this hull intersects the other hull
            for (int i = 0; i < maxThis; i+=2) {
                int i0 = triOffsets[i];
                int i1 = triOffsets[i+1];
                for (int j = 0; j < maxThat; j+=2) {
                    int j0 = triOffsets[j];
                    int j1 = triOffsets[j+1];
                    if (linesCross(tmpThis[i0+0], tmpThis[i0+1], tmpThis[i1+0], tmpThis[i1+1],
                                   tmpThat[j0+0], tmpThat[j0+1], tmpThat[j1+0], tmpThat[j1+1]))
                    {
                        //System.err.println("  cross:");
                        //System.err.printf("    %f %f %f %f\n", tmpThis[i0+0], tmpThis[i0+1], tmpThis[i1+0], tmpThis[i1+1]);
                        //System.err.printf("    %f %f %f %f\n", tmpThat[j0+0], tmpThat[j0+1], tmpThat[j1+0], tmpThat[j1+1]);
                        return true;
                    }
                }
            }

            // check to see if this hull is fully contained within
            // the other hull, or vice versa (we only need to test whether
            // a single point from the first hull/line is inside one of the
            // triangles from the second hull)
            // NOTE: maybe we need to test all points, since the first point
            // might be coincident with a triangle edge?
            for (int i = 0; i < numThis*2; i+=6) {
                for (int j = 0; j < numThat*2; j+=6) {
                    if (numThat > 1) {
                        if (triangleContainsPoint(tmpThis[i+0], tmpThis[i+1],
                                                  tmpThat[j+0], tmpThat[j+1],
                                                  tmpThat[j+2], tmpThat[j+3],
                                                  tmpThat[j+4], tmpThat[j+5]))
                        {
                           return true;
                        }
                    }
                    if (numThis > 1) {
                        if (triangleContainsPoint(tmpThat[j+0], tmpThat[j+1],
                                                  tmpThis[i+0], tmpThis[i+1],
                                                  tmpThis[i+2], tmpThis[i+3],
                                                  tmpThis[i+4], tmpThis[i+5]))
                        {
                           return true;
                        }
                    }
                }
            }

            return false;
        }
    }

    private static class MoveTo extends Segment {
        private final float x;
        private final float y;
        MoveTo(float x, float y) {
            super(Type.MOVETO);
            this.x = x;
            this.y = y;
        }
        int getEdges(float[] edges) {
            return 0;
        }
        int emitVertices(Tessellator tess, VertexBuffer vbCurve) {
            Tess.tessBeginContour(tess);
            emitVert(tess, x, y);
            return 0;
        }
    }

    private static class LineTo extends Segment {
        private final float x1;
        private final float y1;
        private final float x2;
        private final float y2;
        LineTo(float x1, float y1, float x2, float y2) {
            super(Type.LINETO);
            this.x1 = x1;
            this.y1 = y1;
            this.x2 = x2;
            this.y2 = y2;
        }
        int getEdges(float[] edges) {
            edges[0] = x1;
            edges[1] = y1;
            edges[2] = x2;
            edges[3] = y2;
            return 1;
        }
        int emitVertices(Tessellator tess, VertexBuffer vbCurve) {
            // NOTE: need to antialias the edges
            emitVert(tess, x2, y2);
            return 0;
        }
    }

//    private static class QuadTo extends Segment {
//        private final float x1;
//        private final float y1;
//        private final float ctrlx;
//        private final float ctrly;
//        private final float x2;
//        private final float y2;
//        QuadTo(float x1, float y1, float ctrlx, float ctrly, float x2, float y2) {
//            super(Type.QUADTO);
//            this.x1 = x1;
//            this.y1 = y1;
//            this.ctrlx = ctrlx;
//            this.ctrly = ctrly;
//            this.x2 = x2;
//            this.y2 = y2;
//            this.convex = Line2D.relativeCCW(x1, y1, ctrlx, ctrly, x2, y2) > 0;
//        }
//        int getEdges(float[] edges) {
//            edges[0] = x1;
//            edges[1] = y1;
//            edges[2] = ctrlx;
//            edges[3] = ctrly;
//            edges[4] = x2;
//            edges[5] = y2;
//            return 3;
//        }
//        int emitVertices(Tessellator tess, VertexBuffer vbCurve) {
//            float inv;
//            if (convex) {
//                // add a single line segment to the solid path
//                emitVert(tess, x2, y2);
//                inv = 1f; // use the regular equation for convex
//            } else {
//                // add two line segments to the solid path
//                emitVert(tess, ctrlx, ctrly);
//                emitVert(tess, x2, y2);
//                inv = -1f; // flip the sign for concave
//            }
//            // add the triangle representing the quad curve
//            vbCurve.addVert(x1,    y1,    0.0f, 0.0f, inv, 0.0f);
//            vbCurve.addVert(ctrlx, ctrly, 0.5f, 0.0f, inv, 0.0f);
//            vbCurve.addVert(x2,    y2,    1.0f, 1.0f, inv, 0.0f);
//            return 3;
//        }
//    }

    /**
     * The calculations for classifying cubic curve segments and deriving
     * texcoords in the emit*() methods below are based on the techniques
     * described in "Rendering Vector Art on the GPU" (GPU Gems 3, Chapter 25).
     */
    private static class CubicTo extends Segment {
        private final float x1;
        private final float y1;
        private final float ctrlx1;
        private final float ctrly1;
        private final float ctrlx2;
        private final float ctrly2;
        private final float x2;
        private final float y2;
        private final int ccw1;
        private final int ccw2;
        private int hullType = -1;

        CubicTo(float x1, float y1,
                float ctrlx1, float ctrly1,
                float ctrlx2, float ctrly2,
                float x2, float y2)
        {
            super(Type.CUBICTO);
            this.x1 = x1;
            this.y1 = y1;
            this.ctrlx1 = ctrlx1;
            this.ctrly1 = ctrly1;
            this.ctrlx2 = ctrlx2;
            this.ctrly2 = ctrly2;
            this.x2 = x2;
            this.y2 = y2;
            this.ccw1 = Line2D.relativeCCW(x1, y1, ctrlx1, ctrly1, x2, y2);
            this.ccw2 = Line2D.relativeCCW(x1, y1, ctrlx2, ctrly2, x2, y2);
            this.convex = ccw1 > 0 && ccw2 > 0;
        }

        private int calculateHullType() {
            if (triangleContainsPoint(x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2)) {
                // endpoint 1 is inside
                return 2;
            } else if (triangleContainsPoint(x2, y2, x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2)) {
                // endpoint 2 is inside
                return 3;
            } else if (triangleContainsPoint(ctrlx1, ctrly1, x1, y1, ctrlx2, ctrly2, x2, y2)) {
                // control point 1 is inside
                return 4;
            } else if (triangleContainsPoint(ctrlx2, ctrly2, x1, y1, ctrlx1, ctrly1, x2, y2)) {
                // control point 2 is inside
                return 5;
            } else {
                if (ccw1 == ccw2) {
                    // convex hull with control points on same side
                    return 0;
                } else {
                    // convex hull with control points on opposite sides
                    return 1;
                }
            }
        }

        int getEdges(float[] edges) {
            // NOTE: this is a silly, braindead approach...
            if (hullType < 0) {
                hullType = calculateHullType();
                //System.err.printf("hullType: %d %f %f %f %f %f %f %f %f\n",
                //                  hullType, x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2);
            }

            switch (hullType) {
            case 0:
                // convex hull with control points on same side
                edges[0] = x1;
                edges[1] = y1;
                edges[2] = ctrlx1;
                edges[3] = ctrly1;
                edges[4] = ctrlx2;
                edges[5] = ctrly2;
                edges[6] = x1;
                edges[7] = y1;
                edges[8] = ctrlx2;
                edges[9] = ctrly2;
                edges[10] = x2;
                edges[11] = y2;
                return 6;
            case 1:
                // convex hull with control points on opposite sides
                edges[0] = x1;
                edges[1] = y1;
                edges[2] = ctrlx1;
                edges[3] = ctrly1;
                edges[4] = ctrlx2;
                edges[5] = ctrly2;
                edges[6] = ctrlx1;
                edges[7] = ctrly1;
                edges[8] = ctrlx2;
                edges[9] = ctrly2;
                edges[10] = x2;
                edges[11] = y2;
                return 6;
            case 2:
                // endpoint 1 is inside
                edges[0] = ctrlx1;
                edges[1] = ctrly1;
                edges[2] = ctrlx2;
                edges[3] = ctrly2;
                edges[4] = x2;
                edges[5] = y2;
                return 3;
            case 3:
                // endpoint 2 is inside
                edges[0] = x1;
                edges[1] = y1;
                edges[2] = ctrlx1;
                edges[3] = ctrly1;
                edges[4] = ctrlx2;
                edges[5] = ctrly2;
                return 3;
            case 4:
                // control point 1 is inside
                edges[0] = x1;
                edges[1] = y1;
                edges[2] = ctrlx2;
                edges[3] = ctrly2;
                edges[4] = x2;
                edges[5] = y2;
                return 3;
            case 5:
                // control point 2 is inside
                edges[0] = x1;
                edges[1] = y1;
                edges[2] = ctrlx1;
                edges[3] = ctrly1;
                edges[4] = x2;
                edges[5] = y2;
                return 3;
            default:
                throw new InternalError("Unknown hull type");
            }
        }

        private static final Vec3f b0 = new Vec3f();
        private static final Vec3f b1 = new Vec3f();
        private static final Vec3f b2 = new Vec3f();
        private static final Vec3f b3 = new Vec3f();
        private static final Vec3f m0 = new Vec3f();
        private static final Vec3f m1 = new Vec3f();
        private static final Vec3f m2 = new Vec3f();
        private static final Vec3f m3 = new Vec3f();
        private static final Vec3f tmp = new Vec3f();
        private static final float ONE_THIRD = 1f / 3f;
        private static final float TWO_THIRDS = 2f / 3f;
        int emitVertices(Tessellator tess, VertexBuffer vbCurve) {
            b0.set(x1,     y1,     1);
            b1.set(ctrlx1, ctrly1, 1);
            b2.set(ctrlx2, ctrly2, 1);
            b3.set(x2,     y2,     1);

            tmp.cross(b3, b2);
            float a1 = b0.dot(tmp);
            tmp.cross(b0, b3);
            float a2 = b1.dot(tmp);
            tmp.cross(b1, b0);
            float a3 = b2.dot(tmp);

            float d3 = (3*a3);
            float d2 = -a2 + d3;
            float d1 = a1 - (2*a2) + d3;
            // normalize to reduce range of the values
            tmp.set(d1, d2, d3);
            tmp.normalize();
            d1 = tmp.x;
            d2 = tmp.y;
            d3 = tmp.z;

            float dd = (3*d2*d2) - (4*d1*d3);
            float discrI = d1*d1*dd;

            //System.err.println(d1 + " " + d2 + " " + d3 + " " + discrI);

            if (isCloseToZero(d1)) d1 = 0f;
            if (isCloseToZero(d2)) d2 = 0f;
            if (isCloseToZero(d3)) d3 = 0f;
            if (isCloseToZero(discrI)) discrI = 0f;

            //System.err.println("convex=" + convex + " " + ccw1 + " " + ccw2);
            if (discrI > 0f) {
                // serpent
                //System.err.println("cubic: SERPENT");
                return emitSerpent(tess, vbCurve, d1, d2, dd);
            } else if (discrI < 0f) {
                // loop
                //System.err.println("cubic: LOOP");
                return emitLoop(tess, vbCurve, d1, d2, dd);
            } else { //if (discrI == 0f) {
                if (d1 == 0f && d2 == 0f) {
                    if (d3 != 0f) {
                        // quadratic
                        //System.err.println("cubic: QUADRATIC");
                        return emitQuadratic(tess, vbCurve);
                    } else {
                        // line or point
                        throw new InternalError("Line/point segment not yet supported");
                    }
                } else {
                    // cusp
                    throw new InternalError("Cusp segment not yet supported");
                }
            }
        }

        private int emitQuadratic(Tessellator tess, VertexBuffer vbCurve) {
            float inv;
            if (convex) {
                // add a single line segment to the solid path
                emitVert(tess, x2, y2);
                inv = 1f; // use the regular equation for convex
            } else {
                // add line segments to the solid path
                emitVert(tess, ctrlx1, ctrly1);
                emitVert(tess, ctrlx2, ctrly2);
                emitVert(tess, x2, y2);
                inv = -1f; // flip the sign for concave
            }

            float ot = ONE_THIRD;
            float tt = TWO_THIRDS;
            // add the triangles representing the cubic curve
            vbCurve.addVert(x1,     y1,     0f, 0f, 0f, inv);
            vbCurve.addVert(ctrlx1, ctrly1, ot, 0f, ot, inv);
            vbCurve.addVert(x2,     y2,     1f, 1f, 1f, inv);
            vbCurve.addVert(ctrlx1, ctrly1, ot, 0f, ot, inv);
            vbCurve.addVert(ctrlx2, ctrly2, tt, ot, tt, inv);
            vbCurve.addVert(x2,     y2,     1f, 1f, 1f, inv);
            return 6;
        }

        private int emitSerpent(Tessellator tess, VertexBuffer vbCurve,
                                float d1, float d2, float dd)
        {
            float ltmp, mtmp;
            float stmp = (float)Math.sqrt(3*dd);
            float ls = (3*d2) - stmp;
            float lt = 6*d1;
            float ms = (3*d2) + stmp;
            float mt = lt;

            m0.x = ls*ms;
            m0.y = ls*ls*ls;
            m0.z = ms*ms*ms;

            m1.x = ONE_THIRD * ((3*ls*ms) - (ls*mt) - (lt*ms));
            m1.y = (ls*ls) * (ls-lt);
            m1.z = (ms*ms) * (ms-mt);

            ltmp = lt-ls;
            mtmp = mt-ms;
            m2.x = ONE_THIRD * ((lt * (mt - (2*ms))) + (ls * (3*ms - 2*mt)));
            m2.y = ls * ltmp * ltmp;
            m2.z = ms * mtmp * mtmp;

            m3.x = ltmp * mtmp;
            m3.y = -(ltmp*ltmp*ltmp);
            m3.z = -(mtmp*mtmp*mtmp);

            float inv;
            if (ccw1 != ccw2) {
                // control points straddle the line connecting the endpoints
                if (ccw1 > 0) {
                    emitVert(tess, ctrlx1, ctrly1);
                } else {
                    emitVert(tess, ctrlx2, ctrly2);
                }
                emitVert(tess, x2, y2);

                inv = -1; // TODO

                vbCurve.addVert(x1,     y1,     m0.x, m0.y, m0.z, inv);
                vbCurve.addVert(ctrlx1, ctrly1, m1.x, m1.y, m1.z, inv);
                vbCurve.addVert(ctrlx2, ctrly2, m2.x, m2.y, m2.z, inv);
                vbCurve.addVert(ctrlx1, ctrly1, m1.x, m1.y, m1.z, inv);
                vbCurve.addVert(ctrlx2, ctrly2, m2.x, m2.y, m2.z, inv);
                vbCurve.addVert(x2,     y2,     m3.x, m3.y, m3.z, inv);
                return 6;
            } else {
                // control points on same side of line connecting the endpoints
                if (hullType == 4) {
                    // control point 1 is on inside; subdivide into 3 triangles
                    if (convex) {
                        emitVert(tess, x2, y2);
                        inv = -1f;
                    } else {
                        emitVert(tess, ctrlx2, ctrly2);
                        emitVert(tess, x2, y2);
                        inv = 1f;
                    }
                    vbCurve.addVert(x1,     y1,     m0.x, m0.y, m0.z, inv);
                    vbCurve.addVert(ctrlx1, ctrly1, m1.x, m1.y, m1.z, inv);
                    vbCurve.addVert(ctrlx2, ctrly2, m2.x, m2.y, m2.z, inv);
                    vbCurve.addVert(x1,     y1,     m0.x, m0.y, m0.z, inv);
                    vbCurve.addVert(ctrlx1, ctrly1, m1.x, m1.y, m1.z, inv);
                    vbCurve.addVert(x2,     y2,     m3.x, m3.y, m3.z, inv);
                    vbCurve.addVert(ctrlx1, ctrly1, m1.x, m1.y, m1.z, inv);
                    vbCurve.addVert(ctrlx2, ctrly2, m2.x, m2.y, m2.z, inv);
                    vbCurve.addVert(x2,     y2,     m3.x, m3.y, m3.z, inv);
                    return 9;
                } else if (hullType == 5) {
                    // control point 2 is on inside; subdivide into 3 triangles
                    if (convex) {
                        emitVert(tess, x2, y2);
                        inv = -1f;
                    } else {
                        emitVert(tess, ctrlx1, ctrly1);
                        emitVert(tess, x2, y2);
                        inv = 1f;
                    }
                    vbCurve.addVert(x1,     y1,     m0.x, m0.y, m0.z, inv);
                    vbCurve.addVert(ctrlx1, ctrly1, m1.x, m1.y, m1.z, inv);
                    vbCurve.addVert(ctrlx2, ctrly2, m2.x, m2.y, m2.z, inv);
                    vbCurve.addVert(x1,     y1,     m0.x, m0.y, m0.z, inv);
                    vbCurve.addVert(ctrlx2, ctrly2, m2.x, m2.y, m2.z, inv);
                    vbCurve.addVert(x2,     y2,     m3.x, m3.y, m3.z, inv);
                    vbCurve.addVert(ctrlx1, ctrly1, m1.x, m1.y, m1.z, inv);
                    vbCurve.addVert(ctrlx2, ctrly2, m2.x, m2.y, m2.z, inv);
                    vbCurve.addVert(x2,     y2,     m3.x, m3.y, m3.z, inv);
                    return 9;
                } else {
                    // convex hull; subdivide into 2 triangles
                    if (convex) {
                        emitVert(tess, x2, y2);
                        inv = -1f;
                    } else {
                        emitVert(tess, ctrlx1, ctrly1);
                        emitVert(tess, ctrlx2, ctrly2);
                        emitVert(tess, x2, y2);
                        inv = 1f;
                    }
                    vbCurve.addVert(x1,     y1,     m0.x, m0.y, m0.z, inv);
                    vbCurve.addVert(ctrlx1, ctrly1, m1.x, m1.y, m1.z, inv);
                    vbCurve.addVert(ctrlx2, ctrly2, m2.x, m2.y, m2.z, inv);
                    vbCurve.addVert(x1,     y1,     m0.x, m0.y, m0.z, inv);
                    vbCurve.addVert(ctrlx2, ctrly2, m2.x, m2.y, m2.z, inv);
                    vbCurve.addVert(x2,     y2,     m3.x, m3.y, m3.z, inv);
                    return 6;
                }
            }
        }

        private int emitLoop(Tessellator tess, VertexBuffer vbCurve,
                             float d1, float d2, float dd)
        {
            float ltmp, mtmp;

            float stmp = (float)Math.sqrt(-dd);
            float ls = d2 - stmp;
            float lt = 2*d1;
            float ms = d2 + stmp;
            float mt = lt;

            m0.x = ls*ms;
            m0.y = ls*ls*ms;
            m0.z = ls*ms*ms;

            // Note: there was a typo in m1.x in GPU Gems 3
            m1.x =  ONE_THIRD * (-(ls*mt) - (lt*ms) + (3*ls*ms));
            m1.y = -ONE_THIRD * ls * ((ls*(mt-(3*ms))) + (2*lt*ms));
            m1.z = -ONE_THIRD * ms * ((ls*((2*mt)-(3*ms))) + (lt*ms));

            ltmp = lt-ls;
            mtmp = mt-ms;
            m2.x = ONE_THIRD * (lt*(mt-(2*ms)) + (ls*((3*ms)-(2*mt))));
            m2.y = ONE_THIRD * ltmp * ((ls*((2*mt)-(3*ms))) + (lt*ms));
            m2.z = ONE_THIRD * mtmp * ((ls*(mt-(3*ms))) + (2*lt*ms));

            m3.x = ltmp * mtmp;
            m3.y = -(ltmp*ltmp*mtmp);
            m3.z = -(ltmp*mtmp*mtmp);

            float inv;
            if (convex) {
                emitVert(tess, x2, y2);
                inv = -1f;
            } else {
                emitVert(tess, ctrlx1, ctrly1);
                emitVert(tess, ctrlx2, ctrly2);
                emitVert(tess, x2, y2);
                inv = 1f;
            }

            vbCurve.addVert(x1,     y1,     m0.x, m0.y, m0.z, inv);
            vbCurve.addVert(ctrlx1, ctrly1, m1.x, m1.y, m1.z, inv);
            vbCurve.addVert(ctrlx2, ctrly2, m2.x, m2.y, m2.z, inv);
            vbCurve.addVert(x1,     y1,     m0.x, m0.y, m0.z, inv);
            vbCurve.addVert(ctrlx2, ctrly2, m2.x, m2.y, m2.z, inv);
            vbCurve.addVert(x2,     y2,     m3.x, m3.y, m3.z, inv);
            return 6;
        }
    }

    private static class Close extends Segment {
        private final float x1;
        private final float y1;
        private final float x2;
        private final float y2;
        Close(float x1, float y1, float x2, float y2) {
            super(Type.CLOSE);
            this.x1 = x1;
            this.y1 = y1;
            this.x2 = x2;
            this.y2 = y2;
        }
        int getEdges(float[] edges) {
            edges[0] = x1;
            edges[1] = y1;
            edges[2] = x2;
            edges[3] = y2;
            return 1;
        }
        int emitVertices(Tessellator tess, VertexBuffer vbCurve) {
            // it appears that the following will close the path automatically,
            // so no need to issue another vertex here
            Tess.tessEndContour(tess);
            return 0;
        }
    }

    /**
     * Returns true if the given point is on (or very close to) the specified
     * line segment.
     * 

* Based on the implementation from Line2D.ptLineDistSq(), except using * floats instead of doubles. */ static boolean pointOnLine(float px, float py, float x1, float y1, float x2, float y2) { // Adjust vectors relative to x1,y1 // x2,y2 becomes relative vector from x1,y1 to end of segment x2 -= x1; y2 -= y1; // px,py becomes relative vector from x1,y1 to test point px -= x1; py -= y1; float dotprod = px * x2 + py * y2; // dotprod is the length of the px,py vector // projected on the x1,y1=>x2,y2 vector times the // length of the x1,y1=>x2,y2 vector float projlenSq = dotprod * dotprod / (x2 * x2 + y2 * y2); // Distance to line is now the length of the relative point // vector minus the length of its projection onto the line float lenSq = px * px + py * py - projlenSq; return lenSq < EPSILON; } /** * Returns true if the line segments intersect and are not coincident. *

* Based on "Faster Line Segment Intersection" by Franklin Antonio * (from Graphics Gems III). */ static boolean linesCross(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { if (pointOnLine(x1, y1, x3, y3, x4, y4) || pointOnLine(x2, y2, x3, y3, x4, y4) || pointOnLine(x3, y3, x1, y1, x2, y2) || pointOnLine(x4, y4, x1, y1, x2, y2)) { // the line segments share an endpoint, or the endpoint of one // line touches the other line segment without crossing it return false; } float ax = x2-x1; float ay = y2-y1; float bx = x3-x4; float by = y3-y4; float denom = (ay*bx) - (ax*by); if (isCloseToZero(denom)) { // the line segments are coincident return false; } float cx = x1-x3; float cy = y1-y3; float p = (by*cx) - (bx*cy); if (denom > 0) { if (p < 0 || p > denom) { return false; } } else { if (p > 0 || p < denom) { return false; } } float q = (ax*cy) - (ay*cx); if (denom > 0) { if (q < 0 || q > denom) { return false; } } else { if (q > 0 || q < denom) { return false; } } return true; } /** * Returns true if the given point (px,py) is inside the triangle * extending from (x1,y1) to (x2,y2) to (x3,y3), or returns false if * the point is outside or coincident with the triangle. *

* Based on algorithm described at: * http://mathworld.wolfram.com/TriangleInterior.html */ static boolean triangleContainsPoint(float px, float py, float x1, float y1, float x2, float y2, float x3, float y3) { px -= x1; py -= y1; x2 -= x1; y2 -= y1; x3 -= x1; y3 -= y1; float denom = (x2*y3) - (x3*y2); if (isCloseToZero(denom)) { // TODO: triangle is actually a line? return false; } float a = ((px*y3) - (py*x3)) / denom; float b = ((px*y2) - (py*x2)) / -denom; return (a > 0) && (b > 0) && (a+b < 1); } static boolean isCloseToZero(float x) { return x < EPSILON && x > -EPSILON; } /** * Returns true if any of {@code num} elements in the given array * are Inf or NaN. Ideally PathIterators would never produce segments * with these values, but they are sometimes seen in practice and can * confuse the Tessellator, so it is a good idea to protect against * them here. */ static boolean hasInfOrNaN(float[] coords, int num) { for (int i = 0; i < num; i++) { if (Float.isInfinite(coords[i]) || Float.isNaN(coords[i])) { return true; } } return false; } static void emitVert(Tessellator tess, float x, float y) { double[] vert = new double[] { x, y, 0 }; // TODO: reduce garbage Tess.tessVertex(tess, vert, 0, vert); } } class AATessCallbacks extends TessellatorCallbackAdapter { private int nPrims = 0; private int nVerts = 0; private VertexBuffer vb; public void setVertexBuffer(VertexBuffer vb) { this.vb = vb; nPrims = 0; nVerts = 0; } public int getNumVerts() { return nVerts; } @Override public void begin(int primType) { if (primType != GL_TRIANGLES) { throw new InternalError("Only GL_TRIANGLES is supported"); } nVerts = 0; } @Override public void end() { nPrims++; } @Override public void vertex(Object vertexData) { double[] coords = (double[]) vertexData; vb.addVert((float)coords[0], (float)coords[1]); nVerts++; } @Override public void edgeFlag(boolean edge) { } @Override public void combine(double[] coords, Object[] data, float[] weight, Object[] outData) { double[] vertex = new double[3]; for (int i = 0; i < 3; i++) { vertex[i] = coords[i]; } outData[0] = vertex; } @Override public void error(int errorNumber) { System.err.println("Tesselation error " + errorNumber); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy