Skip to content

Commit 539ec20

Browse files
Merge pull request #62 from ArtifactForms/working2
Working2
2 parents 564404a + a3ac5e3 commit 539ec20

File tree

11 files changed

+1155
-16
lines changed

11 files changed

+1155
-16
lines changed
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
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 SmoothFlyByCameraControl} class provides first-person camera control functionality,
12+
* allowing the user to move the camera smoothly in all directions and adjust its orientation using
13+
* mouse and keyboard input. This control simulates a "fly-by" movement with acceleration and
14+
* deceleration, while smoothing rapid mouse movements for a better experience.
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 smoothing applied to reduce sudden, erratic movements.
29+
*/
30+
public class SmoothFlyByCameraControl extends AbstractComponent {
31+
32+
private static final float DEFAULT_MOUSE_SENSITIVITY = 10f;
33+
private static final float DEFAULT_MOVE_SPEED = 30f;
34+
private static final float MAX_VERTICAL_ANGLE = 80f;
35+
private static final float MIN_VERTICAL_ANGLE = -80f;
36+
37+
private final Vector3f forward = new Vector3f();
38+
private final Vector3f target = new Vector3f();
39+
40+
private float mouseSensitivity = DEFAULT_MOUSE_SENSITIVITY;
41+
private float smoothedMouseX = 0f;
42+
private float smoothedMouseY = 0f;
43+
private float mouseSmoothingFactor = 0.7f;
44+
private float moveSpeed = DEFAULT_MOVE_SPEED;
45+
private float acceleration = 10f;
46+
private float deceleration = 5f;
47+
48+
private Input input;
49+
private Camera camera;
50+
51+
private Vector3f currentVelocity = new Vector3f();
52+
private Vector3f targetVelocity = new Vector3f();
53+
54+
/**
55+
* Constructs a new {@code SmoothFlyByCameraControl} with the specified input and camera.
56+
*
57+
* @param input The {@link Input} instance to capture user input.
58+
* @param camera The {@link PerspectiveCamera} to control.
59+
* @throws IllegalArgumentException if either input or camera is null.
60+
*/
61+
public SmoothFlyByCameraControl(Input input, PerspectiveCamera camera) {
62+
if (input == null || camera == null) {
63+
throw new IllegalArgumentException("Input and Camera cannot be null.");
64+
}
65+
this.input = input;
66+
this.camera = camera;
67+
}
68+
69+
/**
70+
* Updates the camera's position and orientation based on user input.
71+
*
72+
* @param tpf Time per frame, used to adjust movement and smoothing.
73+
*/
74+
@Override
75+
public void update(float tpf) {
76+
float rawMouseX = input.getMouseDeltaX() * mouseSensitivity * tpf;
77+
float rawMouseY = input.getMouseDeltaY() * mouseSensitivity * tpf;
78+
79+
// Smooth mouse input using linear interpolation (lerp)
80+
smoothedMouseX = Mathf.lerp(smoothedMouseX, rawMouseX, mouseSmoothingFactor);
81+
smoothedMouseY = Mathf.lerp(smoothedMouseY, rawMouseY, mouseSmoothingFactor);
82+
83+
handleRotation(smoothedMouseX, smoothedMouseY);
84+
updateTargetVelocity();
85+
86+
if (targetVelocity.isZero()) {
87+
if (currentVelocity.length() > 0.01f) {
88+
currentVelocity.lerpLocal(Vector3f.ZERO, deceleration * tpf);
89+
} else {
90+
currentVelocity.set(0, 0, 0);
91+
}
92+
}
93+
94+
currentVelocity.lerpLocal(targetVelocity, acceleration * tpf);
95+
96+
if (currentVelocity.length() > 0) {
97+
applyMovement(currentVelocity, tpf);
98+
}
99+
100+
updateTarget();
101+
input.center();
102+
}
103+
104+
/**
105+
* Handles the camera's yaw and pitch rotation based on mouse movement.
106+
*
107+
* @param mouseX The smoothed mouse movement in the X direction.
108+
* @param mouseY The smoothed mouse movement in the Y direction.
109+
*/
110+
private void handleRotation(float mouseX, float mouseY) {
111+
float yaw = mouseX;
112+
camera.getTransform().rotate(0, Mathf.toRadians(yaw), 0);
113+
114+
Vector3f rotation = camera.getTransform().getRotation();
115+
float currentPitch = rotation.x + Mathf.toRadians(mouseY);
116+
currentPitch =
117+
Mathf.clamp(
118+
currentPitch, Mathf.toRadians(MIN_VERTICAL_ANGLE), Mathf.toRadians(MAX_VERTICAL_ANGLE));
119+
rotation.x = currentPitch;
120+
camera.getTransform().setRotation(rotation);
121+
}
122+
123+
/** Updates the target velocity based on the current keyboard input. */
124+
private void updateTargetVelocity() {
125+
targetVelocity.set(0, 0, 0);
126+
127+
Vector3f forward = camera.getTransform().getForward();
128+
Vector3f right = camera.getTransform().getRight();
129+
130+
if (input.isKeyPressed(Key.W)) targetVelocity.addLocal(forward);
131+
if (input.isKeyPressed(Key.S)) targetVelocity.addLocal(forward.negate());
132+
if (input.isKeyPressed(Key.A)) targetVelocity.addLocal(right.negate());
133+
if (input.isKeyPressed(Key.D)) targetVelocity.addLocal(right);
134+
if (input.isKeyPressed(Key.SPACE)) targetVelocity.addLocal(0, -1, 0);
135+
if (input.isKeyPressed(Key.SHIFT)) targetVelocity.addLocal(0, 1, 0);
136+
137+
targetVelocity.normalizeLocal();
138+
}
139+
140+
/**
141+
* Applies the calculated movement to the camera's position based on the current velocity.
142+
*
143+
* @param velocity The current velocity vector.
144+
* @param tpf Time per frame, used to adjust movement speed.
145+
*/
146+
private void applyMovement(Vector3f velocity, float tpf) {
147+
Vector3f position = camera.getTransform().getPosition();
148+
position.addLocal(velocity.mult(moveSpeed * tpf));
149+
camera.getTransform().setPosition(position);
150+
}
151+
152+
/** Updates the camera's target position based on its forward direction. */
153+
private void updateTarget() {
154+
Vector3f position = camera.getTransform().getPosition();
155+
Vector3f rotation = camera.getTransform().getRotation();
156+
157+
forward
158+
.set(
159+
Mathf.cos(rotation.y) * Mathf.cos(rotation.x),
160+
Mathf.sin(rotation.x),
161+
Mathf.sin(rotation.y) * Mathf.cos(rotation.x))
162+
.normalizeLocal();
163+
164+
target.set(position).addLocal(forward);
165+
camera.setTarget(target);
166+
}
167+
168+
/** Called when the component is attached to a scene node. */
169+
@Override
170+
public void onAttach() {
171+
// TODO Auto-generated method stub
172+
}
173+
174+
/** Called when the component is detached from a scene node. */
175+
@Override
176+
public void onDetach() {
177+
// TODO Auto-generated method stub
178+
}
179+
180+
/**
181+
* Returns the current movement speed of the camera.
182+
*
183+
* @return The movement speed.
184+
*/
185+
public float getMoveSpeed() {
186+
return moveSpeed;
187+
}
188+
189+
/**
190+
* Sets the movement speed of the camera.
191+
*
192+
* @param moveSpeed The new movement speed to set.
193+
*/
194+
public void setMoveSpeed(float moveSpeed) {
195+
this.moveSpeed = moveSpeed;
196+
}
197+
198+
/**
199+
* Returns the current mouse sensitivity.
200+
*
201+
* @return The mouse sensitivity.
202+
*/
203+
public float getMouseSensitivity() {
204+
return mouseSensitivity;
205+
}
206+
207+
/**
208+
* Sets the mouse sensitivity.
209+
*
210+
* @param mouseSensitivity The new mouse sensitivity to set.
211+
*/
212+
public void setMouseSensitivity(float mouseSensitivity) {
213+
this.mouseSensitivity = mouseSensitivity;
214+
}
215+
216+
/**
217+
* Returns the current acceleration factor.
218+
*
219+
* @return The acceleration factor.
220+
*/
221+
public float getAcceleration() {
222+
return acceleration;
223+
}
224+
225+
/**
226+
* Sets the acceleration factor for the camera's movement.
227+
*
228+
* @param acceleration The new acceleration factor to set.
229+
*/
230+
public void setAcceleration(float acceleration) {
231+
this.acceleration = acceleration;
232+
}
233+
234+
/**
235+
* Returns the current deceleration factor.
236+
*
237+
* @return The deceleration factor.
238+
*/
239+
public float getDeceleration() {
240+
return deceleration;
241+
}
242+
243+
/**
244+
* Sets the deceleration factor for the camera's movement.
245+
*
246+
* @param deceleration The new deceleration factor to set.
247+
*/
248+
public void setDeceleration(float deceleration) {
249+
this.deceleration = deceleration;
250+
}
251+
252+
/**
253+
* Returns the current mouse smoothing factor.
254+
*
255+
* @return The mouse smoothing factor.
256+
*/
257+
public float getMouseSmoothingFactor() {
258+
return mouseSmoothingFactor;
259+
}
260+
261+
/**
262+
* Sets the mouse smoothing factor, which controls how much the mouse input is smoothed.
263+
*
264+
* @param mouseSmoothingFactor The new smoothing factor to set.
265+
*/
266+
public void setMouseSmoothingFactor(float mouseSmoothingFactor) {
267+
this.mouseSmoothingFactor = mouseSmoothingFactor;
268+
}
269+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package engine.demos.landmass;
2+
3+
import math.Color;
4+
import math.Mathf;
5+
6+
/**
7+
* A procedural landmass generator, ported from Sebastian Lague's Unity script. This class generates
8+
* a height map and a color map based on Perlin noise and terrain types. It supports adjustable
9+
* parameters for map size, noise generation, and biome regions.
10+
*
11+
* <p>The generated maps can be used to create terrains with realistic variations in height and
12+
* color.
13+
*/
14+
public class MapGenerator {
15+
16+
private int chunkSize = 241;
17+
private int mapWidth = chunkSize;
18+
private int mapHeight = chunkSize;
19+
private int seed = 221;
20+
private int octaves = 4;
21+
private float scale = 50;
22+
private float persistance = 0.5f;
23+
private float lacunarity = 2f;
24+
private float[][] heightMap;
25+
private TerrainType[] regions;
26+
27+
/** Constructs a new {@code MapGenerator} and initializes the height map and terrain regions. */
28+
public MapGenerator() {
29+
initializeRegions();
30+
heightMap =
31+
Noise.createHeightMap(mapWidth, mapHeight, seed, scale, octaves, persistance, lacunarity);
32+
}
33+
34+
/**
35+
* Initializes the predefined terrain regions for the map. Each region is associated with a
36+
* specific height threshold, name, and color.
37+
*/
38+
private void initializeRegions() {
39+
regions = new TerrainType[8];
40+
41+
regions[0] = new TerrainType(0.3f, "Water Deep", Color.getColorFromInt(52, 99, 196));
42+
regions[1] = new TerrainType(0.4f, "Water Shallow", Color.getColorFromInt(54, 102, 199));
43+
regions[2] = new TerrainType(0.45f, "Sand", Color.getColorFromInt(206, 209, 125));
44+
regions[3] = new TerrainType(0.55f, "Grass", Color.getColorFromInt(86, 151, 24));
45+
regions[4] = new TerrainType(0.6f, "Grass 2", Color.getColorFromInt(62, 107, 18));
46+
regions[5] = new TerrainType(0.7f, "Rock", Color.getColorFromInt(90, 69, 62));
47+
regions[6] = new TerrainType(0.9f, "Rock 2", Color.getColorFromInt(77, 69, 56));
48+
regions[7] = new TerrainType(1.0f, "Snow", Color.getColorFromInt(253, 253, 253));
49+
}
50+
51+
/**
52+
* Generates a grayscale noise map representing the terrain height. The noise values are mapped to
53+
* a color gradient ranging from black (low) to white (high).
54+
*
55+
* @return an array of RGBA color values representing the noise map
56+
*/
57+
public int[] createNoiseMap() {
58+
int[] colorMap = new int[heightMap.length * heightMap[9].length];
59+
60+
for (int y = 0; y < mapHeight; y++) {
61+
for (int x = 0; x < mapWidth; x++) {
62+
float lerpedValue = Mathf.lerp(0, 1, heightMap[x][y]);
63+
colorMap[y * mapWidth + x] = new Color(lerpedValue, lerpedValue, lerpedValue).getRGBA();
64+
}
65+
}
66+
return colorMap;
67+
}
68+
69+
/**
70+
* Generates a color map based on predefined terrain regions. The height values in the height map
71+
* are compared against the thresholds of each terrain type to determine the color.
72+
*
73+
* @return an array of RGBA color values representing the color map
74+
*/
75+
public int[] createColorMap() {
76+
int[] colorMap = new int[mapWidth * mapHeight];
77+
78+
for (int y = 0; y < mapHeight; y++) {
79+
for (int x = 0; x < mapWidth; x++) {
80+
float currentHeight = heightMap[x][y];
81+
for (int i = 0; i < regions.length; i++) {
82+
if (currentHeight <= regions[i].height) {
83+
colorMap[y * mapWidth + x] = regions[i].color.getRGBA();
84+
break;
85+
}
86+
}
87+
}
88+
}
89+
return colorMap;
90+
}
91+
92+
/**
93+
* Returns the generated height map. The height map is a 2D array of floats, where each value
94+
* represents the elevation at a specific point.
95+
*
96+
* @return a 2D float array representing the height map
97+
*/
98+
public float[][] getHeightMap() {
99+
return heightMap;
100+
}
101+
102+
/**
103+
* Represents a terrain type, which includes a height threshold, a name, and a color. Terrain
104+
* types are used to categorize different regions of the generated map.
105+
*/
106+
public class TerrainType {
107+
108+
public float height;
109+
public String name;
110+
public Color color;
111+
112+
/**
113+
* Constructs a new {@code TerrainType} with the specified height threshold, name, and color.
114+
*
115+
* @param height the height threshold for this terrain type
116+
* @param name the name of the terrain type
117+
* @param color the color associated with this terrain type
118+
*/
119+
public TerrainType(float height, String name, Color color) {
120+
this.height = height;
121+
this.name = name;
122+
this.color = color;
123+
}
124+
}
125+
}

0 commit comments

Comments
 (0)