你基本上有一个从地球到木星的向量,你想找到这个向量的角度(即方向)。您还需要从正 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());
}
}
下面是这个例子的样子: