|
7 | 7 | import math.Mathf; |
8 | 8 | import math.Vector3f; |
9 | 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 | + */ |
10 | 30 | public class FlyByCameraControl extends AbstractComponent { |
11 | 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 | + |
12 | 40 | private final Vector3f forward = new Vector3f(); |
13 | 41 |
|
14 | 42 | private final Vector3f target = new Vector3f(); |
15 | 43 |
|
16 | | - private float mouseSensitivity = 10f; |
| 44 | + private float mouseSensitivity = DEFAULT_MOUSE_SENSITIVITY; |
17 | 45 |
|
18 | | - private float moveSpeed = 30f; |
| 46 | + private float moveSpeed = DEFAULT_MOVE_SPEED; |
19 | 47 |
|
20 | 48 | private Input input; |
21 | 49 |
|
22 | 50 | private Camera camera; |
23 | 51 |
|
24 | | - // Additional parameters for controlling vertical look limits |
25 | | - private float maxVerticalAngle = 80f; // Max pitch angle in degrees |
26 | | - |
27 | | - private float minVerticalAngle = -80f; // Min pitch angle in degrees |
28 | | - |
| 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 | + */ |
29 | 59 | public FlyByCameraControl(Input input, PerspectiveCamera camera) { |
30 | | - if (input == null) { |
31 | | - throw new IllegalArgumentException("Input cannot be null."); |
32 | | - } |
33 | | - if (camera == null) { |
34 | | - throw new IllegalArgumentException("Camera cannot be null."); |
| 60 | + if (input == null || camera == null) { |
| 61 | + throw new IllegalArgumentException("Input and Camera cannot be null."); |
35 | 62 | } |
36 | 63 | this.input = input; |
37 | 64 | this.camera = camera; |
38 | 65 | } |
39 | 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 | + */ |
40 | 75 | @Override |
41 | 76 | public void update(float tpf) { |
42 | 77 | float mouseX = input.getMouseDeltaX() * mouseSensitivity * tpf; |
43 | 78 | float mouseY = input.getMouseDeltaY() * mouseSensitivity * tpf; |
44 | 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) |
45 | 100 | float yaw = mouseX; |
46 | | - // To avoid issues with yaw wrapping at 360 or -360 degrees, we can clamp yaw between -180 and |
47 | | - // 180 |
48 | 101 | yaw = Mathf.clamp(yaw, -180f, 180f); |
49 | | - |
50 | 102 | camera.getTransform().rotate(0, Mathf.toRadians(yaw), 0); |
51 | 103 |
|
52 | | - updateTarget(); |
53 | | - |
| 104 | + // Handle pitch (up-down rotation) |
54 | 105 | Vector3f rotation = camera.getTransform().getRotation(); |
55 | | - float currentPitch = rotation.x; // Current pitch around the X-axis |
56 | | - currentPitch += Mathf.toRadians(mouseY); // Adjust based on mouse movement |
57 | | - |
58 | | - // Clamp the vertical rotation (pitch) to avoid excessive rotation |
| 106 | + float currentPitch = rotation.x; |
| 107 | + currentPitch += Mathf.toRadians(mouseY); |
59 | 108 | currentPitch = |
60 | 109 | Mathf.clamp( |
61 | | - currentPitch, Mathf.toRadians(minVerticalAngle), Mathf.toRadians(maxVerticalAngle)); |
| 110 | + currentPitch, Mathf.toRadians(MIN_VERTICAL_ANGLE), Mathf.toRadians(MAX_VERTICAL_ANGLE)); |
62 | 111 |
|
63 | 112 | rotation.x = currentPitch; |
64 | 113 | camera.getTransform().setRotation(rotation); |
65 | | - |
66 | | - // Update camera's target based on position and rotation |
67 | | - updateTarget(); |
68 | | - |
69 | | - Vector3f velocity = calculateVelocity(); |
70 | | - |
71 | | - if (velocity.length() > 0) { |
72 | | - velocity.normalizeLocal(); |
73 | | - Vector3f position = camera.getTransform().getPosition(); |
74 | | - position.addLocal(velocity.mult(moveSpeed * tpf)); |
75 | | - camera.getTransform().setPosition(position); |
76 | | - updateTarget(); |
77 | | - } |
78 | | - |
79 | | - input.center(); |
80 | 114 | } |
81 | 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 | + */ |
82 | 122 | private Vector3f calculateVelocity() { |
83 | 123 | Vector3f velocity = new Vector3f(); |
84 | 124 | Vector3f forward = camera.getTransform().getForward(); |
85 | 125 | Vector3f right = camera.getTransform().getRight(); |
86 | 126 |
|
| 127 | + // Movement controls (WASD, Space, Shift) |
87 | 128 | if (input.isKeyPressed(Key.W)) velocity.addLocal(forward); |
88 | 129 | if (input.isKeyPressed(Key.S)) velocity.addLocal(forward.negate()); |
89 | | - if (input.isKeyPressed(Key.A)) velocity.addLocal(right.negate()); // Strafe left |
90 | | - if (input.isKeyPressed(Key.D)) velocity.addLocal(right); // Strafe right |
| 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); |
91 | 134 |
|
92 | | - if (input.isKeyPressed(Key.SPACE)) { |
93 | | - velocity.addLocal(0, -1, 0); |
94 | | - } |
95 | | - if (input.isKeyPressed(Key.SHIFT)) { |
96 | | - velocity.addLocal(0, 1, 0); |
97 | | - } |
98 | 135 | return velocity; |
99 | 136 | } |
100 | 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 | + */ |
101 | 157 | private void updateTarget() { |
102 | 158 | Vector3f position = camera.getTransform().getPosition(); |
103 | 159 | Vector3f rotation = camera.getTransform().getRotation(); |
104 | | - float yaw = rotation.y; |
105 | | - float pitch = rotation.x; |
106 | 160 |
|
107 | 161 | forward |
108 | | - .set(Mathf.cos(yaw) * Mathf.cos(pitch), Mathf.sin(pitch), Mathf.sin(yaw) * Mathf.cos(pitch)) |
| 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)) |
109 | 166 | .normalizeLocal(); |
110 | 167 |
|
111 | 168 | target.set(position).addLocal(forward); |
112 | | - |
113 | 169 | camera.setTarget(target); |
114 | 170 | } |
115 | 171 |
|
| 172 | + /** This method is called when the component is attached to an entity. Currently not used. */ |
116 | 173 | @Override |
117 | 174 | public void onAttach() { |
118 | 175 | // Not used yet |
119 | 176 | } |
120 | 177 |
|
| 178 | + /** This method is called when the component is detached from an entity. Currently not used. */ |
121 | 179 | @Override |
122 | 180 | public void onDetach() { |
123 | 181 | // Not used yet |
|
0 commit comments