JavaFX:使用Point2D和Line进行寻路

时间:2018-12-10 18:13:49

标签: java math javafx path-finding

我需要实现某种寻路算法,上下文如下: 我有一个起点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();
}

我想计算一个从点A到点B的路径(不一定是最短路径),但无法弄清楚该怎么做。现在,我有一些要讲的要点,我不知道如何将它们联系在一起。

2 个答案:

答案 0 :(得分:0)

解决方案策略和实施

我使用以下策略构建了一个解决方案:在从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
}

答案 1 :(得分:0)

这可能不是理想的答案,但是您是否考虑过对数学代码进行单元测试?数学代码很容易做到,然后您可以确定低级函数可以正常工作。

如果此后仍然存在该错误,则可以编写单元测试以使其更容易重现并将其发布在这里。

关于主题:

您使用直线的算法可能会变得非常复杂,甚至无法找到带有更多和/或重叠的圆的解决方案。

为什么不使用标准的A *算法,因为所有非白色像素都是障碍。那是过度杀伤力吗?