Точка за пределами области, которая ближе всего к точке внутри? - PullRequest
1 голос
/ 12 ноября 2011

У меня есть программа, в которой сущность движется в двухмерном пространстве. Чтобы переместиться на один шаг, сущность выбирает свою следующую точку, а затем устанавливает ее в качестве своей текущей точки.

Иногда, однако, следующая точка сущности лежит в Area (java.awt.geom.Area), который запрещен («запретная зона» на самом деле является препятствием скорости).

Как организация может выбрать точку за пределами Area, которая находится ближе всего к ее предпочтительной точке?

Area состоит из различных форм (иногда формы не соприкасаются).

Мой первоначальный план состоял в том, чтобы просто провести линию до предпочтительной точки. Везде, где линия сначала пересекает Area, это будет следующая лучшая точка. Однако нахождение пересечения между линией и Area оказывается довольно сложным.

РЕДАКТИРОВАТЬ: Это не обязательно найти ближайшую точку. Это будет просто найти точку шкафа на той же траектории. Я ищу ближайшую возможную точку.

Возможно, Area не лучший класс для использования. Все, что мне нужно, это что-то, что может добавить несколько фигур, даже если фигуры не соприкасаются.

Ответы [ 4 ]

2 голосов
/ 16 ноября 2011

Я решил проблему:

Сначала найдите все отрезки, которые ограничивают Area.Я написал код, чтобы сделать это для другого ответа .

Затем, это просто вопрос перебора каждого отрезка и записи точки на отрезке, ближайшем к объекту.желаемая точка.Сохраните их в выбранной вами структуре данных (например, ArrayList).

См .: Наименьшее расстояние между точкой и отрезком

Наконец, определитекакая из точек ближе всего к желаемой точке.Вуаля!

Вот демонстрация:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.JFrame;

public class AreaTest extends JFrame{
    private static final long serialVersionUID = -2221432546854106311L;

    Area area = new Area();
    ArrayList<Line2D.Double> areaSegments = new ArrayList<Line2D.Double>();
    Point2D.Double insidePoint = new Point2D.Double(225, 225);
    Point2D.Double closestPoint = new Point2D.Double(-1, -1);
    Point2D.Double bestPoint = new Point2D.Double(-1, -1);
    ArrayList<Point2D.Double> closestPointList = new ArrayList<Point2D.Double>();

    AreaTest() {
        Path2D.Double triangle = new Path2D.Double();
        Random random = new Random();

        // Draw three random triangles
        for (int i = 0; i < 3; i++) {
            triangle.moveTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
            triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
            triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
            triangle.closePath();
            area.add(new Area(triangle));
            triangle.reset();
        }

        // Place a point inside the area
        if (!area.contains(insidePoint)); {
            while (!area.contains(insidePoint)) {
                insidePoint.setLocation(random.nextInt(400) + 50, random.nextInt(400) + 50);
            }
        }

        // Note: we're storing double[] and not Point2D.Double
        ArrayList<double[]> areaPoints = new ArrayList<double[]>();
        double[] coords = new double[6];

        for (PathIterator pi = area.getPathIterator(null); !pi.isDone(); pi.next()) {

            // Because the Area is composed of straight lines
            int type = pi.currentSegment(coords);
            // We record a double array of {segment type, x coord, y coord}
            double[] pathIteratorCoords = {type, coords[0], coords[1]};
            areaPoints.add(pathIteratorCoords);
        }

        double[] start = new double[3]; // To record where each polygon starts
        for (int i = 0; i < areaPoints.size(); i++) {
            // If we're not on the last point, return a line from this point to the next
            double[] currentElement = areaPoints.get(i);

            // We need a default value in case we've reached the end of the ArrayList
            double[] nextElement = {-1, -1, -1};
            if (i < areaPoints.size() - 1) {
                nextElement = areaPoints.get(i + 1);
            }

            // Make the lines
            if (currentElement[0] == PathIterator.SEG_MOVETO) {
                start = currentElement; // Record where the polygon started to close it later
            } 

            if (nextElement[0] == PathIterator.SEG_LINETO) {
                areaSegments.add(
                        new Line2D.Double(
                            currentElement[1], currentElement[2],
                            nextElement[1], nextElement[2]
                        )
                    );
            } else if (nextElement[0] == PathIterator.SEG_CLOSE) {
                areaSegments.add(
                        new Line2D.Double(
                            currentElement[1], currentElement[2],
                            start[1], start[2]
                        )
                    );
            }
        }

        // Calculate the nearest point on the edge
        for (Line2D.Double line : areaSegments) {

            // From: https://stackoverflow.com/questions/6176227
            double u = 
              ((insidePoint.getX() - line.x1) * (line.x2 - line.x1) + (insidePoint.getY() - line.y1) * (line.y2 - line.y1))
            / ((line.x2 - line.x1) * (line.x2 - line.x1) + (line.y2 - line.y1) * (line.y2 - line.y1));

            double xu = line.x1 + u * (line.x2 - line.x1);
            double yu = line.y1 + u * (line.y2 - line.y1);

            if (u < 0) {
                closestPoint.setLocation(line.getP1());
            } else if (u > 1) {
                closestPoint.setLocation(line.getP2());
            } else {
                closestPoint.setLocation(xu, yu);
            }

            closestPointList.add((Point2D.Double) closestPoint.clone());

            if (closestPoint.distance(insidePoint) < bestPoint.distance(insidePoint)) {
                bestPoint.setLocation(closestPoint);
            }
        }

        setSize(new Dimension(500, 500));
        setLocationRelativeTo(null); // To center the JFrame on screen
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setResizable(false);
        setVisible(true);
    }

