Нахождение угла линии между двумя вращающимися точками на двух орбитах, от одного моста к другому, а не наоборот в JavaFX
Я новичок в JavaFX и очень слаб в тригонометрии и математике и пытался найти угол (линии) между двумя точками. Эти две точки вращаются вокруг общей центральной точки по двум различным идеальным круговым орбитам. Эти точки представляют Землю и Юпитер, и мне нужно найти угол между Землей и Юпитером, а точнее угол между Землей и Юпитером. Юпитер вращается по орбите с большим радиусом, чем в нашей Солнечной системе. Я пробовал "атан" и "атан2", но он не дает ожидаемого ответа для всех углов, я слишком туп, чтобы использовать его правильно. При дальнейших исследованиях я обнаружил что-то вроде "он вычисляет только угол относительно положительной стороны оси X", но я понял суть. При дальнейшем чтении и помощи "Калькулятора уклона" (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 line case. Угол 9'O ---> 3'O правильный и соответствует моим требованиям. Чтобы выяснить, сколько градусов нужно добавить к результатам atan / atan2, в настоящее время я нахожу наклон линии и добавляю 0/90/180/360 градусов к результатам atan / atan2 и получаю ожидаемый результат даже для часов 30 минут. ----> 9 минут. По-прежнему что-то не так.O Корпус часов. Угол 9'O ---> 3'O правильный и соответствует моим требованиям. Чтобы выяснить, сколько градусов нужно добавить к результатам atan / atan2, в настоящее время я нахожу наклон линии и добавляю 0/90/180/360 градусов к результатам atan / atan2 и получаю ожидаемый результат даже для часов 30 минут. ----> 9 минут. По-прежнему что-то не так.O Корпус часов. Угол 9'O ---> 3'O правильный и соответствует моим требованиям. Чтобы выяснить, сколько градусов нужно добавить к результатам atan / atan2, в настоящее время я нахожу наклон линии и добавляю 0/90/180/360 градусов к результатам atan / atan2 и получаю ожидаемый результат даже для часов 30 минут. ----> 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
Приложение непрерывно моделирует положение планет для любой даты / времени и постоянно обновляет положение и градус как в графике, так и в тексте. Так что сейчас нужно время, чтобы выяснить, ошибочны ли расчеты, и я уверен, что это так, но очень сложно найти. Кроме того, вычисляемые градусы представляют собой угол между ретроградными планетами (оптическая иллюзия обратного движения), поэтому трудно обнаружить ошибки вычислений, сравнивая их с чем-либо. Придется записывать все исходные углы, положения, ретро-углы на консоль и готовить построчно, чтобы увидеть большие прыжки, или смотреть исходный угол и приблизительно рассчитывать ретро-угол и проверять.
Два полных дня ушло на то, чтобы найти почти полностью верное рабочее решение и больше не тянуть за волосы. Прочитал похожие вопросы на Stackru, и даже у кого-то была почти такая же проблема, но, как мне кажется, ответа не было, и обсуждение продолжалось в чате, но не смог найти больше информации. Я надеюсь, что это будет очень легко для людей, хорошо разбирающихся в математике. Пожалуйста, предоставьте идеальное решение. Заранее спасибо.
2 ответа
По сути, у вас есть вектор от Земли к Юпитеру, и вы хотите найти угол (то есть направление) этого вектора. Вы также хотите, чтобы угол измерялся против часовой стрелки от положительной оси x. Это означает, что вы можете измерить один и тот же угол, используя два вектора - ваш вектор и единичный вектор в положительном направлении оси x. Это важно, поскольку может упростить реализацию, поскольку:
- JavaFX имеет
Point2D
класс, который может представлять векторы и предоставляет удобные методы (например,angle
). - Для угла между двумя векторами используется обратный косинус. Это даст нам значение между
0
и180
градусов, с которыми мне лично легче работать, чем с диапазоном обратной тангенса-90
к90
градусов.
Например, если у вас есть две точки, вы можете рассчитать угол между неявным вектором и положительной осью 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());
}
}
А вот как выглядит пример:
Во-первых, я не уверен, что это точное решение. Основываясь на том, что я понял из вашего вопроса, я пытаюсь объяснить, как можно вычислить угол между точками.
Чтобы получить угол между двумя точками, вам понадобится третья вершина, в которой измеряется угол. Первый шаг - определить положения всех трех точек относительно конкретной системы координат. Допустим, чтобы определить положение точек относительно панели.
Теперь используйте 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);
}
}
Обновление: пожалуйста, проверьте снимок экрана ниже, касающийся моего понимания углов вычисления.