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

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

Go to download

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

The newest version!
package com.github.mathiewz.slick.geom;

import java.util.ArrayList;

/**
 * A set of utilities to play with geometry
 *
 * @author kevin
 */
public class GeomUtil {
    /** The tolerance for determining changes and steps */
    public static final float EPSILON = 0.0001f;
    /** The tolerance for determining direction change */
    public static final float EDGE_SCALE = 1f;
    /** The maximum number of points returned by an operation - prevents full lockups */
    public static final int MAX_POINTS = 10000;
    /** The listener to notify of operations */
    private GeomUtilListener listener;
    
    /**
     * Subtract one shape from another - note this is experimental and doesn't
     * currently handle islands
     *
     * @param targetArg
     *            The target to be subtracted from
     * @param missingArg
     *            The shape to subtract
     * @return The newly created shapes
     */
    public Shape[] subtract(Shape targetArg, Shape missingArg) {
        Shape target = targetArg.transform(new Transform());
        Shape missing = missingArg.transform(new Transform());
        
        int count = 0;
        for (int i = 0; i < target.getPointCount(); i++) {
            if (missing.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
                count++;
            }
        }
        
        if (count == target.getPointCount()) {
            return new Shape[0];
        }
        
        if (!target.intersects(missing)) {
            return new Shape[] { target };
        }
        
        int found = 0;
        for (int i = 0; i < missing.getPointCount(); i++) {
            if (target.contains(missing.getPoint(i)[0], missing.getPoint(i)[1])) {
                if (!onPath(target, missing.getPoint(i)[0], missing.getPoint(i)[1])) {
                    found++;
                }
            }
        }
        for (int i = 0; i < target.getPointCount(); i++) {
            if (missing.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
                if (!onPath(missing, target.getPoint(i)[0], target.getPoint(i)[1])) {
                    found++;
                }
            }
        }
        
        if (found < 1) {
            return new Shape[] { target };
        }
        
        return combine(target, missing, true);
    }
    
