在JavaFX中找到两个轨道中两个旋转点之间的线的角度,从一点到另一点,反之亦然

Finding angle of a line between two revolving points in two orbits, from one pont to other and not viceversa in JavaFX

我是 JavaFX 的新手,在三角函数和数学方面非常薄弱,一直在尝试找出两点之间的角度(直线)。这两点在两个不同的完美圆轨道上围绕一个共同的中心点旋转。这些点代表地球和木星,我需要找到地球和木星之间的角度,准确地说,是从地球到木星的角度。木星在我们的太阳系中的半径较大的轨道上旋转。我试过 'atan' 和 'atan2' 但它没有给出所有角度的预期答案,我太笨了无法正确使用它。经过进一步的研究,我发现了类似“它只计算相对于 X 轴的 +ve 侧的角度”之类的东西,但我明白了。在进一步阅读和 'Slope Calculator' (https://www.calculator.net/slope-calculator.html) 的帮助下,我设法像他们一样解决了这个问题。通过将适当的度数(如 180,360)添加到 atan / atan2 结果并获得正确和(我)预期的最终结果。我没有完全理解这种添加 90/180/360 度(我还没有看到他们添加 90 度,或者为什么要添加 0 度)。我可怜的数学技能说它是测量角度(旧的 +ve x 轴,360/180 - 测量角度?)与 360 度的差异,或者简单地说,未测量的角度(总共 180 或 360 度)问题是,结果并不总是预期的,很少出错,差异很大,完全错误。这是由于向 atan / atan2 结果添加了错误的度数。计算器站点使用的方法给出了正确的结果,除了 180 度线(3'O 时钟到 9'O 时钟线),它给出 0 度。 (不应该是 180 度吗?无论如何,这样的线我需要 180 度)。该站点将结果添加 180 度或 360 度以获得最终结果并且是正确的并且根据我的要求,期望 3'O Clock ----> 9'O Clock 线情况。 9'O ---> 3'O 的角度是正确的,符合我的要求。为了弄清楚要添加到 atan / atan2 结果的度数,我目前正在寻找直线的斜率并将 0/90/180/360 度添加到 atan / atan2 结果并获得预期结果,即使是 3'O 时钟----> 9'O时钟线。还是有问题。

enter image description here

//PANE_HEIGHT is 960, the height and width of pane. PositionX is subtracted from it to get the //Cartesian plane coordinates instead of screen coordinates

    currentJupiterAngleRetroRough = (Math.toDegrees(Math.atan((((PANE_HEIGHT - jupiterPositionY) - ( PANE_HEIGHT-earthPositionY))) / ((jupiterPositionX) - (earthPositionX)))));

//Finding the slope of the line
slope = (((PANE_HEIGHT-jupiterPositionY) - (PANE_HEIGHT-earthPositionY)) / ((jupiterPositionX) - (earthPositionX)));

//Adding required angles to output of atan to get final degrees,based on the slope
currentJupiterAngleRetro = (Math.toDegrees( Math.atan((((PANE_HEIGHT - jupiterPositionY) - ( PANE_HEIGHT-earthPositionY))) / ((jupiterPositionX) - (earthPositionX) ))))  +  
        (slope<0 ? 360:(slope==0?0:(slope>0 & slope<1 ? 0:(slope>1 & slope<2 ? 90:180 ))));

//Various approaches to find the appropriate degrees to add to atan result
(slope<0 ? 360:180); 
(slope<0 ? 360:(slope==0?0:180 ));
// Different One
// Another one
// and so on
(slope<0 ? 360:(slope==0?0:(slope>0 & slope<1 ? 0:(slope>1 & slope<2 ? 90:180 )))); //Improved one, still not fully correct

该应用程序连续模拟任何行星的位置 date/time 并不断更新图形和文本的位置和度数。因此,找出计算是否错误是一项耗时的任务,我确实发现了,但很难找到。除此之外,要计算的度数是逆行行星之间的角度(向后移动的错觉),因此很难通过与任何事物进行比较来发现计算错误。将必须记录所有原始角度、位置、逆向角度以控制台并逐行准备以查看大跳跃,或者将观看原始角度并在脑海中近似计算逆向角度并验证。

我花了整整两天的时间才找到一个几乎完全正确的工作解决方案,而且不能再拉扯我的头发了。在 Whosebug 上阅读了类似的问题,甚至有人遇到了几乎相同的问题,但我相信没有答案,并且在聊天中继续讨论但找不到更多信息。 我希望这对擅长数学的人来说会很容易。请提供完美的解决方案。提前致谢。

首先,我可能不确定这是否是正确的解决方案。根据我从您的问题中了解到的内容,我正在尝试解释如何计算点之间的角度。

要获得两点之间的角度,您需要测量角度的第三个顶点。第一步是确定所有三个点相对于特定坐标系的位置。比方说要参考窗格来确定点的位置。

现在使用 Point2D API 通过选择所需的顶点来计算三点之间的角度。这将始终为您带来锐角。

以下是以太阳为顶点计算两颗行星夹角的示例演示。

请注意,顶点不必是中心点。可以是参考坐标系中的任意一点。

我希望这可以帮助您开始学习:)

import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.binding.DoubleBinding;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import javafx.util.Duration;

public class EarthJupiterAngle_Demo extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        VBox root = new VBox();
        root.setSpacing(10);
        root.setPadding(new Insets(10));
        Scene scene = new Scene(root, 600, 600);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Angle between Earth & Jupiter");
        primaryStage.show();

        Pane pane = new Pane();
        pane.setPadding(new Insets(10));
        pane.setStyle("-fx-border-width:1px;-fx-border-color:black;-fx-background-color:white;");
        VBox.setVgrow(pane, Priority.ALWAYS);

        Circle sun = new Circle(8, Color.ORANGE);
        sun.centerXProperty().bind(pane.widthProperty().divide(2));
        sun.centerYProperty().bind(pane.heightProperty().divide(2));

        double earthAU = 100;
        Circle earthOrbit = new Circle(earthAU);
        earthOrbit.setFill(null);
        earthOrbit.setStroke(Color.LIGHTBLUE);
        earthOrbit.setStrokeWidth(1);
        earthOrbit.getStrokeDashArray().addAll(10d, 5d);
        earthOrbit.centerXProperty().bind(sun.centerXProperty());
        earthOrbit.centerYProperty().bind(sun.centerYProperty());

        Circle earth = new Circle(5, Color.BLUE);
        earth.layoutXProperty().bind(sun.centerXProperty());
        earth.layoutYProperty().bind(sun.centerYProperty());
        PathTransition earthRotate = new PathTransition();
        earthRotate.setDuration(Duration.millis(10000));
        earthRotate.setNode(earth);
        earthRotate.setPath(earthOrbit);
        earthRotate.setCycleCount(Animation.INDEFINITE);
        earthRotate.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
        earthRotate.setInterpolator(Interpolator.LINEAR);
        earthRotate.play();

        Line earthLine = new Line();
        earthLine.startXProperty().bind(sun.centerXProperty());
        earthLine.startYProperty().bind(sun.centerYProperty());
        earthLine.endXProperty().bind(earth.layoutXProperty().add(earth.translateXProperty()));
        earthLine.endYProperty().bind(earth.layoutYProperty().add(earth.translateYProperty()));
        earthLine.setStroke(Color.GRAY);
        earthLine.setStrokeWidth(1);
        earthLine.getStrokeDashArray().addAll(10d, 5d);

        double jupiterAU = 180;
        Circle jupiterOrbit = new Circle(jupiterAU);
        jupiterOrbit.setFill(null);
        jupiterOrbit.setStroke(Color.SANDYBROWN);
        jupiterOrbit.setStrokeWidth(1);
        jupiterOrbit.getStrokeDashArray().addAll(10d, 5d);
        jupiterOrbit.centerXProperty().bind(sun.centerXProperty());
        jupiterOrbit.centerYProperty().bind(sun.centerYProperty());

        Circle jupiter = new Circle(7, Color.BROWN);
        jupiter.layoutXProperty().bind(sun.centerXProperty());
        jupiter.layoutYProperty().bind(sun.centerYProperty());
        PathTransition jupiterRotate = new PathTransition();
        jupiterRotate.setDuration(Duration.millis(18000));
        jupiterRotate.setNode(jupiter);
        jupiterRotate.setPath(jupiterOrbit);
        jupiterRotate.setCycleCount(Animation.INDEFINITE);
        jupiterRotate.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
        jupiterRotate.setInterpolator(Interpolator.LINEAR);
        jupiterRotate.play();

        Line jupiterLine = new Line();
        jupiterLine.startXProperty().bind(sun.centerXProperty());
        jupiterLine.startYProperty().bind(sun.centerYProperty());
        jupiterLine.endXProperty().bind(jupiter.layoutXProperty().add(jupiter.translateXProperty()));
        jupiterLine.endYProperty().bind(jupiter.layoutYProperty().add(jupiter.translateYProperty()));
        jupiterLine.setStroke(Color.GRAY);
        jupiterLine.setStrokeWidth(1);
        jupiterLine.getStrokeDashArray().addAll(10d, 5d);


        DoubleBinding angle = new DoubleBinding() {
            {
                bind(earth.translateXProperty(), earth.layoutXProperty(), earth.translateYProperty(), earth.layoutYProperty()
                        , jupiter.translateXProperty(), jupiter.layoutXProperty(), jupiter.translateYProperty(), jupiter.layoutYProperty());
            }

            @Override
            protected double computeValue() {
                // Sun position in pane
                double sX = sun.getCenterX();
                double sY = sun.getCenterY();

                // Earth position in pane
                double eX = earth.getLayoutX() + earth.getTranslateX();
                double eY = earth.getLayoutY() + earth.getTranslateY();

                // Jupiter position in pane
                double jX = jupiter.getLayoutX() + jupiter.getTranslateX();
                double jY = jupiter.getLayoutY() + jupiter.getTranslateY();

                // Use Point2D API to calculate angle between three points
                return Math.round(new Point2D(sX, sY).angle(new Point2D(eX, eY), new Point2D(jX, jY)));
            }
        };

        Label angleLabel = new Label("Angle : ");
        Label valLabel = new Label("");

        Timeline angleTimeline = new Timeline(new KeyFrame(Duration.millis(100), e -> valLabel.setText(angle.get() + " deg")));
        angleTimeline.setCycleCount(Animation.INDEFINITE);
        angleTimeline.play();

        pane.getChildren().addAll(earthLine, jupiterLine, sun, earthOrbit, earth, jupiterOrbit, jupiter);
        ToggleButton button = new ToggleButton("Pause");
        button.setPrefWidth(120);
        button.selectedProperty().addListener((obs, old, selected) -> {
            if (selected) {
                button.setText("Play");
                earthRotate.pause();
                jupiterRotate.pause();
                angleTimeline.pause();
            } else {
                button.setText("Pause");
                earthRotate.play();
                jupiterRotate.play();
                angleTimeline.play();
            }
        });
        HBox hb = new HBox(button, angleLabel, valLabel);
        hb.setStyle("-fx-font-size:18px;");
        hb.setAlignment(Pos.CENTER_LEFT);
        hb.setSpacing(10);
        root.getChildren().addAll(hb, pane);
    }
}

