I am working with an attempt to recreate the Bohr atomic model, at this moment I have managed to get the electrons to have an adequate position relative to the total number of electrons in the orbital, but now I am struggling a lot to make them rotate around the said orbital. The problem I have is that the path transition makes them go from said places, towards the center of the orbital, instead of making them rotate around it.
Alright, I have this nodes:
@FXML
private Circle orbital;
@FXML
private Circle Electron;
@FXML
private Circle Electron2;
@FXML
private Circle Electron3;
And this is the method that should make them spin:
private void animate(Circle electron, Circle orbital, double angulo, int duration) {
Path path = new Path();
double anguloRadianes = Math.toRadians(angulo);//pass the angle to radians
double X = orbital.getCenterX() + orbital.getRadius() * Math.cos(anguloRadianes);//calculate the x coordinate of the position of the electron
double Y = orbital.getCenterY() + orbital.getRadius() * Math.sin(anguloRadianes);//calculate the y coordinate of the position of the electron
path.getElements().add(new javafx.scene.shape.MoveTo(X, Y));//Put the electrons in their respective places
path.getElements().add(new javafx.scene.shape.ArcTo(orbital.getRadius(), orbital.getRadius(), angulo, electron.getCenterX(), electron.getCenterY(), false, false));//This should trace the path of the electrons, which should be around the orbital
//Here start the animations
PathTransition pathTransition = new PathTransition();
pathTransition.setNode(electron);
pathTransition.setPath(path);
pathTransition.setInterpolator(javafx.animation.Interpolator.LINEAR);
pathTransition.setCycleCount(PathTransition.INDEFINITE);
pathTransition.setDuration(Duration.millis(duration));
pathTransition.play();
}
And this is how I am calling the method for testing:
public void calcular(ActionEvent event){
try{
int num = Integer.parseInt(cantidad.getText());//gets the number of electrons for the orbital
double ang = 360 / num;//divide the 360 degrees of the circle by the total number of electrons
double x = orbital.getCenterX();
double y = orbital.getCenterY();
double rad = orbital.getRadius();
animate(Electron, orbital, ang * 1, 750);
animate(Electron2, orbital, ang * 2, 750);
animate(Electron3, orbital, ang * 3, 750);
}
catch (Exception e){
System.out.println(e);
}
}
You can:
PathTransition
.OR
Rotate
transform about a pivot point using a Timeline
.The 2D example demonstrates using both the PathTransition and rotation in a timeline techniques.
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Background;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class SolApp extends Application {
private static final double S = 400, C = S / 2, O = S * 3/8;
private static final Duration T = Duration.seconds(10);
private static final Color INDIA_INK = Color.web("#3c3f4a");
@Override
public void start(Stage stage) {
Circle sun = new Circle(
C, C,
S/10,
Color.ORANGERED
);
Circle earth = new Circle(
C + O, C,
S/40,
Color.PALETURQUOISE
);
Circle orbit = new Circle(C, C, O);
orbit.setFill(null);
orbit.setStroke(Color.GREEN);
orbit.setStrokeWidth(2);
Pane space = new Pane(sun, orbit, earth);
space.setPrefSize(S, S);
space.setBackground(Background.fill(INDIA_INK));
// Animation orbiter = getTimelineOrbiter(earth);
Animation orbiter = getPathTransitionOrbiter(orbit, earth);
stage.setScene(new Scene(space));
stage.show();
orbiter.play();
}
private static Transition getPathTransitionOrbiter(Circle orbit, Circle earth) {
PathTransition orbiter = new PathTransition(T, orbit, earth);
orbiter.setInterpolator(Interpolator.LINEAR);
orbiter.setCycleCount(Animation.INDEFINITE);
return orbiter;
}
private static Timeline getTimelineOrbiter(Circle earth) {
Rotate rotate = new Rotate();
rotate.setPivotX(C);
rotate.setPivotY(C);
earth.getTransforms().add(rotate);
Timeline orbiter = new Timeline(
new KeyFrame(
Duration.ZERO,
new KeyValue(
rotate.angleProperty(),
0
)
),
new KeyFrame(
T,
new KeyValue(
rotate.angleProperty(),
360
)
)
);
orbiter.setCycleCount(Animation.INDEFINITE);
return orbiter;
}
public static void main(String[] args) {
launch(args);
}
}
If you want to adjust the starting point of an orbit, you can apply the following line before playing the orbiter animation, adjusting the divisor between 0 and 1 as needed.
orbiter.jumpTo(T.divide(.17));
This will allow you to position a node anywhere along an arbitrary path (even if you aren't animating along the path).
For circles and spheres, like those in the example here, you won't notice the node orientation, but for non-symmetric nodes, you may wish to orient the node so that it points in the direction you require as it is positioned on the path or travels along it.
The same technique will also work for animating objects in 3D scenes.
import javafx.animation.*;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class SolAppThreeD extends Application {
private static final double S = 400, C = S / 2, O = S * 3/8;
private static final Duration T = Duration.seconds(10);
private static final Color INDIA_INK = Color.web("#3c3f4a");
@Override
public void start(Stage stage) {
Rectangle rectangle = new Rectangle(10, 10, Color.ORANGERED.darker().darker());
Image illuminationMap = rectangle.snapshot(null, null);
Sphere sun = new Sphere(
S/10
);
sun.setMaterial(new PhongMaterial(Color.ORANGERED, null, null, null, illuminationMap));
sun.setTranslateX(C);
sun.setTranslateY(C);
PointLight sunsRadiance = new PointLight(Color.LIGHTYELLOW);
sunsRadiance.translateXProperty().bind(sun.translateXProperty());
sunsRadiance.translateYProperty().bind(sun.translateYProperty());
sunsRadiance.translateZProperty().bind(sun.translateZProperty());
Sphere earth = new Sphere(
S/40
);
earth.setMaterial(new PhongMaterial(Color.DARKTURQUOISE));
earth.setTranslateX(C + O);
earth.setTranslateY(C);
// an f(xyz) Torus could be used instead here for higher fidelity.
// https://github.com/FXyz/FXyzLib/blob/master/src/org/fxyz/shapes/Torus.java
Circle orbit = new Circle(C, C, O);
orbit.setFill(null);
orbit.setStroke(Color.GREEN.deriveColor(0, 1, 1, .8));
orbit.setStrokeWidth(4);
Animation orbiter = getPathTransitionOrbiter(orbit, earth);
Group space = new Group(sun, sunsRadiance, orbit, earth);
PerspectiveCamera camera = new PerspectiveCamera();
DirectionalLight cameraLight = new DirectionalLight(Color.GRAY);
cameraLight.translateXProperty().bind(camera.translateXProperty());
cameraLight.translateYProperty().bind(camera.translateYProperty());
cameraLight.translateZProperty().bind(camera.translateZProperty());
Rotate xform = new Rotate(-80, new Point3D(1, 0, 0));
xform.setPivotX(C);
xform.setPivotY(C);
space.getTransforms().add(xform);
Group root = new Group(cameraLight, space);
Scene scene = new Scene(root, S, S, true, SceneAntialiasing.BALANCED);
scene.setCamera(camera);
scene.setFill(INDIA_INK);
stage.setScene(scene);
stage.show();
orbiter.play();
}
private static Transition getPathTransitionOrbiter(Circle orbit, Node satellite) {
PathTransition orbiter = new PathTransition(T, orbit, satellite);
orbiter.setInterpolator(Interpolator.LINEAR);
orbiter.setCycleCount(Animation.INDEFINITE);
return orbiter;
}
public static void main(String[] args) {
launch(args);
}
}
AnimationTimer
A third (more complex) technique (not shown here), would be to use an AnimationTimer, calculating and adjusting the translation properties of the node each time the handle
method in the timer is called to update the scene. This technique is often used in game loops, or in physics models where objects are modeled using velocity vectors that update their positions according to physical attributes like gravity, collisions, and bounces.