    public void paint(Graphics g) {
        // Fill the area
        Graphics2D g2d = (Graphics2D) g;
        g.setColor(Color.lightGray);
        g2d.fill(area);

        // Draw the border line by line
        g.setColor(Color.black);
        for (Line2D.Double line : areaSegments) {
            g2d.draw(line);
        }

        // Draw the inside point
        g.setColor(Color.red);
        g2d.fill(
                new Ellipse2D.Double(
                        insidePoint.getX() - 3,
                        insidePoint.getY() - 3,
                        6,
                        6
                        )
            );

        // Draw the other close points
        for (Point2D.Double point : closestPointList) {
            g.setColor(Color.black);
            g2d.fill(
                    new Ellipse2D.Double(
                            point.getX() - 3,
                            point.getY() - 3,
                            6,
                            6
                            )
                );
        }

        // Draw the outside point
        g.setColor(Color.green);
        g2d.fill(
                new Ellipse2D.Double(
                        bestPoint.getX() - 3,
                        bestPoint.getY() - 3,
                        6,
                        6
                        )
            );
    }

    public static void main(String[] args) {
        new AreaTest();
    }
}

Вот результат:

enter image description here

И снова:

enter image description here

0 голосов
/ 16 июля 2017

Посмотреть мой ответ на это сообщение

Вы можете получить ближайшую точку вне многоугольника с помощью простого и легкого подхода:

Просто найдите ближайший линейный сегмент и найдите перпендикулярный угол к тому сегменту, который пересекает точку ввода.

I don't need to describe it, just look at the image!


Пример кода:

Вектор2 равен 2, x и y (как Unity)

public class PolyCollisions {

    // Call this function...
    public static Vector2 doCollisions (Vector2[] polygon, Vector2 point) {

        if(!pointIsInPoly(polygon, point)) {
            // The point is not colliding with the polygon, so it does not need to change location
            return point;
        }

        // Get the closest point off the polygon
        return closestPointOutsidePolygon(polygon, point);

    }

