Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions src/main/java/engine/components/FlyByCameraControl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package engine.components;

import engine.input.Input;
import engine.input.Key;
import engine.scene.camera.Camera;
import engine.scene.camera.PerspectiveCamera;
import math.Mathf;
import math.Vector3f;

/**
* The {@code FlyByCameraControl} class provides first-person camera control functionality, allowing
* the user to move the camera in all directions and adjust its orientation using mouse and keyboard
* input. This control simulates a "fly-by" movement, typically used in 3D games or applications
* that require free-form camera navigation.
*
* <p>The movement is controlled by the following keys:
*
* <ul>
* <li>W: Move forward
* <li>S: Move backward
* <li>A: Move left (strafe)
* <li>D: Move right (strafe)
* <li>SPACE: Move up
* <li>SHIFT: Move down
* </ul>
*
* <p>The mouse controls the camera's yaw (left-right) and pitch (up-down) based on the mouse
* movement, with vertical angle limits set for pitch to prevent excessive rotations.
*/
public class FlyByCameraControl extends AbstractComponent {

private static final float DEFAULT_MOUSE_SENSITIVITY = 10f;

private static final float DEFAULT_MOVE_SPEED = 30f;

private static final float MAX_VERTICAL_ANGLE = 80f;

private static final float MIN_VERTICAL_ANGLE = -80f;

private final Vector3f forward = new Vector3f();

private final Vector3f target = new Vector3f();

private float mouseSensitivity = DEFAULT_MOUSE_SENSITIVITY;

private float moveSpeed = DEFAULT_MOVE_SPEED;

private Input input;

private Camera camera;

/**
* Constructs a new {@code FlyByCameraControl} with the specified input and camera.
*
* @param input The {@link Input} instance to capture user input.
* @param camera The {@link PerspectiveCamera} to control.
* @throws IllegalArgumentException if either {@code input} or {@code camera} is {@code null}.
*/
public FlyByCameraControl(Input input, PerspectiveCamera camera) {
if (input == null || camera == null) {
throw new IllegalArgumentException("Input and Camera cannot be null.");
}
this.input = input;
this.camera = camera;
}

/**
* Updates the camera control state, handling mouse input for camera rotation and keyboard input
* for movement.
*
* <p>This method is typically called once per frame.
*
* @param tpf The time per frame (delta time) used for movement scaling.
*/
@Override
public void update(float tpf) {
float mouseX = input.getMouseDeltaX() * mouseSensitivity * tpf;
float mouseY = input.getMouseDeltaY() * mouseSensitivity * tpf;

handleRotation(mouseX, mouseY);
updateTarget();

Vector3f velocity = calculateVelocity();
if (velocity.length() > 0) {
applyMovement(velocity, tpf);
}

input.center();
}

/**
* Handles camera rotation based on mouse input, applying yaw and pitch to the camera's transform.
* The vertical rotation (pitch) is clamped within defined limits to prevent excessive rotation.
*
* @param mouseX The mouse movement on the X-axis (horizontal movement).
* @param mouseY The mouse movement on the Y-axis (vertical movement).
*/
private void handleRotation(float mouseX, float mouseY) {
// Handle yaw (left-right rotation)
float yaw = mouseX;
yaw = Mathf.clamp(yaw, -180f, 180f);
camera.getTransform().rotate(0, Mathf.toRadians(yaw), 0);

// Handle pitch (up-down rotation)
Vector3f rotation = camera.getTransform().getRotation();
float currentPitch = rotation.x;
currentPitch += Mathf.toRadians(mouseY);
currentPitch =
Mathf.clamp(
currentPitch, Mathf.toRadians(MIN_VERTICAL_ANGLE), Mathf.toRadians(MAX_VERTICAL_ANGLE));

rotation.x = currentPitch;
camera.getTransform().setRotation(rotation);
}

/**
* Calculates the movement velocity vector based on the current keyboard input. The velocity is a
* vector that indicates the desired direction of movement.
*
* @return A {@link Vector3f} representing the movement velocity.
*/
private Vector3f calculateVelocity() {
Vector3f velocity = new Vector3f();
Vector3f forward = camera.getTransform().getForward();
Vector3f right = camera.getTransform().getRight();

// Movement controls (WASD, Space, Shift)
if (input.isKeyPressed(Key.W)) velocity.addLocal(forward);
if (input.isKeyPressed(Key.S)) velocity.addLocal(forward.negate());
if (input.isKeyPressed(Key.A)) velocity.addLocal(right.negate());
if (input.isKeyPressed(Key.D)) velocity.addLocal(right);
if (input.isKeyPressed(Key.SPACE)) velocity.addLocal(0, -1, 0);
if (input.isKeyPressed(Key.SHIFT)) velocity.addLocal(0, 1, 0);

return velocity;
}

/**
* Applies the calculated velocity to the camera's position, updating the camera's location based
* on user movement input.
*
* @param velocity The movement velocity to apply.
* @param tpf The time per frame (delta time) to scale the movement.
*/
private void applyMovement(Vector3f velocity, float tpf) {
velocity.normalizeLocal();
Vector3f position = camera.getTransform().getPosition();
position.addLocal(velocity.mult(moveSpeed * tpf));
camera.getTransform().setPosition(position);
updateTarget();
}

/**
* Updates the target position for the camera. The target is a point in the scene that the camera
* looks at, and it is updated based on the camera's current position and rotation.
*/
private void updateTarget() {
Vector3f position = camera.getTransform().getPosition();
Vector3f rotation = camera.getTransform().getRotation();

forward
.set(
Mathf.cos(rotation.y) * Mathf.cos(rotation.x),
Mathf.sin(rotation.x),
Mathf.sin(rotation.y) * Mathf.cos(rotation.x))
.normalizeLocal();

target.set(position).addLocal(forward);
camera.setTarget(target);
}

/** This method is called when the component is attached to an entity. Currently not used. */
@Override
public void onAttach() {
// Not used yet
}

/** This method is called when the component is detached from an entity. Currently not used. */
@Override
public void onDetach() {
// Not used yet
}
}
7 changes: 5 additions & 2 deletions src/main/java/engine/debug/DebugInfoUpdater.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import engine.scene.Scene;
import engine.scene.camera.Camera;
import math.Mathf;
import workspace.GraphicsPImpl;

/**
* The {@code DebugInfoUpdater} class is responsible for updating debug information displayed by a
Expand Down Expand Up @@ -101,8 +102,8 @@ private void updateCameraInfo(Camera camera) {
if (camera == null) return;
setInfo(CATEGORY_CAMERA, "Aspect", camera.getAspectRatio());
setInfo(CATEGORY_CAMERA, "FOV", Mathf.toDegrees(camera.getFieldOfView()));
setInfo(CATEGORY_CAMERA, "Near", Mathf.toDegrees(camera.getNearPlane()));
setInfo(CATEGORY_CAMERA, "Far", Mathf.toDegrees(camera.getFarPlane()));
setInfo(CATEGORY_CAMERA, "Near", camera.getNearPlane());
setInfo(CATEGORY_CAMERA, "Far", camera.getFarPlane());
}

private void updateOsMetrics() {
Expand All @@ -122,6 +123,8 @@ private void updateSceneMetrics(Scene activeScene) {
setInfo(CATEGORY_SCENE, "Root count", activeScene.getRootCount());
setInfo(CATEGORY_SCENE, "Lights count", activeScene.getLightCount());
setInfo(CATEGORY_SCENE, "Wireframe mode", activeScene.isWireframeMode());
setInfo(CATEGORY_SCENE, "Faces", GraphicsPImpl.faceCount);
setInfo(CATEGORY_SCENE, "Vertices", GraphicsPImpl.vertexCount);
}

private void updateTimeMetrics(Timer timer) {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/engine/input/MouseInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ public interface MouseInput {
float getMouseDeltaY();

void updateMouseState(); // To track mouse-specific states

void center();
}
2 changes: 2 additions & 0 deletions src/main/java/engine/processing/ProcessingApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import engine.input.Input;
import engine.input.KeyInput;
import engine.input.MouseInput;
import engine.resources.ResourceManager;
import processing.core.PApplet;
import workspace.GraphicsPImpl;
import workspace.ui.Graphics;
Expand All @@ -29,6 +30,7 @@ public void settings() {
@Override
public void setup() {
Graphics g = new GraphicsPImpl(this);
ResourceManager.getInstance().setImageLoader(new ProcessingImageLoader(this));
container.setGraphics(g);
getSurface().setTitle(settings.getTitle());
setupInput();
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/engine/processing/ProcessingInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ public void updateMouseState() {
mouseInput.updateMouseState();
}

@Override
public void center() {
mouseInput.center();
}

@Override
public void update() {
updateKeyState();
Expand Down
19 changes: 17 additions & 2 deletions src/main/java/engine/processing/ProcessingMouseInput.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package engine.processing;

import java.awt.Robot;

import engine.input.MouseInput;
import processing.core.PApplet;

Expand All @@ -9,9 +11,15 @@ public class ProcessingMouseInput implements MouseInput {

private float mouseWheelDelta = 0;

private Robot robot;

public ProcessingMouseInput(PApplet applet) {
this.applet = applet;
applet.registerMethod("mouseEvent", this);
try {
robot = new Robot();
} catch (Throwable e) {
}
}

public void mouseEvent(processing.event.MouseEvent event) {
Expand Down Expand Up @@ -79,7 +87,14 @@ public float getMouseWheelDelta() {
}

@Override
public void updateMouseState() {
// Handle frame-specific mouse state updates if necessary
public void updateMouseState() {}

@Override
public void center() {
applet.mouseX = applet.width / 2;
applet.mouseY = applet.height / 2;
applet.pmouseX = applet.width / 2;
applet.pmouseY = applet.height / 2;
robot.mouseMove((int) getScreenWidth() / 2, (int) getScreenHeight() / 2);
}
}
Loading
Loading