    /**
     * Check if the given point is on the path
     *
     * @param path
     *            The path to check
     * @param x
     *            The x coordinate of the point to check
     * @param y
     *            The y coordiante of teh point to check
     * @return True if the point is on the path
     */
    private boolean onPath(Shape path, float x, float y) {
        for (int i = 0; i < path.getPointCount() + 1; i++) {
            int n = rationalPoint(path, i + 1);
            Line line = getLine(path, rationalPoint(path, i), n);
            if (line.distance(new Vector2f(x, y)) < EPSILON * 100) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Set the listener to be notified of geometry based operations
     *
     * @param listener
     *            The listener to be notified of geometry based operations
     */
    public void setListener(GeomUtilListener listener) {
        this.listener = listener;
    }
    
    /**
     * Join to shapes together. Note that the shapes must be touching
     * for this method to work.
     *
     * @param targetArg
     *            The target shape to union with
     * @param otherArg
     *            The additional shape to union
     * @return The newly created shapes
     */
    public Shape[] union(Shape targetArg, Shape otherArg) {
        Shape target = targetArg.transform(new Transform());
        Shape other = otherArg.transform(new Transform());
        
        if (!target.intersects(other)) {
            return new Shape[] { target, other };
        }
        
        // handle the case where intersects is true but really we're talking
        // about edge points
        boolean touches = false;
        int buttCount = 0;
        for (int i = 0; i < target.getPointCount(); i++) {
            if (other.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
                if (!other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
                    touches = true;
                    break;
                }
            }
            if (other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
                buttCount++;
            }
        }
        for (int i = 0; i < other.getPointCount(); i++) {
            if (target.contains(other.getPoint(i)[0], other.getPoint(i)[1])) {
                if (!target.hasVertex(other.getPoint(i)[0], other.getPoint(i)[1])) {
                    touches = true;
                    break;
                }
            }
        }
        
        if (!touches && buttCount < 2) {
            return new Shape[] { target, other };
        }
        
        // so they are definitely touching, consider the union
        return combine(target, other, false);
    }
    
    /**
     * Perform the combination
     *
     * @param target
     *            The target shape we're updating
     * @param other
     *            The other shape in the operation
     * @param subtract
     *            True if it's a subtract operation, otherwise it's union
     * @return The set of shapes produced
     */
    private Shape[] combine(Shape target, Shape other, boolean subtract) {
        if (subtract) {
            ArrayList shapes = new ArrayList<>();
            ArrayList used = new ArrayList<>();
            
            // remove any points that are contianed in the shape we're removing, these
            // are implicitly used
            for (int i = 0; i < target.getPointCount(); i++) {
                float[] point = target.getPoint(i);
                if (other.contains(point[0], point[1])) {
                    used.add(new Vector2f(point[0], point[1]));
                    if (listener != null) {
                        listener.pointExcluded(point[0], point[1]);
                    }
                }
            }
            
            for (int i = 0; i < target.getPointCount(); i++) {
                float[] point = target.getPoint(i);
                Vector2f pt = new Vector2f(point[0], point[1]);
                
                if (!used.contains(pt)) {
                    Shape result = combineSingle(target, other, true, i);
                    shapes.add(result);
                    for (int j = 0; j < result.getPointCount(); j++) {
                        float[] kpoint = result.getPoint(j);
                        Vector2f kpt = new Vector2f(kpoint[0], kpoint[1]);
                        used.add(kpt);
                    }
                }
            }
            
            return shapes.toArray(new Shape[0]);
        } else {
            for (int i = 0; i < target.getPointCount(); i++) {
                if (!other.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
                    if (!other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
                        Shape shape = combineSingle(target, other, false, i);
                        return new Shape[] { shape };
                    }
                }
            }
            
            return new Shape[] { other };
        }
    }
    
    /**
     * Combine two shapes
     *
     * @param target
     *            The target shape
     * @param missing
     *            The second shape to apply
     * @param subtract
     *            True if we should subtract missing from target, otherwise union
     * @param start
     *            The point to start at
     * @return The newly created shape
     */
    private Shape combineSingle(Shape target, Shape missing, boolean subtract, int start) {
        Shape current = target;
        Shape other = missing;
        int point = start;
        int dir = 1;
        
        Polygon poly = new Polygon();
        boolean first = true;
        
        int loop = 0;
        
        // while we've not reached the same point
        float px = current.getPoint(point)[0];
        float py = current.getPoint(point)[1];
        
        while (!poly.hasVertex(px, py) || first || current != target) {
            first = false;
            loop++;
            if (loop > MAX_POINTS) {
                break;
            }
            
            // add the current point to the result shape
            poly.addPoint(px, py);
            if (listener != null) {
                listener.pointUsed(px, py);
            }
            
            // if the line between the current point and the next one intersect the
            // other shape work out where on the other shape and start traversing it's
            // path instead
            Line line = getLine(current, px, py, rationalPoint(current, point + dir));
            HitResult hit = intersect(other, line);
            
            if (hit != null) {
                Line hitLine = hit.line;
                Vector2f pt = hit.pt;
                px = pt.getX();
                py = pt.getY();
                
                if (listener != null) {
                    listener.pointIntersected(px, py);
                }
                
                if (other.hasVertex(px, py)) {
                    point = other.indexOf(pt.getX(), pt.getY());
                    dir = 1;
                    px = pt.getX();
                    py = pt.getY();
                    
                    Shape temp = current;
                    current = other;
                    other = temp;
                    continue;
                }
                
                float dx = hitLine.getDX() / hitLine.length();
                float dy = hitLine.getDY() / hitLine.length();
                dx *= EDGE_SCALE;
                dy *= EDGE_SCALE;
                
                if (current.contains(pt.getX() + dx, pt.getY() + dy)) {
                    // the point is the next one, we need to take the first and traverse
                    // the path backwards
                    if (subtract && current == missing || !subtract && current == target) {
                        point = hit.p2;
                        dir = -1;
                    } else {
                        point = hit.p1;
                        dir = 1;
                    }
                    
                    // swap the shapes over, we'll traverse the other one
                    Shape temp = current;
                    current = other;
                    other = temp;
                } else if (current.contains(pt.getX() - dx, pt.getY() - dy)) {
                    if (subtract) {
                        if (current == target) {
                            point = hit.p2;
                            dir = -1;
                        } else {
                            point = hit.p1;
                            dir = 1;
                        }
                    } else {
                        point = hit.p1;
                        dir = 1;
                    }
                    
                    // swap the shapes over, we'll traverse the other one
                    Shape temp = current;
                    current = other;
                    other = temp;
                } else {
                    // give up
                    if (subtract) {
                        break;
                    } else {
                        point = hit.p1;
                        dir = 1;
                        Shape temp = current;
                        current = other;
                        other = temp;
                        
                        point = rationalPoint(current, point + dir);
                        px = current.getPoint(point)[0];
                        py = current.getPoint(point)[1];
                    }
                }
            } else {
                // otherwise just move to the next point in the current shape
                point = rationalPoint(current, point + dir);
                px = current.getPoint(point)[0];
                py = current.getPoint(point)[1];
            }
        }
        
        poly.addPoint(px, py);
        if (listener != null)
        
        {
            listener.pointUsed(px, py);
        }
        
        return poly;
    }
    
    /**
     * Intersect a line with a shape
     *
     * @param shape
     *            The shape to compare
     * @param line
     *            The line to intersect against the shape
     * @return The result describing the intersection or null if none
     */
    public HitResult intersect(Shape shape, Line line) {
        float distance = Float.MAX_VALUE;
        HitResult hit = null;
        
        for (int i = 0; i < shape.getPointCount(); i++) {
            int next = rationalPoint(shape, i + 1);
            Line local = getLine(shape, i, next);
            
            Vector2f pt = line.intersect(local, true);
            if (pt != null) {
                float newDis = pt.distance(line.getStart());
                if (newDis < distance && newDis > EPSILON) {
                    hit = new HitResult();
                    hit.pt = pt;
                    hit.line = local;
                    hit.p1 = i;
                    hit.p2 = next;
                    distance = newDis;
                }
            }
        }
        
        return hit;
    }
    
    /**
     * Rationalise a point in terms of a given shape
     *
     * @param shape
     *            The shape
     * @param p
     *            The index of the point
     * @return The index that is rational for the shape
     */
    public static int rationalPoint(Shape shape, int p) {
        int r = p;
        while (r < 0) {
            r += shape.getPointCount();
        }
        while (r >= shape.getPointCount()) {
            r -= shape.getPointCount();
        }
        
        return r;
    }
    
    /**
     * Get a line between two points in a shape
     *
     * @param shape
     *            The shape
     * @param s
     *            The index of the start point
     * @param e
     *            The index of the end point
     * @return The line between the two points
     */
    public Line getLine(Shape shape, int s, int e) {
        float[] start = shape.getPoint(s);
        float[] end = shape.getPoint(e);
        
        return new Line(start[0], start[1], end[0], end[1]);
    }
    
    /**
     * Get a line between two points in a shape
     *
     * @param shape
     *            The shape
     * @param sx
     *            The x coordinate of the start point
     * @param sy
     *            The y coordinate of the start point
     * @param e
     *            The index of the end point
     * @return The line between the two points
     */
    public Line getLine(Shape shape, float sx, float sy, int e) {
        float[] end = shape.getPoint(e);
        return new Line(sx, sy, end[0], end[1]);
    }
    
    /**
     * A lightweigtht description of a intersection between a shape and
     * line.
     */
    public class HitResult {
        /** The line on the target shape that intersected */
        private Line line;
        /** The index of the first point on the target shape that forms the line */
        private int p1;
        /** The index of the second point on the target shape that forms the line */
        private int p2;
        /** The position of the intersection */
        private Vector2f pt;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy