在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 方向上的单位向量)测量相同的角度。这很重要,因为它可以简化实施,因为:
- JavaFX 有
Point2D
class 可以表示向量并提供方便的方法(例如 angle
)。
- 两个向量之间的角度使用反余弦。这将为我们提供一个介于
0
和 180
度之间的值,我个人认为它比反正切的范围 -90
到 90
度更容易处理。
例如,如果您有两个点,则可以使用以下公式计算隐式向量与正 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());
}
}
示例如下所示:
我是 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 方向上的单位向量)测量相同的角度。这很重要,因为它可以简化实施,因为:
- JavaFX 有
Point2D
class 可以表示向量并提供方便的方法(例如angle
)。 - 两个向量之间的角度使用反余弦。这将为我们提供一个介于
0
和180
度之间的值,我个人认为它比反正切的范围-90
到90
度更容易处理。
例如,如果您有两个点,则可以使用以下公式计算隐式向量与正 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());
}
}
示例如下所示: