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

com.github.mathiewz.slick.geom.MannTriangulator Maven / Gradle / Ivy

Go to download

The main purpose of this libraryis to modernize and maintain the slick2D library.

The newest version!
/*
 * Triangulator0.java
 * (BSD license)
 * Copyright (c) 2005, Matthias Mann (www.matthiasmann.de)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * * Neither the name of the Matthias Mann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * Created on 17. March 2005, 22:19
 */
package com.github.mathiewz.slick.geom;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

/**
 * A 2D Triangulator. Graciously made available by the man(n) himself.
 *
 * @author Matthias Mann
 */
public class MannTriangulator implements Triangulator {
    /** The allowed error value */
    private static final double EPSILON = 1e-5;

    /** The outer countour of the shape */
    protected PointBag contour;
    /** The holes defined in the polygon */
    protected PointBag holes;
    /** The next available point bag */
    private PointBag nextFreePointBag;
    /** The next available point */
    private Point nextFreePoint;
    /** The list of triangles created (or rather points in triangles, 3xn) */
    private final List triangles = new ArrayList<>();

    /** Creates a new instance of Triangulator0 */
    public MannTriangulator() {
        contour = getPointBag();
    }

    /**
     * @see com.github.mathiewz.slick.geom.Triangulator#addPolyPoint(float, float)
     */
    @Override
    public void addPolyPoint(float x, float y) {
        addPoint(new Vector2f(x, y));
    }

    /**
     * Reset the internal state of the triangulator
     */
    public void reset() {
        while (holes != null) {
            holes = freePointBag(holes);
        }

        contour.clear();
        holes = null;
    }

    /**
     * Begin adding a hole to the polygon
     */
    @Override
    public void startHole() {
        PointBag newHole = getPointBag();
        newHole.next = holes;
        holes = newHole;
    }

    /**
     * Add a defined point to the current contour
     *
     * @param pt
     *            The point to add
     */
    private void addPoint(Vector2f pt) {
        if (holes == null) {
            Point p = getPoint(pt);
            contour.add(p);
        } else {
            Point p = getPoint(pt);
            holes.add(p);
        }
    }

    /**
     * Triangulate the points given
     *
     * @param result
     *            The array to fill with the result or use to determine type
     * @return The resultng triangles
     */
    private Vector2f[] triangulate(Vector2f[] result) {
        // Step 1: Compute all angles
        contour.computeAngles();
        for (PointBag hole = holes; hole != null; hole = hole.next) {
            hole.computeAngles();
        }
        
        // Step 2: Connect the holes with the contour (build bridges)
        while (holes != null) {
            Point pHole = holes.first;
            outer: do {
                if (pHole.angle <= 0) {
                    Point pContour = contour.first;
                    inner: do {
                        if (pHole.isInfront(pContour) && pContour.isInfront(pHole) && !contour.doesIntersectSegment(pHole.pt, pContour.pt)) {
                            PointBag hole = holes;
                            do {
                                if (hole.doesIntersectSegment(pHole.pt, pContour.pt)) {
                                    break inner;
                                }
                            } while ((hole = hole.next) != null);
                            
                            Point newPtContour = getPoint(pContour.pt);
                            pContour.insertAfter(newPtContour);
                            
                            Point newPtHole = getPoint(pHole.pt);
                            pHole.insertBefore(newPtHole);
                            
                            pContour.next = pHole;
                            pHole.prev = pContour;
                            
                            newPtHole.next = newPtContour;
                            newPtContour.prev = newPtHole;
                            
                            pContour.computeAngle();
                            pHole.computeAngle();
                            newPtContour.computeAngle();
                            newPtHole.computeAngle();
                            
                            // detach the points from the hole
                            holes.first = null;
                            break outer;
                        }
                    } while ((pContour = pContour.next) != contour.first);
                }
            } while ((pHole = pHole.next) != holes.first);
            
            // free the hole
            holes = freePointBag(holes);
        }
        
        // Step 3: Make sure we have enough space for the result
        int numTriangles = contour.countPoints() - 2;
        int neededSpace = numTriangles * 3 + 1; // for the null
        if (result.length < neededSpace) {
            result = (Vector2f[]) Array.newInstance(result.getClass().getComponentType(), neededSpace);
        }
        
        // Step 4: Extract the triangles
        int idx = 0;
        for (;;) {
            Point pContour = contour.first;
            
            if (pContour == null) {
                break;
            }
            // Are there 2 or less points left ?
            if (pContour.next == pContour.prev) {
                break;
            }
            
            do {
                if (pContour.angle > 0) {
                    Point prev = pContour.prev;
                    Point next = pContour.next;
                    
                    if ((next.next == prev || prev.isInfront(next) && next.isInfront(prev)) && !contour.doesIntersectSegment(prev.pt, next.pt)) {
                        result[idx++] = pContour.pt;
                        result[idx++] = next.pt;
                        result[idx++] = prev.pt;
                        break;
                    }
                }
            } while ((pContour = pContour.next) != contour.first);
            
            // remove the point - we do it in every case to prevent endless loop
            Point prev = pContour.prev;
            Point next = pContour.next;
            
            contour.first = prev;
            pContour.unlink();
            freePoint(pContour);
            
            next.computeAngle();
            prev.computeAngle();
        }
        
        // Step 5: Append a null (see Collection.toArray)
        result[idx] = null;
        
        // Step 6: Free memory
        contour.clear();
        
        // Finished !
        return result;
        
    }

    /**
     * Create a new point bag (or recycle an old one)
     *
     * @return The new point bag
     */
    private PointBag getPointBag() {
        PointBag pb = nextFreePointBag;
        if (pb != null) {
            nextFreePointBag = pb.next;
            pb.next = null;
            return pb;
        }
        return new PointBag();
    }

    /**
     * Release a pooled bag
     *
     * @param pb
     *            The bag to release
     * @return The next available bag
     */
    private PointBag freePointBag(PointBag pb) {
        PointBag next = pb.next;
        pb.clear();
        pb.next = nextFreePointBag;
        nextFreePointBag = pb;
        return next;
    }

    /**
     * Create or reuse a point
     *
     * @param pt
     *            The point data to set
     * @return The new point
     */
    private Point getPoint(Vector2f pt) {
        Point p = nextFreePoint;
        if (p != null) {
            nextFreePoint = p.next;
            // initialize new point to safe values
            p.next = null;
            p.prev = null;
            p.pt = pt;
            return p;
        }
        return new Point(pt);
    }

    /**
     * Release a point into the pool
     *
     * @param p
     *            The point to release
     */
    private void freePoint(Point p) {
        p.next = nextFreePoint;
        nextFreePoint = p;
    }

    /**
     * A single point being considered during triangulation
     *
     * @author Matthias Mann
     */
    private static class Point implements Serializable {
        /** The location of the point */
        protected Vector2f pt;
        /** The previous point in the contour */
        protected Point prev;
        /** The next point in the contour */
        protected Point next;
        /** The x component of the of the normal */
        protected double nx;
        /** The y component of the of the normal */
        protected double ny;
        /** The angle at this point in the path */
        protected double angle;

        /**
         * Create a new point
         *
         * @param pt
         *            The points location
         */
        public Point(Vector2f pt) {
            this.pt = pt;
        }

        /**
         * Remove this point from it's contour
         */
        public void unlink() {
            prev.next = next;
            next.prev = prev;
            next = null;
            prev = null;
        }

        /**
         * Insert a point before this one (see LinkedList)
         *
         * @param p
         *            The point to insert
         */
        public void insertBefore(Point p) {
            prev.next = p;
            p.prev = prev;
            p.next = this;
            prev = p;
        }

        /**
         * Insert a point after this one (see LinkedList)
         *
         * @param p
         *            The point to insert
         */
        public void insertAfter(Point p) {
            next.prev = p;
            p.prev = this;
            p.next = next;
            next = p;
        }

        /**
         * Java 5 hypot method
         *
         * @param x
         *            The x component
         * @param y
         *            The y component
         * @return The hypotenuse
         */
        private double hypot(double x, double y) {
            return Math.sqrt(x * x + y * y);
        }

        /**
         * Compute the angle at this point
         */
        public void computeAngle() {
            if (prev.pt.equals(pt)) {
                pt.setX(pt.getX() + 0.01f);
            }
            double dx1 = pt.getX() - prev.pt.getX();
            double dy1 = pt.getY() - prev.pt.getY();
            double len1 = hypot(dx1, dy1);
            dx1 /= len1;
            dy1 /= len1;

            if (next.pt.equals(pt)) {
                pt.setY(pt.getY() + 0.01f);
            }
            double dx2 = next.pt.getX() - pt.getX();
            double dy2 = next.pt.getY() - pt.getY();
            double len2 = hypot(dx2, dy2);
            dx2 /= len2;
            dy2 /= len2;

            double nx1 = -dy1;
            double ny1 = dx1;

            nx = (nx1 - dy2) * 0.5;
            ny = (ny1 + dx2) * 0.5;

            if (nx * nx + ny * ny < EPSILON) {
                nx = dx1;
                ny = dy2;
                angle = 1; // TODO: nx1,ny1 and nx2,ny2 facing ?
                if (dx1 * dx2 + dy1 * dy2 > 0) {
                    nx = -dx1;
                    ny = -dy1;
                }
            } else {
                angle = nx * dx2 + ny * dy2;
            }
        }

        /**
         * Check if this point is infront of another
         *
         * @param dx
         *            The other x
         * @param dy
         *            The other y
         * @return True if this point is infront (in the contour)
         */
        public boolean isInfront(double dx, double dy) {
            // no nead to normalize, amplitude does not metter for side
            // detection
            boolean sidePrev = (prev.pt.getY() - pt.getY()) * dx + (pt.getX() - prev.pt.getX()) * dy >= 0;
            boolean sideNext = (pt.getY() - next.pt.getY()) * dx + (next.pt.getX() - pt.getX()) * dy >= 0;

            return angle < 0 ? sidePrev || sideNext : sidePrev && sideNext;
        }

        /**
         * Check if this point is infront of another
         *
         * @param p
         *            The other point
         * @return True if this point is infront (in the contour)
         */
        public boolean isInfront(Point p) {
            return isInfront(p.pt.getX() - pt.getX(), p.pt.getY() - pt.getY());
        }
    }

    /**
     * A bag/pool of point objects
     *
     * @author kevin
     */
    protected class PointBag implements Serializable {
        /** The first point in the bag - head of the list */
        protected Point first;
        /** The next bag in the list of bags */
        protected PointBag next;

        /**
         * Clear all the points from this bag
         */
        public void clear() {
            if (first != null) {
                freePoints(first);
                first = null;
            }
        }

        /**
         * Release all points
         *
         * @param head
         *            The head of the points bag
         */
        private void freePoints(Point head) {
            head.prev.next = nextFreePoint;
            head.prev = null;
            nextFreePoint = head;
        }

        /**
         * Add a point to the bag
         *
         * @param p
         *            The point to add
         */
        public void add(Point p) {
            if (first != null) {
                first.insertBefore(p);
            } else {
                first = p;
                p.next = p;
                p.prev = p;
            }
        }

        /**
         * Compute the angles for the points in this bag
         */
        public void computeAngles() {
            if (first == null) {
                return;
            }

            Point p = first;
            do {
                p.computeAngle();
            } while ((p = p.next) != first);
        }

        /**
         * Check if the points in this bag form a path intersecting
         * with the specified path
         *
         * @param v1
         *            The start point of the segment
         * @param v2
         *            The end point of the segment
         * @return True if points in this contour intersect with the segment
         */
        public boolean doesIntersectSegment(Vector2f v1, Vector2f v2) {
            double dxA = v2.getX() - v1.getX();
            double dyA = v2.getY() - v1.getY();

            for (Point p = first;;) {
                Point n = p.next;
                if (p.pt != v1 && n.pt != v1 && p.pt != v2 && n.pt != v2) {
                    double dxB = n.pt.getX() - p.pt.getX();
                    double dyB = n.pt.getY() - p.pt.getY();
                    double d = dxA * dyB - dyA * dxB;

                    if (Math.abs(d) > EPSILON) {
                        double tmp1 = p.pt.getX() - v1.getX();
                        double tmp2 = p.pt.getY() - v1.getY();
                        double tA = (dyB * tmp1 - dxB * tmp2) / d;
                        double tB = (dyA * tmp1 - dxA * tmp2) / d;

                        if (tA >= 0 && tA <= 1 && tB >= 0 && tB <= 1) {
                            return true;
                        }
                    }
                }

                if (n == first) {
                    return false;
                }
                p = n;
            }
        }

        /**
         * Get the number of points in the bag
         *
         * @return The number of points in the bag
         */
        public int countPoints() {
            if (first == null) {
                return 0;
            }

            int count = 0;
            Point p = first;
            do {
                ++count;
            } while ((p = p.next) != first);
            return count;
        }

        /**
         * Check if the point provided was contained
         *
         * @param point
         *            The point provided
         * @return True if it's in the bag
         */
        public boolean contains(Vector2f point) {
            return first != null && (first.prev.pt.equals(point) || first.pt.equals(point));
        }
    }

    @Override
    public boolean triangulate() {
        Vector2f[] temp = triangulate(new Vector2f[0]);

        for (Vector2f element : temp) {
            if (element == null) {
                break;
            } else {
                triangles.add(element);
            }
        }

        return true;
    }

    /**
     * @see com.github.mathiewz.slick.geom.Triangulator#getTriangleCount()
     */
    @Override
    public int getTriangleCount() {
        return triangles.size() / 3;
    }

    /**
     * @see com.github.mathiewz.slick.geom.Triangulator#getTrianglePoint(int, int)
     */
    @Override
    public float[] getTrianglePoint(int tri, int i) {
        Vector2f pt = triangles.get(tri * 3 + i);

        return new float[] { pt.getX(), pt.getY() };
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy