Issue
I am trying to create a first person camera in JavaFX based on bindings. The camera and the actual position both work perfectly. The only problem is that they don’t match! As you can see in the picture, the actual position (red box) is in the middle of the circle, but the camera is outside. How can I change that? What did I do wrong?
The Player class handles the PerspectiveCamera.
package game;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.PerspectiveCamera;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.robot.Robot;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class Player extends Character {
private static final Robot ROBOT = new Robot();
private DoubleProperty relativeCenterX = new SimpleDoubleProperty();
private DoubleProperty relativeCenterY = new SimpleDoubleProperty();
protected PerspectiveCamera camera = new PerspectiveCamera();
protected Rotate xAxis = new Rotate(0, 250, 0, 0, Rotate.Y_AXIS);
protected Rotate yAxis = new Rotate(0, 0, 250, 0, Rotate.X_AXIS);
protected Translate translate = new Translate();
protected DoubleProperty centerX = new SimpleDoubleProperty();
protected DoubleProperty centerY = new SimpleDoubleProperty();
@SuppressWarnings("exports")
public Player(Stage stage) {
camera.getTransforms().addAll(xAxis, yAxis);
centerX.bind(stage.widthProperty().divide(2));
centerY.bind(stage.heightProperty().divide(2));
relativeCenterX.bind(stage.xProperty().add(centerX));
relativeCenterY.bind(stage.yProperty().add(centerY));
camera.translateXProperty().bind(posX.subtract(centerX));
camera.translateYProperty().bind(posZ);
camera.translateZProperty().bind(posY.subtract(centerY));
xAxis.angleProperty().bind(viewX.subtract(90));
yAxis.angleProperty().bind(viewY);
translate.xProperty().bind(posX);
translate.zProperty().bind(posY);
translate.yProperty().bind(posZ);
}
@SuppressWarnings("exports")
public EventHandler<KeyEvent> getKeyHandle() {
return e -> {
switch (e.getCode()) {
case A:
view(-1, 0);
break;
case D:
view(1, 0);
break;
case W:
move(1, 1, 0);
break;
case S:
move(-1, -1, 0);
break;
case SPACE:
move(0, 0, 10);
break;
case F:
move(0, 0, -10);
break;
default:
break;
}
};
}
@SuppressWarnings("exports")
public EventHandler<MouseEvent> getMouseHandle() {
return e -> {
view(e.getSceneX() - centerX.doubleValue(), centerY.doubleValue() - e.getSceneY());
Platform.runLater(() -> {
ROBOT.mouseMove(relativeCenterX.intValue(), relativeCenterY.intValue());
});
};
}
@SuppressWarnings("exports")
public PerspectiveCamera getPespectiveCamera() {
return camera;
}
}
The Character class calculates position and view.
package game;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public abstract class Character {
protected DoubleProperty posX = new SimpleDoubleProperty();
protected DoubleProperty posY = new SimpleDoubleProperty();
protected DoubleProperty posZ = new SimpleDoubleProperty();
protected DoubleProperty viewX = new SimpleDoubleProperty();
protected DoubleProperty viewY = new SimpleDoubleProperty();
protected DoubleProperty speed = new SimpleDoubleProperty(10);
public void move(double x, double y, double z) {
double fX = Math.cos(Math.toRadians(viewX.get()));
double fY = -Math.sin(Math.toRadians(viewX.get()));
double fZ = 1;
posX.set(posX.get() + fX * x * speed.get());
posY.set(posY.get() + fY * y * speed.get());
posZ.set(posZ.get() + fZ * z);
}
public void view(double x, double y) {
viewX.set(viewX.get() + x);
viewY.set(viewY.get() + y);
}
@SuppressWarnings("exports")
public DoubleProperty posXPorperty() {
return posX;
}
@SuppressWarnings("exports")
public DoubleProperty posYPorperty() {
return posY;
}
@SuppressWarnings("exports")
public DoubleProperty posZPorperty() {
return posZ;
}
@SuppressWarnings("exports")
public DoubleProperty viewXPorperty() {
return viewX;
}
@SuppressWarnings("exports")
public DoubleProperty viewYPorperty() {
return viewY;
}
}
My Application, which shows the total graphical content.
package graphics;
import game.Player;
import javafx.application.Application;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class GameStage extends Application implements Runnable {
@Override
public void run() {
launch();
}
@SuppressWarnings("exports")
@Override
public void start(Stage stage) throws Exception {
BorderPane pane = new BorderPane();
Scene scene = new Scene(pane, 500, 500);
Group content = new Group(), map = new Group();
ContentScene subscene = new ContentScene(content, map, 500, 500);
subscene.widthProperty().bind(scene.widthProperty());
subscene.heightProperty().bind(scene.heightProperty());
pane.getChildren().add(subscene);
pane.setBottom(map);
Player player = new Player(stage);
Box box = new Box(50, 50, 50);
box.translateXProperty().bind(player.posXPorperty());
box.translateYProperty().bind(player.posZPorperty());
box.translateZProperty().bind(player.posYPorperty());
box.rotateProperty().bind(player.viewXPorperty());
box.setMaterial(new PhongMaterial(Color.RED));
content.getChildren().add(box);
Rectangle rectangle = new Rectangle(5, 5);
rectangle.translateXProperty().bind(player.posXPorperty().divide(10));
rectangle.translateYProperty().bind(player.posYPorperty().divide(10));
rectangle.setFill(Color.RED);
map.getChildren().add(rectangle);
subscene.setCamera(player.getPespectiveCamera());
scene.addEventHandler(KeyEvent.KEY_PRESSED, player.getKeyHandle());
scene.addEventHandler(MouseEvent.MOUSE_MOVED, player.getMouseHandle());
scene.setFill(Color.BLACK);
Cursor cursor = Cursor.CROSSHAIR;
scene.setCursor(cursor);
stage.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() != KeyCode.F11) {
return;
}
if (stage.isFullScreen()) {
stage.setFullScreen(false);
} else {
stage.setFullScreen(true);
}
});
stage.setAlwaysOnTop(true);
stage.setScene(scene);
stage.show();
}
private class ContentScene extends SubScene {
public ContentScene(Group content, Group map, double width, double height) {
super(content, width, height, true, SceneAntialiasing.BALANCED);
PhongMaterial material = new PhongMaterial(Color.AQUA);
for (int v = 0; v < 3_600; v += 180) {
for (int y = 0; y < 500; y += 100) {
Box box = new Box(50, 50, 50);
box.setTranslateX(Math.sin(v / 10) * 1_000);
box.setTranslateY(y);
box.setTranslateZ(Math.cos(v / 10) * 1_000);
box.setMaterial(material);
content.getChildren().add(box);
Rectangle rectangle = new Rectangle(5, 5);
rectangle.translateXProperty().bind(box.translateXProperty().divide(10));
rectangle.translateYProperty().bind(box.translateZProperty().divide(10));
rectangle.setFill(Color.AQUA);
map.getChildren().add(rectangle);
}
}
}
}
}
Solution
Thanks to Thomas, I was able to solve the problem. The code now loks like this:
package graphics;
import game.Player;
import javafx.application.Application;
import javafx.scene.AmbientLight;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class GameStage extends Application implements Runnable {
@Override
public void run() {
launch();
}
@SuppressWarnings("exports")
@Override
public void start(Stage stage) throws Exception {
// Parents
BorderPane pane = new BorderPane();
Group content = new Group(new AmbientLight()), map = new Group();
Pane stackpane = new Pane(map);
// Scenes
Scene scene = new Scene(pane, 500, 500);
SubScene contentSubscene = new SubScene(content, 500, 500, true, SceneAntialiasing.BALANCED);
contentSubscene.widthProperty().bind(scene.widthProperty());
contentSubscene.heightProperty().bind(scene.heightProperty());
SubScene minimapSubscene = new SubScene(stackpane, 256, 256);
minimapSubscene.setFill(Color.DARKGREY);
pane.getChildren().add(contentSubscene);
pane.setBottom(minimapSubscene);
// Create Player
Player player = new Player(stage);
Rectangle currentPosition = new Rectangle(5, 5);
currentPosition.layoutXProperty().bind(minimapSubscene.widthProperty().divide(2));
currentPosition.layoutYProperty().bind(minimapSubscene.heightProperty().divide(2));
currentPosition.setFill(Color.RED);
stackpane.getChildren().add(currentPosition);
map.layoutXProperty().bind(player.posXPorperty().divide(-10).add(minimapSubscene.widthProperty().divide(2)));
map.layoutYProperty().bind(player.posYPorperty().divide(-10).add(minimapSubscene.heightProperty().divide(2)));
// Create Box in
PhongMaterial material = new PhongMaterial(Color.AQUA);
for (int v = 0; v < 3_600; v += 180) {
for (int y = 0; y < 500; y += 100) {
Box box = new Box(50, 50, 50);
box.setTranslateX(Math.sin(v / 10) * 1_000);
box.setTranslateY(y);
box.setTranslateZ(Math.cos(v / 10) * 1_000);
box.setMaterial(material);
content.getChildren().add(box);
Rectangle boxPosition = new Rectangle(5, 5);
boxPosition.translateXProperty().bind(box.translateXProperty().divide(10));
boxPosition.translateYProperty().bind(box.translateZProperty().divide(10));
boxPosition.setFill(Color.AQUA);
map.getChildren().add(boxPosition);
}
}
contentSubscene.setCamera(player.getPespectiveCamera());
scene.addEventHandler(KeyEvent.KEY_PRESSED, player.getKeyHandle());
scene.addEventHandler(MouseEvent.MOUSE_MOVED, player.getMouseHandle());
scene.setCursor(Cursor.CROSSHAIR);
scene.setFill(Color.WHITE);
stage.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() != KeyCode.F11) {
return;
}
if (stage.isFullScreen()) {
stage.setFullScreen(false);
} else {
stage.setFullScreen(true);
}
});
stage.setAlwaysOnTop(true);
stage.setScene(scene);
stage.show();
}
}
package game;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
public abstract class Character {
protected DoubleProperty posX = new SimpleDoubleProperty();
protected DoubleProperty posY = new SimpleDoubleProperty();
protected DoubleProperty posZ = new SimpleDoubleProperty();
protected DoubleProperty viewX = new SimpleDoubleProperty();
protected DoubleProperty viewY = new SimpleDoubleProperty();
protected DoubleProperty speed = new SimpleDoubleProperty(10);
public void move(double x, double y, double z) {
double fX = Math.cos(Math.toRadians(viewX.get()));
double fY = -Math.sin(Math.toRadians(viewX.get()));
double fZ = 1;
posX.set(posX.get() + fX * x * speed.get());
posY.set(posY.get() + fY * y * speed.get());
posZ.set(posZ.get() + fZ * z);
}
public void view(double x, double y) {
viewX.set(viewX.get() + x);
viewY.set(viewY.get() + y);
}
@SuppressWarnings("exports")
public DoubleProperty posXPorperty() {
return posX;
}
@SuppressWarnings("exports")
public DoubleProperty posYPorperty() {
return posY;
}
@SuppressWarnings("exports")
public DoubleProperty posZPorperty() {
return posZ;
}
@SuppressWarnings("exports")
public DoubleProperty viewXPorperty() {
return viewX;
}
@SuppressWarnings("exports")
public DoubleProperty viewYPorperty() {
return viewY;
}
}
package game;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.PerspectiveCamera;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.robot.Robot;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class Player extends Character {
private static final Robot ROBOT = new Robot();
private DoubleProperty relativeCenterX = new SimpleDoubleProperty();
private DoubleProperty relativeCenterY = new SimpleDoubleProperty();
protected PerspectiveCamera camera = new PerspectiveCamera(true);
protected Rotate xAxis = new Rotate(0, 250, 0, 0, Rotate.Y_AXIS);
protected Rotate yAxis = new Rotate(0, 0, 250, 0, Rotate.X_AXIS);
protected DoubleProperty centerX = new SimpleDoubleProperty();
protected DoubleProperty centerY = new SimpleDoubleProperty();
@SuppressWarnings("exports")
public Player(Stage stage) {
camera.getTransforms().addAll(xAxis, yAxis);
camera.setFieldOfView((40 + 62) / 2);
camera.setNearClip(0.1);
camera.setFarClip(100000);
camera.setVerticalFieldOfView(true);
centerX.bind(stage.widthProperty().divide(2));
centerY.bind(stage.heightProperty().divide(2));
relativeCenterX.bind(stage.xProperty().add(centerX));
relativeCenterY.bind(stage.yProperty().add(centerY));
xAxis.angleProperty().bind(viewX.subtract(90));
yAxis.angleProperty().bind(viewY);
camera.translateXProperty().bind(posX);
camera.translateZProperty().bind(posY);
camera.translateYProperty().bind(posZ);
}
@SuppressWarnings("exports")
public EventHandler<KeyEvent> getKeyHandle() {
return e -> {
switch (e.getCode()) {
case A:
view(-1, 0);
break;
case D:
view(1, 0);
break;
case W:
move(1, 1, 0);
break;
case S:
move(-1, -1, 0);
break;
case SPACE:
move(0, 0, 10);
break;
case F:
move(0, 0, -10);
break;
default:
break;
}
};
}
@SuppressWarnings("exports")
public EventHandler<MouseEvent> getMouseHandle() {
return e -> {
view(e.getSceneX() - centerX.doubleValue(), centerY.doubleValue() - e.getSceneY());
Platform.runLater(() -> {
ROBOT.mouseMove(relativeCenterX.intValue(), relativeCenterY.intValue());
});
};
}
@SuppressWarnings("exports")
public PerspectiveCamera getPespectiveCamera() {
return camera;
}
}
Answered By - Darth Bane
Answer Checked By - Dawn Plyler (JavaFixing Volunteer)