Skip to content

Commit 7887da9

Browse files
committed
Replace free camera with physics-based character
- Add gravity, jump, and AABB collision to the player - Horizontal WASD movement is now flat (yaw-only, no pitch tilt) - Space jumps when on the ground; Ctrl fly-down removed - Player bounding box is 0.6×1.8 units with eye at 1.62 above feet - Axis-by-axis collision resolution queries ChunkManager each frame
1 parent 6a36d43 commit 7887da9

File tree

3 files changed

+120
-21
lines changed

3 files changed

+120
-21
lines changed

src/app.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,19 @@ void App::mainLoop() {
119119
glfwPollEvents();
120120
processInput(window);
121121

122+
// Physics: gravity, jumping, AABB collision with world blocks
123+
applyPhysics(deltaTime, [&](int wx, int wy, int wz) -> bool {
124+
if (wy < 0) return true; // below world = solid floor
125+
if (wy >= CHUNK_HEIGHT) return false; // above world = air
126+
int cx = (int)floorf((float)wx / (float)CHUNK_SIZE);
127+
int cz = (int)floorf((float)wz / (float)CHUNK_SIZE);
128+
int lx = wx - cx * CHUNK_SIZE;
129+
int lz = wz - cz * CHUNK_SIZE;
130+
Chunk* chunk = chunkManager.getChunk(cx, cz);
131+
if (!chunk) return false;
132+
return chunk->isSolid(lx, wy, lz);
133+
});
134+
122135
// ── Hotbar key selection (1-7) ────────────────────────────────────
123136
static const int hotbarKeys[7] = {
124137
GLFW_KEY_1, GLFW_KEY_2, GLFW_KEY_3, GLFW_KEY_4,

src/camera.cpp

Lines changed: 101 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
#include "camera.h"
2+
#include <cmath>
23

3-
Camera camera = {{0.f, 20.f, 30.f}, -90.f, -20.f, 15.f, 0.1f};
4+
static constexpr float WALK_SPEED = 5.0f;
5+
static constexpr float JUMP_SPEED = 9.0f;
6+
static constexpr float GRAVITY = -25.0f;
7+
static constexpr float MAX_FALL = -50.0f;
8+
static constexpr float PLAYER_HW = 0.3f; // half-width (player is 0.6 wide)
9+
static constexpr float PLAYER_H = 1.8f; // full height
10+
static constexpr float EYE_HEIGHT = 1.62f; // eye above feet
11+
12+
Camera camera = {{0.f, 70.f, 30.f}, -90.f, -20.f, 0.1f, {0.f, 0.f, 0.f}, false, false};
413
float lastX = 400.f;
514
float lastY = 300.f;
615
bool firstMouse = true;
@@ -30,31 +39,104 @@ void processInput(GLFWwindow* window) {
3039
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
3140
glfwSetWindowShouldClose(window, true);
3241

33-
float v = camera.speed * deltaTime;
34-
vec3 front = {
35-
cosf(glm_rad(camera.yaw)) * cosf(glm_rad(camera.pitch)),
36-
sinf(glm_rad(camera.pitch)),
37-
sinf(glm_rad(camera.yaw)) * cosf(glm_rad(camera.pitch))
38-
};
39-
glm_vec3_normalize(front);
40-
42+
// Horizontal movement: flat XZ based on yaw only (no pitch tilt)
43+
float yawRad = glm_rad(camera.yaw);
44+
vec3 front = { cosf(yawRad), 0.f, sinf(yawRad) }; // already unit length
4145
vec3 worldUp = {0.f, 1.f, 0.f};
4246
vec3 right; glm_vec3_cross(front, worldUp, right); glm_vec3_normalize(right);
43-
vec3 up; glm_vec3_cross(right, front, up);
4447

45-
auto move = [&](vec3 dir, float sign) {
46-
vec3 tmp; glm_vec3_scale(dir, sign * v, tmp);
47-
glm_vec3_add(camera.position, tmp, camera.position);
48+
vec3 moveDir = {0.f, 0.f, 0.f};
49+
auto addDir = [&](vec3 dir, float sign) {
50+
vec3 tmp; glm_vec3_scale(dir, sign, tmp);
51+
glm_vec3_add(moveDir, tmp, moveDir);
4852
};
53+
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) addDir(front, 1.f);
54+
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) addDir(front, -1.f);
55+
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) addDir(right, -1.f);
56+
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) addDir(right, 1.f);
57+
58+
float mag = glm_vec3_norm(moveDir);
59+
if (mag > 0.001f) {
60+
camera.velocity[0] = moveDir[0] * (WALK_SPEED / mag);
61+
camera.velocity[2] = moveDir[2] * (WALK_SPEED / mag);
62+
} else {
63+
camera.velocity[0] = 0.f;
64+
camera.velocity[2] = 0.f;
65+
}
4966