更新: 请检查下面的屏幕截图,了解我对计算角度的理解。

你基本上有一个从地球到木星的矢量,你想找到这个矢量的角度(即方向)。您还需要从正 x-axis 逆时针测量的角度。这意味着您可以使用 两个 向量(您的向量和正 x 方向上的单位向量)测量相同的角度。这很重要,因为它可以简化实施,因为:

  1. JavaFX 有 Point2D class 可以表示向量并提供方便的方法(例如 angle)。
  2. 两个向量之间的角度使用反余弦。这将为我们提供一个介于 0180 度之间的值,我个人认为它比反正切的范围 -9090 度更容易处理。

例如,如果您有两个点,则可以使用以下公式计算隐式向量与正 x-axis 之间的角度:

/**
 * Computes the angle (in degrees) of the vector from {@code p1} to {@code p2}. The angle 
 * will be in the range {@code 0} (inclusive) to {@code 360} (exclusive) as measured 
 * counterclockwise from the positive x-axis.
 *
 * @param p1 the start point of the vector
 * @param p2 the end point of the vector
 * @return the angle, in degrees, of the vector from {@code p1} to {@code p2} measured
 *         counterclockwise from the positive x-axis
 */
public static double computeAngleOfVector(Point2D p1, Point2D p2) {
  Point2D vector = new Point2D(p2.getX() - p1.getX(), p2.getY() - p1.getY());
  double angle = vector.angle(1.0, 0.0);
  if (vector.getY() > 0) {
    // vector pointing downwards and thus is in the 3rd or 4th quadrant
    return 360.0 - angle;
  }
  // vector pointing upwards and thus is in the 1st or 2nd quadrant
  return angle;
}