    // Check if the given point is within the given polygon (Vertexes)
    // 
    // If so, call on collision if required, and move the point to the
    // closest point outside of the polygon
    public static boolean pointIsInPoly(Vector2[] verts, Vector2 p) {
        int nvert = verts.length;
        double[] vertx = new double[nvert];
        double[] verty = new double[nvert];
        for(int i = 0; i < nvert; i++) {
            Vector2 vert = verts[i];
            vertx[i] = vert.x;
            verty[i] = vert.y;
        }
        double testx = p.x;
        double testy = p.y;
        int i, j;
        boolean c = false;
        for (i = 0, j = nvert-1; i < nvert; j = i++) {
            if ( ((verty[i]>testy) != (verty[j]>testy)) &&
                    (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
                c = !c;
        }
        return c;
    }

    // Gets the closed point that isn't inside the polygon...
    public static Vector2 closestPointOutsidePolygon (Vector2[] poly, Vector2 point) {

        return getClosestPointInSegment(closestSegment(poly, point), point);

    }

    public static Vector2 getClosestPointInSegment (Vector2[] segment, Vector2 point) {

        return newPointFromCollision(segment[0], segment[1], point);

    }

    public static Vector2 newPointFromCollision (Vector2 aLine, Vector2 bLine, Vector2 p) {

        return nearestPointOnLine(aLine.x, aLine.y, bLine.x, bLine.y, p.x, p.y);

    }

    public static Vector2 nearestPointOnLine(double ax, double ay, double bx, double by, double px, double py) {

        // /1372407/tochka-privyazki-k-linii

        double apx = px - ax;
        double apy = py - ay;
        double abx = bx - ax;
        double aby = by - ay;

        double ab2 = abx * abx + aby * aby;
        double ap_ab = apx * abx + apy * aby;
        double t = ap_ab / ab2;
        if (t < 0) {
            t = 0;
        } else if (t > 1) {
            t = 1;
        }
        return new Vector2(ax + abx * t, ay + aby * t);
    }

    public static Vector2[] closestSegment (Vector2[] points, Vector2 point) {

        Vector2[] returns = new Vector2[2];

        int index = closestPointIndex(points, point);

        returns[0] = points[index];

        Vector2[] neighbors = new Vector2[] {
                points[(index+1+points.length)%points.length],
                points[(index-1+points.length)%points.length]
        };

        double[] neighborAngles = new double[] {
                getAngle(new Vector2[] {point, returns[0], neighbors[0]}),
                getAngle(new Vector2[] {point, returns[0], neighbors[1]})
        };

        if(neighborAngles[0] < neighborAngles[1]) {
            returns[1] = neighbors[0];
        } else {
            returns[1] = neighbors[0];
        }

        return returns;

    }

    public static double getAngle (Vector2[] abc) {

        // https://stackoverflow.com/questions/1211212/how-to-calculate-an-angle-from-three-points
        // atan2(P2.y - P1.y, P2.x - P1.x) - atan2(P3.y - P1.y, P3.x - P1.x)
        return Math.atan2(abc[2].y - abc[0].y, abc[2].x - abc[0].x) - Math.atan2(abc[1].y - abc[0].y, abc[1].x - abc[0].x);

    }

    //public static Vector2 lerp (Vector2 a, Vector2 b, double c) {
    //  
    //  return new Vector2(c*(a.x-b.x)+b.x, c*(a.y-b.y)+b.y);
    //  
    //}

    /*public static Vector2 closestPoint (Vector2[] points, Vector2 point) {

        int leastDistanceIndex = 0;
        double leastDistance = Double.MAX_VALUE;

        for(int i = 0; i < points.length; i++) {
            double dist = distance(points[i], point);
            if(dist < leastDistance) {
                leastDistanceIndex = i;
                leastDistance = dist;
            }
        }

        return points[leastDistanceIndex];

    }*/

    public static int closestPointIndex (Vector2[] points, Vector2 point) {

        int leastDistanceIndex = 0;
        double leastDistance = Double.MAX_VALUE;

        for(int i = 0; i < points.length; i++) {
            double dist = distance(points[i], point);
            if(dist < leastDistance) {
                leastDistanceIndex = i;
                leastDistance = dist;
            }
        }

        return leastDistanceIndex;

    }

    public static double distance (Vector2 a, Vector2 b) {
        return Math.sqrt(Math.pow(Math.abs(a.x-b.x), 2)+Math.pow(Math.abs(a.y-b.y), 2));
    }

}

Полезные ссылки / ответы

Точка привязки к линии

Как рассчитать угол из 3 точек

0 голосов
/ 12 ноября 2011

Формула для расстояния между двумя точками (javascript):

var xDiff = ( point1x - point2x ),
    yDiff = ( point1y - point2y ),
    distance = Math.sqrt( ( xDiff * xDiff ) + ( yDiff * yDiff ) );

Обведите «предложенную новую точку», начиная с единицы x-1, y-1 to x+1, y+1.В каждой точке проверяйте, чтобы убедиться, что это не запрещенная точка, не точка, из которой вы только что пришли, и не за пределами карты.Если он соответствует всем этим критериям, используйте приведенную выше формулу для измерения расстояния и добавьте его в массив.В конце цикла «1-point out» проверьте, есть ли какие-либо расстояния в этом массиве.Если так, возьмите самый маленький и все готово.Если их нет, перейдите на x-2, y-2 to x+2, y+2 (2 балла).

Это будет очень быстро для небольшой области, на которую вы ссылаетесь.

Демонстрация: http://jsfiddle.net/ThinkingStiff/V7Bqm/

var X = 0, 
    Y = 1,
    currentPoint = [5,5],
    proposedPoint = [5,6],
    forbiddenPoints = [[5,6],[6,6],[4,7],[5,7],[6,7],[4,8],[5,8]],
    map = { left:1, top:1, right:10, bottom:10 };

function closestSafePoint( point ) {

    var x = point[X], y = point[Y], safePoints = [];

    for( var left = x - 1, top = y - 1, right = x + 1, bottom = y + 1;
         left <= map.left || top <= map.top || right <= map.right || bottom <= map.bottom;
         left--, top--, right++, bottom++) {

        checkHorizontalPoints( safePoints, point, left, right, top );
        checkHorizontalPoints( safePoints, point, left, right, bottom );
        checkVerticalPoints( safePoints, point, top + 1, bottom - 1, left );
        checkVerticalPoints( safePoints, point, top + 1, bottom - 1, right );

        safePoints.sort( function( a, b ){ return a[1] - b[1] } );

        return safePoints.length ? safePoints[0] : point;

    };

};

function checkHorizontalPoints( points, fromPoint, startX, endX, y ) {

    for( var x = startX; x <= endX ; x++ ) {

        var toPoint = [x, y];

        if( !isForbidden( toPoint ) && !isCurrent( toPoint) && onMap( toPoint ) ) {

            points.push( [toPoint, distance( fromPoint, toPoint )] );

        };

    };

};

function checkVerticalPoints( points, fromPoint, startY, endY, x ) {

    for( var y = startY; y <= endY ; y++ ) {

        var toPoint = [x, y];

        if( !isForbidden( toPoint ) && !isCurrent( toPoint) && onMap( toPoint ) ) {

            points.push( [toPoint, distance( fromPoint, toPoint )] );

        };

    };

};

function isForbidden( point ) {

    for( var index = 0; index < forbiddenPoints.length; index++ ) {

        if( forbiddenPoints[index].toString() == point.toString() ) return true;

    };

};

function isCurrent( point ) {

    return currentPoint.toString() == point.toString() ? true : false;

};

function onMap( point ) {

    var x = point[X], y = point[Y];
    return x >= map.left && y >= map.top && x <= map.right && y <= map.bottom;

};

function distance( pointA, pointB ) {

    var xDiff = ( pointA[X] - pointB[X] ),
        yDiff = ( pointA[Y] - pointB[Y] );

    return Math.sqrt( ( xDiff * xDiff ) + ( yDiff * yDiff ) );    

};

console.log( 
      'current: ' + currentPoint + ', '
    + 'proposed: ' + proposedPoint + ', '
    + 'closest: ' + closestSafePoint( proposedPoint )[0] 
);

Одна оптимизация, которую вы могли бы сделать, если выВы точно уверены, что большинство ваших безопасных мест будут на расстоянии одной или двух точек - это пробиться, как только вы достигнете точки, расстояние которой совпадает с уровнем, на котором вы находитесь.Так что, если вы находитесь на первом цикле, и вы получаете точку, которая является расстоянием = 1, остановитесь, поскольку вы никогда не станете ближе, чем это.

ОБНОВЛЕНИЕ: я заметил, что вы добавили "ту же траекторию" к вашемувопрос.Но в одном из комментариев вы также говорите, что он не может перепрыгнуть через запрещенную область.Эти утверждения, кажется, противоречат друг другу.

Та же траектория немного сложнее и требует некоторого триггера.Посмотрите мою демонстрацию круговых дел в http://jsfiddle.net/ThinkingStiff/uLu7v/. На полпути вниз есть функция «точка на луче»:

$this.siblings( ".circle" ).each( function()

Это вычисляет расстояние для перемещения окружающих кругов на луч отвыбранный круг.Это может быть использовано для расчета точки на вашей траектории.Но я думаю, что моя первоначальная функция - это то, что вы ищете, и вы не имели в виду ту же траекторию.

0 голосов
/ 12 ноября 2011

Самый простой (и самый неэффективный) подход - это грубая сила.У вас есть предпочтительная точка внутри области.чтобы найти ближайшую к нему точку: удерживайте две переменные, одну для минимального расстояния и одну для текущей ближайшей точки.Теперь просто перешагните через любую другую точку в вашем двухмерном пространстве: если эта точка не находится внутри запрещенной области (или любой запрещенной области, если их много), то вычислите расстояние между ней и предпочтительной точкой.Если это расстояние меньше текущего минимального расстояния, сделайте так, чтобы оно стало текущим минимальным расстоянием, и сделайте точку текущей текущей ближайшей точкой.когда вы закончите, у вас будет самая близкая точка за пределами области, и если ничего не было найдено, вы останетесь на своей исходной точке.

Я не специалист по геометрическим алгоритмам, но если двумерное пространство очень большое ирасчет не завершается достаточно быстро, возможно, вы можете попытаться улучшить его следующим образом: в классе Area есть метод содержит метод, который «проверяет, содержит ли внутренняя часть фигуры полностью указанную прямоугольную область».поэтому начните создавать прямоугольники (или квадраты) вокруг предпочтительной точки.вы начинаете с минимального прямоугольника, окружающего точку, и в каждом цикле вы увеличиваете его на одну точку в каждом направлении.для каждого прямоугольника, который вы создаете, проверьте, содержится ли он в области.Вы останавливаете вычисление прямоугольников, когда вы нажимаете на первый прямоугольник, который не полностью содержится в области.затем вы используете вышеупомянутый алгоритм (грубая сила), но только для точек, содержащихся в этом прямоугольнике и не находящихся внутри области.

...