50-
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) move(front, 1.f);
51-
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) move(front, -1.f);
52-
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) move(right, -1.f);
53-
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) move(right, 1.f);
54-
if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS) move(up, 1.f);
55-
if (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS) move(up, -1.f);
67+
// Jump (only when on the ground)
68+
if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS && camera.onGround)
69+
camera.jumpRequested = true;
5670

5771
bool eNow = glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS;
5872
if (eNow && !ePrevious) wireframe = !wireframe;
5973
ePrevious = eNow;
6074
}
75+
76+
void applyPhysics(float dt, std::function<bool(int,int,int)> isSolid) {
77+
// Handle jump request
78+
if (camera.jumpRequested && camera.onGround) {
79+
camera.velocity[1] = JUMP_SPEED;
80+
camera.onGround = false;
81+
}
82+
camera.jumpRequested = false;
83+
84+
// Gravity
85+
camera.velocity[1] += GRAVITY * dt;
86+
if (camera.velocity[1] < MAX_FALL) camera.velocity[1] = MAX_FALL;
87+
88+
// Work in feet-space (eye is EYE_HEIGHT above feet)
89+
float fx = camera.position[0];
90+
float fy = camera.position[1] - EYE_HEIGHT;
91+
float fz = camera.position[2];
92+
93+
// Returns true if the player AABB at (px, py, pz) overlaps any solid block
94+
auto aabbSolid = [&](float px, float py, float pz) -> bool {
95+
int x0 = (int)floorf(px - PLAYER_HW);
96+
int x1 = (int)floorf(px + PLAYER_HW - 0.001f);
97+
int y0 = (int)floorf(py);
98+
int y1 = (int)floorf(py + PLAYER_H - 0.001f);
99+
int z0 = (int)floorf(pz - PLAYER_HW);
100+
int z1 = (int)floorf(pz + PLAYER_HW - 0.001f);
101+
for (int bx = x0; bx <= x1; bx++)
102+
for (int by = y0; by <= y1; by++)
103+
for (int bz = z0; bz <= z1; bz++)
104+
if (isSolid(bx, by, bz))
105+
return true;
106+
return false;
107+
};
108+
109+
// Resolve X
110+
float nx = fx + camera.velocity[0] * dt;
111+
if (aabbSolid(nx, fy, fz)) {
112+
camera.velocity[0] = 0.f;
113+
} else {
114+
fx = nx;
115+
}
116+
117+
// Resolve Y
118+
float ny = fy + camera.velocity[1] * dt;
119+
if (aabbSolid(fx, ny, fz)) {
120+
if (camera.velocity[1] < 0.f)
121+
camera.onGround = true;
122+
camera.velocity[1] = 0.f;
123+
// fy stays at old value (no penetration)
124+
} else {
125+
fy = ny;
126+
if (camera.velocity[1] < 0.f)
127+
camera.onGround = false; // falling freely
128+
}
129+
130+
// Resolve Z
131+
float nz = fz + camera.velocity[2] * dt;
132+
if (aabbSolid(fx, fy, nz)) {
133+
camera.velocity[2] = 0.f;
134+
} else {
135+
fz = nz;
136+
}
137+
138+
// Write back eye position
139+
camera.position[0] = fx;
140+
camera.position[1] = fy + EYE_HEIGHT;
141+
camera.position[2] = fz;
142+
}

src/camera.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
#define GLFW_INCLUDE_VULKAN
44
#include <GLFW/glfw3.h>
55
#include <cglm/cglm.h>
6+
#include <functional>
67

78
struct Camera {
8-
vec3 position;
9+
vec3 position; // eye position (feet + EYE_HEIGHT)
910
float yaw;
1011
float pitch;
11-
float speed;
1212
float sensitivity;
13+
vec3 velocity; // units/s
14+
bool onGround;
15+
bool jumpRequested;
1316
};
1417

1518
// Global camera state
@@ -24,3 +27,4 @@ extern bool ePrevious;
2427

2528
void mouseCallback(GLFWwindow* window, double xpos, double ypos);
2629
void processInput(GLFWwindow* window);
30+
void applyPhysics(float dt, std::function<bool(int,int,int)> isSolid);

0 commit comments

Comments
 (0)