Below is a simplified example of what I have so far. It's a 2d array which can display blocks based on row/col in the array data. The user can move around the scene forward, back, up and down, and turn left/right 45 degrees using the arrow keys.
- up arrow = forward
- back arrow = backward
- left arrow = turn left
- right arrow = turn right
- PgUp = look upward
- PgDown = look downward
- Ins = move up
- Del = move down
The place I'm stuck is how can I add transition animations for these movements? Whenever I try everything goes out whack.
Here's the copy/paste code (running JavaFX with Java 17).
Thanks!
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Material;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import java.util.Random;
public class MazeAnimationApp extends Application {
private World world;
private Stage stage;
private Scene scene;
static final Random random = new Random(1);
private final BorderPane mainPane = new BorderPane();
@Override
public void start(Stage stage) throws Exception {
this.stage = stage;
world = new World(getData());
setupScene();
setupStage();
stage.show();
}
private String[][] getData() {
String[][] data = new String[32][32];
for (int j = 0; j < 32; j++) {
for (int k = 0; k < 32; k++) {
if (random.nextInt(32) % 3 == 0 && random.nextInt(32) % 3 == 0) {
data[j][k] = "X";
}
}
}
return data;
}
private void setupScene() {
scene = new Scene(mainPane, 1024, 768);
mainPane.setCenter(world.getScene());
Button btn = new Button("press arrows");
btn.setTranslateX(0);
btn.setTranslateY(0);
mainPane.setBottom(btn);
btn.setOnKeyPressed(event -> {
keyPressed(event.getCode());
});
}
private void setupStage() {
stage.setTitle("Demo of something");
stage.setFullScreenExitHint("");
stage.setWidth(1024);
stage.setHeight(768);
stage.setScene(scene);
}
private void keyPressed(KeyCode keyCode) {
System.out.println("keypressed " + keyCode);
var camera = world.getCamera();
switch (keyCode) {
case KP_UP, NUMPAD8, UP -> {
// forward
camera.addToPos(1);
}
case KP_DOWN, NUMPAD2, DOWN -> {
// back
camera.addToPos(-1);
}
case KP_LEFT, LEFT, NUMPAD4 -> {
// left
camera.addToHAngle(-90);
}
case KP_RIGHT, RIGHT, NUMPAD6 -> {
// right
camera.addToHAngle(90);
}
case PAGE_UP -> {
// look up
camera.addToVAngle(45);
}
case PAGE_DOWN -> {
// look down
camera.addToVAngle(-45);
}
case INSERT -> {
// go up
camera.elevate(-0.5);
}
case DELETE -> {
// go down
camera.elevate(0.5);
}
}
}
public static void main(String[] args) {
launch(args);
}
private static class MazeCamera extends PerspectiveCamera {
private final Translate pos = new Translate();
/**
* Direction on the horizontal plane.
*/
private final Rotate hdir = new Rotate(-180, Rotate.Y_AXIS);
/**
* Direction on the vertical plane.
*/
private final Rotate vdir = new Rotate(0, Rotate.X_AXIS);
public MazeCamera() {
super(true);
setFieldOfView(100);
setVerticalFieldOfView(false);
setNearClip(0.001);
setFarClip(30);
getTransforms().addAll(pos, hdir, vdir);
// y = - up + down
// z = - forward + back
// x = - left + right
}
public void setPos(final double x, final double y, final double z) {
pos.setX(x);
pos.setY(y);
pos.setZ(z);
}
public void setHAngle(final double hangle) {
hdir.setAngle(hangle);
}
public void addToHAngle(final double hdelta) {
hdir.setAngle(hdir.getAngle() + hdelta);
}
public void setVAngle(final double vangle) {
vdir.setAngle(vangle);
}
public void addToVAngle(final double vdelta) {
final double vangle = vdir.getAngle() + vdelta;
if (vangle < -90 || vangle > 90) {
return;
}
vdir.setAngle(vdir.getAngle() + vdelta);
}
/**
* Adds the specified amount to the camera's horizontal position, toward the camera's current horizontal direction.
*
* @param helta horizontal distance to be added to the camera's current horizontal position
*/
public void addToPos(final double helta) {
addToPos(helta, hdir.getAngle());
}
/**
* Adds the specified amount to the camera's horizontal position, toward the specified horizontal direction.
*
* @param hdelta horizontal distance to be added to the camera's current horizontal position
*/
public void addToPos(double hdelta, final double hangle) {
final double rad = Math.toRadians(hangle);
pos.setX(pos.getX() + hdelta * Math.sin(rad));
pos.setZ(pos.getZ() + hdelta * Math.cos(rad));
}
/**
* Elevates the camera: adds the specified vertical delta to its y position.
*
* @param vdelta (vertical) elevation to be added to the camera's current vertical position
*/
public void elevate(final double vdelta) {
pos.setY(pos.getY() + vdelta);
}
public Translate getPos() {
return pos;
}
public Rotate getHDir() {
return hdir;
}
}
private record BlockPos(int row, int col) {
static int maxDistance = 32;
}
private static class World {
private final Group root = new Group();
private final SubScene scene = new SubScene(root, 800, 600, true, SceneAntialiasing.BALANCED);
private final MazeCamera camera = new MazeCamera();
private final PointLight pointLight = new PointLight(Color.gray(0.3));
private final AmbientLight ambientLight = new AmbientLight(Color.ANTIQUEWHITE);
private final Material material1 = new PhongMaterial(Color.RED, null, null, null, null);
private final Material material2 = new PhongMaterial(Color.GREEN, null, null, null, null);
private final Material material3 = new PhongMaterial(Color.BLUE, null, null, null, null);
private final String[][] data;
public World(String[][] data) {
this.data = data;
initScene();
}
private void initScene() {
root.getChildren().clear();
pointLight.getTransforms().clear();
camera.setVAngle(0);
camera.setHAngle(0);
root.getChildren().add(camera);
root.getChildren().add(ambientLight);
root.getChildren().add(pointLight);
scene.setCamera(camera);
scene.setFill(Color.LIGHTBLUE);
int row = 5;
int col = 5;
camera.setPos(col + 0.5, 0, BlockPos.maxDistance - row + 0.5);
for (int r = 0; r < BlockPos.maxDistance; r++) {
for (int c = 0; c < BlockPos.maxDistance; c++) {
if ("X".equalsIgnoreCase(data[r][c])) {
BlockPos pos = new BlockPos(r, c);
root.getChildren().add(createBlock(pos));
}
}
}
}
private Node createBlock(BlockPos pos) {
Box box = new Box(1, 1, 1);
Material material = null;
int r = random.nextInt(3);
switch (r) {
case 0 -> material = material1;
case 1 -> material = material2;
case 2 -> material = material3;
}
box.setMaterial(material);
box.setTranslateX(pos.col() + 0.5);
box.setTranslateZ((BlockPos.maxDistance - pos.row()) + 0.5);
return box;
}
public SubScene getScene() {
return scene;
}
public MazeCamera getCamera() {
return camera;
}
}
}