JavaFX: поиск пути с использованием Point2D и Line - PullRequest
0 голосов
/ 10 декабря 2018

Мне нужно реализовать алгоритм поиска пути, контекст следующий: у меня есть начальная точка Point2D и цель (круг).Я рисую линию между начальной точкой и центром круга.Я пытаюсь рассчитать путь, который не пересекает другие круги.(Синий квадрат - это мой объект, который я хочу переместить (в начальной точке)), а красный круг - моя цель).Multiples circle with a line intersecting one of them Сначала я хотел сделать что-то вроде этого: Image showing a path avoiding the circle Но у меня, похоже, иногда глючит код, у меня есть координаты пересечения отрицательных точек(черные точки).Есть ли другой способ решить эту проблему?Я вижу проблему с правильной точки зрения?Существует также проблема, поскольку я перебираю круги, чтобы определить, какие пересекаются или нет, но если линия пересекает 2 или более окружностей, порядок ее пересечения с планетами отличается от порядка, в котором я вижу точки на экране.Моя цель - создать PathTransition между начальной точкой и целью по правильному пути (без пересечения).

Я не упоминал об этом, но контейнер является панелью.

РЕДАКТИРОВАТЬ:

public static Point2D getMidPoint(Point2D p1, Point2D p2) {
    return new Point2D((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2);
}

public static Circle createCircleFromPoint2D(Point2D p) {
    return new Circle(p.getX(), p.getY(), 5);
}

public static Point2D createPoint2D(double x, double y) {
    return new Point2D(x, y);
}

public static Pair<Point2D, Point2D> translate(int distance, Point2D p1, Point2D p2, double reference, double startX) {
    double pente = (p2.getY() - p1.getY()) / (p2.getX() - p1.getX());
    double newX1 = p1.getX() + (startX < reference ? -1 : 1) * (Math.sqrt(((distance*distance) / (1 + (pente*pente)))));
    double newX2 = p2.getX() + (startX > reference ? -1 : 1) * (Math.sqrt(((distance*distance) / (1 + (pente*pente)))));
    double newY1 = pente * (newX1 - p1.getX()) + p1.getY();
    double newY2 = pente * (newX2 - p2.getX()) + p2.getY();

    return new Pair<>(new Point2D(newX1, newY1), new Point2D(newX2, newY2));
}

public void start(Stage primaryStage) throws Exception{
    Pane pane = new Pane();

    Circle objective = new Circle(800, 250, 25);
    Circle circle2 = new Circle(500, 250, 125);
    Circle circle3 = new Circle(240, 400, 75);
    Circle circle4 = new Circle(700, 500, 150, Color.VIOLET);
    Circle circle5 = new Circle(1150, 300, 115, Color.ORANGE);

    Rectangle myObject = new Rectangle(175, 175, 15, 15);

    objective.setFill(Color.RED);
    circle2.setFill(Color.BLUE);
    circle3.setFill(Color.GREEN);
    myObject.setFill(Color.BLUE);

    ArrayList<Circle> circles = new ArrayList<>();
    circles.add(objective);
    circles.add(circle2);
    circles.add(circle3);
    circles.add(circle4);
    circles.add(circle5);

    Line straightLine = new Line();
    pane.setOnMouseClicked(new EventHandler<MouseEvent>() {

        @Override
        public void handle(MouseEvent event) {
            myObject.setX(event.getX());
            myObject.setY(event.getY());

            // My starting coordinates (at mouse position)
            double fromX = myObject.getX();
            double fromY = myObject.getY();

            // Where I want to go
            double toX = objective.getCenterX();
            double toY = objective.getCenterY();

            // Line style
            straightLine.setStartX(event.getX());
            straightLine.setStartY(event.getY());
            straightLine.setEndX(toX);
            straightLine.setEndY(toY);
            straightLine.setStrokeWidth(2);
            straightLine.setStroke(Color.GRAY.deriveColor(0, 1, 1, 0.5));
            straightLine.setStrokeLineCap(StrokeLineCap.BUTT);
            straightLine.getStrokeDashArray().setAll(10.0, 5.0);
            straightLine.setMouseTransparent(true);

            // Coordinates to Point2D
            Point2D from = new Point2D(fromX, fromY);
            Point2D to = new Point2D(toX, toY);

            Path path = new Path();
            path.getElements().add(new MoveTo(fromX, fromY));

            for (Circle c : circles) {
                if (straightLine.intersects(c.getLayoutBounds())) {

                    // I don't want to do anything if I'm intersecting the objective (for now)
                    if (c == objective)
                        continue;

                    Shape s = Shape.intersect(straightLine, c);

                    double xmin = s.getBoundsInLocal().getMinX();
                    double ymin = s.getBoundsInLocal().getMinY();
                    double xmax = s.getBoundsInLocal().getMaxX();
                    double ymax = s.getBoundsInLocal().getMaxY();

                    Point2D intersectionPt1 = createPoint2D((fromX < objective.getCenterX()) ? xmin : xmax , (fromY < objective.getCenterY()) ? ymin : ymax);
                    Point2D intersectionPt2 = createPoint2D((fromX > objective.getCenterX()) ? xmin : xmax , (fromY < objective.getCenterY()) ? ymax : ymin);

                    Point2D middlePt = getMidPoint(intersectionPt1, intersectionPt2);
                    Circle circlePt1 = new Circle(intersectionPt1.getX(), intersectionPt1.getY(), 5);
                    Circle circlePt2 = new Circle(intersectionPt2.getX(), intersectionPt2.getY(), 5);
                    Circle circleMiddle = new Circle(middlePt.getX(), middlePt.getY(), 5, Color.RED);

                    if (c != objective) {
                        // To calculate the points just before/after the first/second points (green points)
                        Pair<Point2D, Point2D> pts = translate(50, intersectionPt1, intersectionPt2, objective.getCenterX(), fromX);
                        Point2D beforePt1 = pts.getKey();
                        Point2D beforePt2 = pts.getValue();
                        Circle circleBeforePt1 = createCircleFromPoint2D(beforePt1);
                        Circle circleBeforePt2 = createCircleFromPoint2D(beforePt2);
                        circleBeforePt1.setFill(Color.GREEN);
                        circleBeforePt2.setFill(Color.GREEN);

                        pane.getChildren().addAll(circleBeforePt1, circleBeforePt2);
                    }

                    pane.getChildren().addAll(s, circlePt1, circlePt2, circleMiddle);
                }
            }

            PathTransition pathTransition = new PathTransition();
            pathTransition.setDuration(Duration.seconds(2));
            pathTransition.setNode(myObject);
            pathTransition.setPath(path);
            pathTransition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);

            pathTransition.play();
        }
    });

    pane.getChildren().addAll(circles);
    pane.getChildren().addAll(myObject, straightLine);

    Scene scene = new Scene(pane, 1600, 900);
    primaryStage.setScene(scene);
    primaryStage.show();
}

Я хочу рассчитать путь (не обязательно самый короткий путь) из точки А в точку Б, но не могу понять, как это сделать.Теперь у меня есть точки, которые я хотел бы передать, я не знаю, как связать их вместе.

Ответы [ 2 ]

0 голосов
/ 14 декабря 2018

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

Если после этого у вас все еще есть ошибка, вы можете написать модульный тест для более простого воспроизведения и опубликовать его здесь.

По теме:

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

ПочемуНе используйте стандартный алгоритм A *, где все небелые пиксели являются препятствиями.Это излишне?

0 голосов
/ 14 декабря 2018

Стратегия и реализация решения

Я построил решение со следующей стратегией: На данной линии от from(X,Y) до to(X,Y) Я вычисляю ближайшее пересечение с одной из форм препятствий.Из этой формы я беру длину пересечения как меру того, насколько велико препятствие, и смотрю на точки слева и справа на 1/2 этой длины от некоторой точки незадолго до пересечения.Первая из левой и правой точек, которая не находится внутри препятствия, затем используется для разделения задачи нахождения пути вокруг препятствий.

    protected void computeIntersections(double fromX, double fromY, double toX, double toY) {
        // recursively test for obstacles and try moving around them by 
        // calling this same procedure on the segments to and from 
        // a suitable new point away from the line
        Line testLine = new Line(fromX, fromY, toX, toY);
        //compute the unit direction of the line
        double dX = toX-fromX, dY = toY-fromY;
        double ds = Math.hypot(dX,dY);
        dX /= ds; dY /= ds;

        // get the length from the initial point of the minimal intersection point
        // and the opposite point of the same obstacle, remember also the closest obstacle
        double t1=-1, t2=-1;
        Shape obst = null;

        for (Shape c : lstObstacles) {
            if (testLine.intersects(c.getLayoutBounds())) {

                Shape s = Shape.intersect(testLine, c);
                if( s.getLayoutBounds().isEmpty() ) continue;
                // intersection bounds of the current shape
                double s1, s2;
                if(Math.abs(dX) < Math.abs(dY) ) {
                    s1 = ( s.getBoundsInLocal().getMinY()-fromY ) / dY;
                    s2 = ( s.getBoundsInLocal().getMaxY()-fromY ) / dY;
                } else {
                    s1 = ( s.getBoundsInLocal().getMinX()-fromX ) / dX;
                    s2 = ( s.getBoundsInLocal().getMaxX()-fromX ) / dX;
                }
                // ensure s1 < s2
                if ( s2 < s1 ) { double h=s2; s2=s1; s1=h; }
                // remember the closest intersection
                if ( ( t1 < 0 ) || ( s1 < t1 ) ) { t1 = s1; t2 = s2; obst = c; }
            }
        }
        // at least one intersection found
        if( ( obst != null ) && ( t1 > 0 )  ) {
            intersectionDecorations.getChildren().add(Shape.intersect(testLine, obst));
            // coordinates for the vertex point of the path
            double midX, midY; 
            // go to slightly before the intersection set
            double intersectX = fromX + 0.8*t1*dX, intersectY = fromY + 0.8*t1*dY;
            // orthogonal segment of half the length of the intersection, go left and right
            double perpX = 0.5*(t2-t1)*dY, perpY = 0.5*(t1-t2)*dX;
            Rectangle testRect = new Rectangle( 10, 10);
            // go away from the line to hopefully have less obstacle from the new point
            while( true ) {
                // go "left", test if free
                midX = intersectX + perpX; midY = intersectY + perpY;
                testRect.setX(midX-5); testRect.setY(midY-5); 
                if( Shape.intersect(testRect, obst).getLayoutBounds().isEmpty() ) break;
                // go "right"
                midX = intersectX - perpX; midY = intersectY - perpY;
                testRect.setX(midX-5); testRect.setY(midY-5); 
                if( Shape.intersect(testRect, obst).getLayoutBounds().isEmpty() ) break;
                // if obstacles left and right, try closer points next
                perpX *= 0.5; perpY *= 0.5;
            }
            intersectionDecorations.getChildren().add(new Line(intersectX, intersectY, midX, midY));
            // test the first segment for intersections with obstacles
            computeIntersections(fromX, fromY, midX, midY);
            // add the middle vertex to the solution path
            connectingPath.getElements().add(new LineTo(midX, midY));
            // test the second segment for intersections with obstacles
            computeIntersections(midX, midY, toX, toY);
        }
    }

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

pathfinder application

Вспомогательные линии - это пересечениякоторые были использованы и перпендикулярные линии к новым средним точкам.

PathfinderApp.java

Я использовал эту проблему, чтобы ознакомиться с использованием FXML, таким образом, основное приложение имеет обычный шаблонный код.

package pathfinder;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class PathfinderApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("pathfinder.fxml"));
        primaryStage.setTitle("Finding a path around obstacles");
        primaryStage.setScene(new Scene(root, 1600, 900));
        primaryStage.show();
    }


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

pathfinder.fxml

Файл FXML содержит «наиболее» статические (в смысле всегда присутствующие для данного типа задачи) элементы пользовательского интерфейса.Это прямоугольник курсора, целевой круг и линия между ними.Затем группы для препятствий и «украшения» от строительства пути, и сам путь.Такое разделение позволяет очищать и заполнять эти группы независимо друг от друга без каких-либо других организационных усилий.

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.Group?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.shape.Line?>
<?import javafx.scene.shape.Path?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.scene.paint.Color?> 

<Pane xmlns:fx="http://javafx.com/fxml" 
      fx:controller="pathfinder.PathfinderController" onMouseClicked="#setCursor">
    <Circle fx:id="target" centerX="800" centerY="250" radius="25" fill="red"/>
    <Rectangle fx:id="cursor" x="175" y="175" width="15" height="15" fill="lightblue"/>
    <Line fx:id="straightLine" startX="${cursor.X}" startY="${cursor.Y}" endX="${target.centerX}" endY="${target.centerY}" 
          strokeWidth="2" stroke="gray" strokeLineCap="butt" strokeDashArray="10.0, 5.0" mouseTransparent="true" />
    <Group fx:id="obstacles" />
    <Group fx:id="intersectionDecorations" />
    <Path fx:id="connectingPath" strokeWidth="2" stroke="blue" />
</Pane>

PathfinderController.java

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

package pathfinder;

import javafx.fxml.FXML;
import javafx.geometry.Bounds;
import javafx.scene.layout.Pane;
import javafx.scene.Group;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.shape.Shape;
import javafx.scene.shape.Line;
import javafx.scene.shape.Path;
import javafx.scene.shape.LineTo; 
import javafx.scene.shape.MoveTo; 
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.paint.Color;
import javafx.scene.input.MouseEvent;

import java.util.*;

public class PathfinderController {

    @FXML
    private Circle target;

    @FXML
    private Rectangle cursor;

    @FXML
    private Line straightLine;

    @FXML
    private Path connectingPath;

    @FXML
    private Group obstacles, intersectionDecorations;

    private static List<Shape> lstObstacles = Arrays.asList(
        new Circle( 500, 250, 125, Color.BLUE  ),  
        new Circle( 240, 400,  75, Color.GREEN ), 
        new Circle( 700, 500, 150, Color.VIOLET), 
        new Circle(1150, 300, 115, Color.ORANGE)
    );

    @FXML
    public void initialize() {
        straightLine.startXProperty().bind(cursor.xProperty());
        straightLine.startYProperty().bind(cursor.yProperty());
        obstacles.getChildren().addAll(lstObstacles);
        findPath();
    }

    @FXML
    protected void setCursor(MouseEvent e) {
        Shape test = new Rectangle(e.getX()-5, e.getY()-5, 10, 10);
        for (Shape c : lstObstacles) {
            if( !Shape.intersect(c, test).getLayoutBounds().isEmpty() ) return;
        }

        cursor.setX(e.getX());
        cursor.setY(e.getY());        
        findPath();
    }

    protected void findPath() {
        double fromX = cursor.getX();
        double fromY = cursor.getY();

        double toX = target.getCenterX();
        double toY = target.getCenterY();

        intersectionDecorations.getChildren().clear();
        connectingPath.getElements().clear();

        // first point of path
        connectingPath.getElements().add(new MoveTo(fromX, fromY));
        // check path for intersections, move around if necessary
        computeIntersections(fromX, fromY, toX, toY);
        // last point of the path
        connectingPath.getElements().add(new LineTo(toX, toY));
    }

    protected void computeIntersections(double fromX, double fromY, double toX, double toY) {
        ...
        }

    // end class
}
...