在两个轨道中找到两个旋转点之间的直线的角度,在JavaFX中是从一个点到另一点而不是相反的

问题描述

我是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 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

该应用程序可在任何日期/时间连续模拟行星的位置,并连续更新图形和文本中的位置和度数。因此,要花时间去发现计算是否错误,我确定是正确的,但是很难找到。除此之外,要计算的度数是正在退化的行星之间的角度(向后移动的错觉),因此很难通过与任何事物进行比较来发现计算误差。必须记录所有原始角度,位置,复古角度以进行控制台操作,并逐行准备好以查看大跳动,或者将手表原始角度记录下来并大致计算后视角度并进行验证。

花了整整两天的时间才能找到几乎完全正确的工作解决方案,并且再也无法拉扯我的头发了。已经阅读了关于StackOverflow的类似问题,甚至有人遇到了几乎相同的问题,但我相信没有答案,并且讨论继续在聊天中进行,但找不到更多信息。 我希望对数学精通的人会很容易。请提供完善的解决方案。预先感谢。

解决方法

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

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

现在,通过选择所需的顶点,使用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);
    }
}

enter image description here

更新: 请检查以下屏幕截图,了解我对计算角度的理解。 enter image description here

,

您实际上有一个从地球到木星的向量,并且您想找到该向量的角度(即方向)。您还希望从X轴正方向逆时针测量角度。这意味着您可以使用两个矢量来测量同一角度,这两个矢量是您的矢量和正x方向的单位矢量。这很重要,因为它可以简化实现,因为:

  1. JavaFX具有Point2D类,该类可以表示向量并提供方便的方法(例如angle)。
  2. 两个向量之间的角度使用反余弦。这将使我们得到一个介于0180度之间的值,我个人觉得它比-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的原因是,与大多数(?)GUI框架一样,JavaFX的正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,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),earthOrbitIndicator.getRadius());
    animateOrbit(Duration.seconds(16),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 of demo application