|
| 1 | +package engine.components; |
| 2 | + |
| 3 | +import engine.input.Input; |
| 4 | +import engine.input.Key; |
| 5 | +import engine.scene.camera.Camera; |
| 6 | +import engine.scene.camera.PerspectiveCamera; |
| 7 | +import math.Mathf; |
| 8 | +import math.Vector3f; |
| 9 | + |
| 10 | +/** |
| 11 | + * The {@code FlyByCameraControl} class provides first-person camera control functionality, allowing |
| 12 | + * the user to move the camera in all directions and adjust its orientation using mouse and keyboard |
| 13 | + * input. This control simulates a "fly-by" movement, typically used in 3D games or applications |
| 14 | + * that require free-form camera navigation. |
| 15 | + * |
| 16 | + * <p>The movement is controlled by the following keys: |
| 17 | + * |
| 18 | + * <ul> |
| 19 | + * <li>W: Move forward |
| 20 | + * <li>S: Move backward |
| 21 | + * <li>A: Move left (strafe) |
| 22 | + * <li>D: Move right (strafe) |
| 23 | + * <li>SPACE: Move up |
| 24 | + * <li>SHIFT: Move down |
| 25 | + * </ul> |
| 26 | + * |
| 27 | + * <p>The mouse controls the camera's yaw (left-right) and pitch (up-down) based on the mouse |
| 28 | + * movement, with vertical angle limits set for pitch to prevent excessive rotations. |
| 29 | + */ |
| 30 | +public class FlyByCameraControl extends AbstractComponent { |
| 31 | + |
| 32 | + private static final float DEFAULT_MOUSE_SENSITIVITY = 10f; |
| 33 | + |
| 34 | + private static final float DEFAULT_MOVE_SPEED = 30f; |
| 35 | + |
| 36 | + private static final float MAX_VERTICAL_ANGLE = 80f; |
| 37 | + |
| 38 | + private static final float MIN_VERTICAL_ANGLE = -80f; |
| 39 | + |
| 40 | + private final Vector3f forward = new Vector3f(); |
| 41 | + |
| 42 | + private final Vector3f target = new Vector3f(); |
| 43 | + |
| 44 | + private float mouseSensitivity = DEFAULT_MOUSE_SENSITIVITY; |
| 45 | + |
| 46 | + private float moveSpeed = DEFAULT_MOVE_SPEED; |
| 47 | + |
| 48 | + private Input input; |
| 49 | + |
| 50 | + private Camera camera; |
| 51 | + |
| 52 | + /** |
| 53 | + * Constructs a new {@code FlyByCameraControl} with the specified input and camera. |
| 54 | + * |
| 55 | + * @param input The {@link Input} instance to capture user input. |
| 56 | + * @param camera The {@link PerspectiveCamera} to control. |
| 57 | + * @throws IllegalArgumentException if either {@code input} or {@code camera} is {@code null}. |
| 58 | + */ |
| 59 | + public FlyByCameraControl(Input input, PerspectiveCamera camera) { |
| 60 | + if (input == null || camera == null) { |
| 61 | + throw new IllegalArgumentException("Input and Camera cannot be null."); |
| 62 | + } |
| 63 | + this.input = input; |
| 64 | + this.camera = camera; |
| 65 | + } |
| 66 | + |
| 67 | + /** |
| 68 | + * Updates the camera control state, handling mouse input for camera rotation and keyboard input |
| 69 | + * for movement. |
| 70 | + * |
| 71 | + * <p>This method is typically called once per frame. |
| 72 | + * |
| 73 | + * @param tpf The time per frame (delta time) used for movement scaling. |
| 74 | + */ |
| 75 | + @Override |
| 76 | + public void update(float tpf) { |
| 77 | + float mouseX = input.getMouseDeltaX() * mouseSensitivity * tpf; |
| 78 | + float mouseY = input.getMouseDeltaY() * mouseSensitivity * tpf; |
| 79 | + |
| 80 | + handleRotation(mouseX, mouseY); |
| 81 | + updateTarget(); |
| 82 | + |
| 83 | + Vector3f velocity = calculateVelocity(); |
| 84 | + if (velocity.length() > 0) { |
| 85 | + applyMovement(velocity, tpf); |
| 86 | + } |
| 87 | + |
| 88 | + input.center(); |
| 89 | + } |
| 90 | + |
| 91 | + /** |
| 92 | + * Handles camera rotation based on mouse input, applying yaw and pitch to the camera's transform. |
| 93 | + * The vertical rotation (pitch) is clamped within defined limits to prevent excessive rotation. |
| 94 | + * |
| 95 | + * @param mouseX The mouse movement on the X-axis (horizontal movement). |
| 96 | + * @param mouseY The mouse movement on the Y-axis (vertical movement). |
| 97 | + */ |
| 98 | + private void handleRotation(float mouseX, float mouseY) { |
| 99 | + // Handle yaw (left-right rotation) |
| 100 | + float yaw = mouseX; |
| 101 | + yaw = Mathf.clamp(yaw, -180f, 180f); |
| 102 | + camera.getTransform().rotate(0, Mathf.toRadians(yaw), 0); |
| 103 | + |
| 104 | + // Handle pitch (up-down rotation) |
| 105 | + Vector3f rotation = camera.getTransform().getRotation(); |
| 106 | + float currentPitch = rotation.x; |
| 107 | + currentPitch += Mathf.toRadians(mouseY); |
| 108 | + currentPitch = |
| 109 | + Mathf.clamp( |
| 110 | + currentPitch, Mathf.toRadians(MIN_VERTICAL_ANGLE), Mathf.toRadians(MAX_VERTICAL_ANGLE)); |
| 111 | + |
| 112 | + rotation.x = currentPitch; |
| 113 | + camera.getTransform().setRotation(rotation); |
| 114 | + } |
| 115 | + |
| 116 | + /** |
| 117 | + * Calculates the movement velocity vector based on the current keyboard input. The velocity is a |
| 118 | + * vector that indicates the desired direction of movement. |
| 119 | + * |
| 120 | + * @return A {@link Vector3f} representing the movement velocity. |
| 121 | + */ |
| 122 | + private Vector3f calculateVelocity() { |
| 123 | + Vector3f velocity = new Vector3f(); |
| 124 | + Vector3f forward = camera.getTransform().getForward(); |
| 125 | + Vector3f right = camera.getTransform().getRight(); |
| 126 | + |
| 127 | + // Movement controls (WASD, Space, Shift) |
| 128 | + if (input.isKeyPressed(Key.W)) velocity.addLocal(forward); |
| 129 | + if (input.isKeyPressed(Key.S)) velocity.addLocal(forward.negate()); |
| 130 | + if (input.isKeyPressed(Key.A)) velocity.addLocal(right.negate()); |
| 131 | + if (input.isKeyPressed(Key.D)) velocity.addLocal(right); |
| 132 | + if (input.isKeyPressed(Key.SPACE)) velocity.addLocal(0, -1, 0); |
| 133 | + if (input.isKeyPressed(Key.SHIFT)) velocity.addLocal(0, 1, 0); |
| 134 | + |
| 135 | + return velocity; |
| 136 | + } |
| 137 | + |
| 138 | + /** |
| 139 | + * Applies the calculated velocity to the camera's position, updating the camera's location based |
| 140 | + * on user movement input. |
| 141 | + * |
| 142 | + * @param velocity The movement velocity to apply. |
| 143 | + * @param tpf The time per frame (delta time) to scale the movement. |
| 144 | + */ |
| 145 | + private void applyMovement(Vector3f velocity, float tpf) { |
| 146 | + velocity.normalizeLocal(); |
| 147 | + Vector3f position = camera.getTransform().getPosition(); |
| 148 | + position.addLocal(velocity.mult(moveSpeed * tpf)); |
| 149 | + camera.getTransform().setPosition(position); |
| 150 | + updateTarget(); |
| 151 | + } |
| 152 | + |
| 153 | + /** |
| 154 | + * Updates the target position for the camera. The target is a point in the scene that the camera |
| 155 | + * looks at, and it is updated based on the camera's current position and rotation. |
| 156 | + */ |
| 157 | + private void updateTarget() { |
| 158 | + Vector3f position = camera.getTransform().getPosition(); |
| 159 | + Vector3f rotation = camera.getTransform().getRotation(); |
| 160 | + |
| 161 | + forward |
| 162 | + .set( |
| 163 | + Mathf.cos(rotation.y) * Mathf.cos(rotation.x), |
| 164 | + Mathf.sin(rotation.x), |
| 165 | + Mathf.sin(rotation.y) * Mathf.cos(rotation.x)) |
| 166 | + .normalizeLocal(); |
| 167 | + |
| 168 | + target.set(position).addLocal(forward); |
| 169 | + camera.setTarget(target); |
| 170 | + } |
| 171 | + |
| 172 | + /** This method is called when the component is attached to an entity. Currently not used. */ |
| 173 | + @Override |
| 174 | + public void onAttach() { |
| 175 | + // Not used yet |
| 176 | + } |
| 177 | + |
| 178 | + /** This method is called when the component is detached from an entity. Currently not used. */ |
| 179 | + @Override |
| 180 | + public void onDetach() { |
| 181 | + // Not used yet |
| 182 | + } |
| 183 | +} |
0 commit comments