请注意,我使用 vector.getY() > 0 而不是 vector.getY() < 0 的原因是因为 JavaFX 与大多数(?)GUI 框架一样,具有正 y 方向指向 向下 屏幕。根据您在模型中表示坐标系的方式,您可能需要稍微修改代码。


这是一个应用程序,以我认为符合您要求的方式展示了上述内容:

import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.value.ObservableDoubleValue;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Main extends Application {

  private static final double SCENE_WIDTH = 1000;
  private static final double SCENE_HEIGHT = 700;

  @Override
  public void start(Stage primaryStage) {
    Circle sun = createCelestialBody(50, Color.YELLOW);

    Circle earth = createCelestialBody(20, Color.BLUE);
    Circle earthOrbitIndicator = createOrbitIndicator(150);

    Circle jupiter = createCelestialBody(35, Color.BROWN);
    Circle jupiterOrbitIndicator = createOrbitIndicator(300);

    Line earthJupiterVector = createBodyToBodyVector(earth, jupiter);
    DoubleBinding angleObservable = createAngleBinding(earthJupiterVector);
    Line xAxisIndicator = createXAxisIndicator(earth);
    Arc angleIndicator = createAngleIndicator(earth, angleObservable);

    Pane root =
        new Pane(
            createAngleLabel(angleObservable),
            earthOrbitIndicator,
            jupiterOrbitIndicator,
            sun,
            earth,
            jupiter,
            earthJupiterVector,
            xAxisIndicator,
            angleIndicator);
    primaryStage.setScene(new Scene(root, SCENE_WIDTH, SCENE_HEIGHT));
    primaryStage.setTitle("Earth-Jupiter Vector Angle");
    primaryStage.setResizable(false);
    primaryStage.show();

    animateOrbit(Duration.seconds(7), earth, earthOrbitIndicator.getRadius());
    animateOrbit(Duration.seconds(16), jupiter, jupiterOrbitIndicator.getRadius());
  }

  private Label createAngleLabel(ObservableDoubleValue angleObservable) {
    Label label = new Label();
    label.setPadding(new Insets(10));
    label.setUnderline(true);
    label.setFont(Font.font("Monospaced", FontWeight.BOLD, 18));
    label
        .textProperty()
        .bind(
            Bindings.createStringBinding(
                () -> String.format("Angle: %06.2f", angleObservable.get()), angleObservable));
    return label;
  }

  private Circle createCelestialBody(double radius, Color fill) {
    Circle body = new Circle(radius, fill);
    body.setCenterX(SCENE_WIDTH / 2);
    body.setCenterY(SCENE_HEIGHT / 2);
    return body;
  }

  private Circle createOrbitIndicator(double radius) {
    Circle indicator = new Circle(radius, Color.TRANSPARENT);
    indicator.setStroke(Color.DARKGRAY);
    indicator.getStrokeDashArray().add(5.0);
    indicator.setCenterX(SCENE_WIDTH / 2);
    indicator.setCenterY(SCENE_HEIGHT / 2);
    return indicator;
  }

  private void animateOrbit(Duration duration, Circle celestialBody, double orbitRadius) {
    Circle path = new Circle(SCENE_WIDTH / 2, SCENE_HEIGHT / 2, orbitRadius);
    PathTransition animation = new PathTransition(duration, path, celestialBody);
    animation.setCycleCount(Animation.INDEFINITE);
    animation.setInterpolator(Interpolator.LINEAR);
    animation.playFromStart();
  }

  private Line createBodyToBodyVector(Circle firstBody, Circle secondBody) {
    Line vectorLine = new Line();
    vectorLine.setStroke(Color.BLACK);
    vectorLine.setStrokeWidth(2);
    vectorLine.getStrokeDashArray().add(5.0);
    vectorLine.startXProperty().bind(centerXInParentOf(firstBody));
    vectorLine.startYProperty().bind(centerYInParentOf(firstBody));
    vectorLine.endXProperty().bind(centerXInParentOf(secondBody));
    vectorLine.endYProperty().bind(centerYInParentOf(secondBody));
    return vectorLine;
  }

  private Line createXAxisIndicator(Circle anchor) {
    Line xAxisIndicator = new Line();
    xAxisIndicator.setStroke(Color.GREEN);
    xAxisIndicator.setStrokeWidth(2);
    xAxisIndicator.startXProperty().bind(centerXInParentOf(anchor));
    xAxisIndicator.startYProperty().bind(centerYInParentOf(anchor));
    xAxisIndicator.endXProperty().bind(xAxisIndicator.startXProperty().add(75));
    xAxisIndicator.endYProperty().bind(xAxisIndicator.startYProperty());
    return xAxisIndicator;
  }

  private Arc createAngleIndicator(Circle anchor, ObservableDoubleValue angleObservable) {
    Arc arc = new Arc();
    arc.setFill(Color.TRANSPARENT);
    arc.setStroke(Color.RED);
    arc.setStrokeWidth(2);
    arc.getStrokeDashArray().add(5.0);
    arc.centerXProperty().bind(centerXInParentOf(anchor));
    arc.centerYProperty().bind(centerYInParentOf(anchor));
    arc.setRadiusX(50);
    arc.setRadiusY(50);
    arc.setStartAngle(0);
    arc.lengthProperty().bind(angleObservable);
    return arc;
  }

  // NOTE: getCenterX() and getCenterY() were added in JavaFX 11. The calculations
  //       are simple, however. It's just (minX + maxX) / 2 and similar for y.

  private DoubleBinding centerXInParentOf(Node node) {
    return Bindings.createDoubleBinding(
        () -> node.getBoundsInParent().getCenterX(), node.boundsInParentProperty());
  }

  private DoubleBinding centerYInParentOf(Node node) {
    return Bindings.createDoubleBinding(
        () -> node.getBoundsInParent().getCenterY(), node.boundsInParentProperty());
  }

  private DoubleBinding createAngleBinding(Line line) {
    return Bindings.createDoubleBinding(
        () -> {
          Point2D vector =
              new Point2D(line.getEndX() - line.getStartX(), line.getEndY() - line.getStartY());
          double angle = vector.angle(1, 0);
          if (vector.getY() > 0) {
            return 360 - angle;
          }
          return angle;
        },
        line.startXProperty(),
        line.endXProperty(),
        line.startYProperty(),
        line.endYProperty());
  }
}

示例如下所示: