-1

我是 JavaFX 的新手,在三角学和数学方面真的很弱,一直试图找到两点之间的角度(线的)。这两个点在两个不同的完美圆形轨道上围绕一个共同的中心点旋转。这些点代表地球和木星,我需要找到地球和木星之间的角度,准确地说,是从地球到木星的角度。木星在一个半径更大的轨道上运行,就像在我们的太阳系中一样。我尝试了“atan”和“atan2”,但它并没有给出所有角度的预期答案,我太笨了,无法正确使用它。经过进一步研究,我发现类似“它只计算相对于 X 轴 + ve 侧的角度”,但我明白了。通过“坡度计算器”的进一步阅读和帮助(https://www.calculator.net/slope-calculator.html) 我设法像他们一样解决了这个问题。通过在 atan / atan2 结果中添加适当的度数(例如 180,360)并获得正确和预期的(由我)最终结果。我没有完全理解这种 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 时钟 ----> 9'O 时钟线案例。9'O ---> 3'O 的角度是正确的,符合我的要求。为了弄清楚要在 atan / atan2 结果中增加多少度,我目前正在寻找线的斜率并将 0/90/180/360 度添加到 atan / atan2 结果中,即使对于 3'O 时钟也能获得预期结果----> 9 点钟线。还是有问题。O 时钟线盒。9'O ---> 3'O 的角度是正确的,符合我的要求。为了弄清楚要在 atan / atan2 结果中增加多少度,我目前正在寻找线的斜率并将 0/90/180/360 度添加到 atan / atan2 结果中,即使对于 3'O 时钟也能获得预期结果----> 9 点钟线。还是有问题。O 时钟线盒。9'O ---> 3'O 的角度是正确的,符合我的要求。为了弄清楚要在 atan / atan2 结果中增加多少度,我目前正在寻找线的斜率并将 0/90/180/360 度添加到 atan / atan2 结果中,即使对于 3'O 时钟也能获得预期结果----> 9 点钟线。还是有问题。

在此处输入图像描述

//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

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

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

4

2 回答 2

3

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

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

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

/**
 * 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());
  }
}

下面是这个例子的样子:

演示应用程序的 GIF

于 2020-10-13T22:02:48.060 回答
3

首先,我可能不确定这是否是确切的解决方案。根据我对您的问题的理解,我试图解释如何计算点之间的角度。

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

现在使用 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);
    }
}

在此处输入图像描述

更新: 请查看下面的屏幕截图,了解我对计算角度的理解。 在此处输入图像描述

于 2020-10-13T04:17:30.463 回答