|
diff --git a/game.c b/game.c
|
| 1 |
#include "all.h" |
1 |
#include "all.h" |
| 2 |
#include <stdio.h> |
2 |
|
| 3 |
#include <string.h> |
3 |
#include <string.h> |
| 4 |
#include <ctype.h> |
|
|
| 5 |
|
4 |
|
| 6 |
GameState game; |
5 |
GameState game; |
| 7 |
static array(CachedTexture) texture_cache; |
|
|
| 8 |
|
|
|
| 9 |
Texture2D GetTexture(const char *name) { |
|
|
| 10 |
for (int i = 0; i < texture_cache.length; i++) { |
|
|
| 11 |
if (strcmp(texture_cache.data[i].name, name) == 0) return texture_cache.data[i].tex; |
|
|
| 12 |
} |
|
|
| 13 |
|
|
|
| 14 |
char lname[256]; |
|
|
| 15 |
strncpy(lname, name, sizeof(lname)); |
|
|
| 16 |
for (int i = 0; lname[i]; i++) lname[i] = tolower(lname[i]); |
|
|
| 17 |
|
|
|
| 18 |
char path[256]; |
|
|
| 19 |
void *data = NULL; |
|
|
| 20 |
size_t size = 0; |
|
|
| 21 |
const char *ext = ".jpg"; |
|
|
| 22 |
|
|
|
| 23 |
snprintf(path, sizeof(path), "textures/%s.png", lname); |
|
|
| 24 |
data = vfs_read(path, &size); |
|
|
| 25 |
if (data) { |
|
|
| 26 |
ext = ".png"; |
|
|
| 27 |
} else { |
|
|
| 28 |
snprintf(path, sizeof(path), "textures/%s.jpg", lname); |
|
|
| 29 |
data = vfs_read(path, &size); |
|
|
| 30 |
ext = ".jpg"; |
|
|
| 31 |
} |
|
|
| 32 |
|
|
|
| 33 |
Texture2D tex = { 0 }; |
|
|
| 34 |
if (data) { |
|
|
| 35 |
Image img = LoadImageFromMemory(ext, data, (int)size); |
|
|
| 36 |
if (img.data) { |
|
|
| 37 |
tex = LoadTextureFromImage(img); |
|
|
| 38 |
UnloadImage(img); |
|
|
| 39 |
} |
|
|
| 40 |
vfs_free(data); |
|
|
| 41 |
} |
|
|
| 42 |
|
|
|
| 43 |
if (tex.id == 0) { |
|
|
| 44 |
TraceLog(LOG_WARNING, "Failed to load texture: '%s' (tried path: '%s')", name, path); |
|
|
| 45 |
Image img = GenImageChecked(64, 64, 8, 8, (Color){128, 128, 128, 255}, (Color){200, 200, 200, 255}); |
|
|
| 46 |
tex = LoadTextureFromImage(img); |
|
|
| 47 |
UnloadImage(img); |
|
|
| 48 |
} |
|
|
| 49 |
|
|
|
| 50 |
if (tex.id != 0) { |
|
|
| 51 |
GenTextureMipmaps(&tex); |
|
|
| 52 |
SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); |
|
|
| 53 |
SetTextureWrap(tex, TEXTURE_WRAP_REPEAT); |
|
|
| 54 |
|
|
|
| 55 |
CachedTexture cached; |
|
|
| 56 |
strncpy(cached.name, name, sizeof(cached.name)); |
|
|
| 57 |
cached.tex = tex; |
|
|
| 58 |
array_push(texture_cache, cached); |
|
|
| 59 |
} |
|
|
| 60 |
|
|
|
| 61 |
return tex; |
|
|
| 62 |
} |
|
|
| 63 |
|
|
|
| 64 |
void UnloadMap(void) { |
|
|
| 65 |
if (game.world_models) { |
|
|
| 66 |
for (int i = 0; i < game.world_model_count; i++) { |
|
|
| 67 |
UnloadModel(game.world_models[i]); |
|
|
| 68 |
} |
|
|
| 69 |
free(game.world_models); |
|
|
| 70 |
game.world_models = NULL; |
|
|
| 71 |
game.world_model_count = 0; |
|
|
| 72 |
} |
|
|
| 73 |
|
|
|
| 74 |
for (int i = 0; i < texture_cache.length; i++) { |
|
|
| 75 |
UnloadTexture(texture_cache.data[i].tex); |
|
|
| 76 |
} |
|
|
| 77 |
array_clear(texture_cache); |
|
|
| 78 |
} |
|
|
| 79 |
|
|
|
| 80 |
bool LoadMap(const char *filename) { |
|
|
| 81 |
TraceLog(LOG_INFO, "Loading map: %s", filename); |
|
|
| 82 |
|
|
|
| 83 |
Map map = ParseMap(filename); |
|
|
| 84 |
if (map.entity_count == 0) { |
|
|
| 85 |
TraceLog(LOG_ERROR, "Failed to load map or map is empty: %s", filename); |
|
|
| 86 |
return false; |
|
|
| 87 |
} |
|
|
| 88 |
|
|
|
| 89 |
UnloadMap(); |
|
|
| 90 |
|
|
|
| 91 |
array(TextureGroup) groups; |
|
|
| 92 |
array_init(groups); |
|
|
| 93 |
|
|
|
| 94 |
// Default camera if no start found |
|
|
| 95 |
game.camera.position = (Vector3){ 0, 10, 0 }; |
|
|
| 96 |
game.camera.target = (Vector3){ 1, 10, 0 }; |
|
|
| 97 |
game.camera.up = (Vector3){ 0, 1, 0 }; |
|
|
| 98 |
game.camera.fovy = PLAYER_FOV; |
|
|
| 99 |
game.camera.projection = CAMERA_PERSPECTIVE; |
|
|
| 100 |
|
|
|
| 101 |
int total_brushes = 0; |
|
|
| 102 |
for (int i = 0; i < map.entity_count; i++) { |
|
|
| 103 |
MapEntity *e = &map.entities[i]; |
|
|
| 104 |
bool is_world = false; |
|
|
| 105 |
const char *classname = ""; |
|
|
| 106 |
for (int j = 0; j < e->property_count; j++) { |
|
|
| 107 |
if (strcmp(e->properties[j].key, "classname") == 0) { |
|
|
| 108 |
classname = e->properties[j].value; |
|
|
| 109 |
if (strcmp(classname, "worldspawn") == 0) is_world = true; |
|
|
| 110 |
} |
|
|
| 111 |
if (strcmp(e->properties[j].key, "origin") == 0) { |
|
|
| 112 |
float x, y, z; |
|
|
| 113 |
sscanf(e->properties[j].value, "%f %f %f", &x, &y, &z); |
|
|
| 114 |
if (strcmp(classname, "info_player_start") == 0) { |
|
|
| 115 |
game.pos = (Vector3){ x, z, -y }; |
|
|
| 116 |
game.camera.position = game.pos; |
|
|
| 117 |
float angle = 0; |
|
|
| 118 |
for (int k = 0; k < e->property_count; k++) { |
|
|
| 119 |
if (strcmp(e->properties[k].key, "angle") == 0) { |
|
|
| 120 |
angle = (float)atof(e->properties[k].value); |
|
|
| 121 |
} |
|
|
| 122 |
} |
|
|
| 123 |
game.yaw = (angle + 90.0f) * DEG2RAD; |
|
|
| 124 |
game.pitch = 0; |
|
|
| 125 |
game.camera.target = Vector3Add(game.pos, (Vector3){sinf(game.yaw), 0, cosf(game.yaw)}); |
|
|
| 126 |
TraceLog(LOG_INFO, "Player spawn set to: %f, %f, %f (yaw: %f)", game.pos.x, game.pos.y, game.pos.z, game.yaw); |
|
|
| 127 |
} |
|
|
| 128 |
} |
|
|
| 129 |
} |
|
|
| 130 |
|
|
|
| 131 |
if (is_world) { |
|
|
| 132 |
total_brushes += e->brush_count; |
|
|
| 133 |
for (int j = 0; j < e->brush_count; j++) { |
|
|
| 134 |
MapBrush *brush = &e->brushes[j]; |
|
|
| 135 |
for (int p_idx = 0; p_idx < brush->plane_count; p_idx++) { |
|
|
| 136 |
MapPlane *mp = &brush->planes[p_idx]; |
|
|
| 137 |
Plane plane = PlaneFromPoints(mp->p[0], mp->p[1], mp->p[2]); |
|
|
| 138 |
|
|
|
| 139 |
if (Vector3Length(plane.normal) < 0.5f) continue; |
|
|
| 140 |
|
|
|
| 141 |
Polygon poly = CreateLargeQuad(plane); |
|
|
| 142 |
for (int k = 0; k < brush->plane_count; k++) { |
|
|
| 143 |
if (p_idx == k) continue; |
|
|
| 144 |
Plane clipPlane = PlaneFromPoints(brush->planes[k].p[0], brush->planes[k].p[1], brush->planes[k].p[2]); |
|
|
| 145 |
if (Vector3Length(clipPlane.normal) < 0.5f) continue; |
|
|
| 146 |
|
|
|
| 147 |
Polygon next = PolyClip(poly, clipPlane); |
|
|
| 148 |
PolyFree(poly); |
|
|
| 149 |
poly = next; |
|
|
| 150 |
} |
|
|
| 151 |
if (poly.count >= 3) { |
|
|
| 152 |
TextureGroup *group = NULL; |
|
|
| 153 |
for (int g = 0; g < groups.length; g++) { |
|
|
| 154 |
if (strcmp(groups.data[g].texture, mp->texture) == 0) { |
|
|
| 155 |
group = &groups.data[g]; |
|
|
| 156 |
break; |
|
|
| 157 |
} |
|
|
| 158 |
} |
|
|
| 159 |
if (!group) { |
|
|
| 160 |
TextureGroup new_group = { 0 }; |
|
|
| 161 |
strncpy(new_group.texture, mp->texture, sizeof(new_group.texture)); |
|
|
| 162 |
array_init(new_group.vertices); |
|
|
| 163 |
array_init(new_group.texcoords); |
|
|
| 164 |
array_init(new_group.normals); |
|
|
| 165 |
array_push(groups, new_group); |
|
|
| 166 |
group = &groups.data[groups.length - 1]; |
|
|
| 167 |
} |
|
|
| 168 |
for (int v = 1; v < poly.count - 1; v++) { |
|
|
| 169 |
Vector3 v0 = poly.verts[0]; |
|
|
| 170 |
Vector3 v1 = poly.verts[v+1]; |
|
|
| 171 |
Vector3 v2 = poly.verts[v]; |
|
|
| 172 |
array_push(group->vertices, v0.x); array_push(group->vertices, v0.y); array_push(group->vertices, v0.z); |
|
|
| 173 |
array_push(group->vertices, v1.x); array_push(group->vertices, v1.y); array_push(group->vertices, v1.z); |
|
|
| 174 |
array_push(group->vertices, v2.x); array_push(group->vertices, v2.y); array_push(group->vertices, v2.z); |
|
|
| 175 |
Vector2 uv0 = GetUV(v0, mp, plane); |
|
|
| 176 |
Vector2 uv1 = GetUV(v1, mp, plane); |
|
|
| 177 |
Vector2 uv2 = GetUV(v2, mp, plane); |
|
|
| 178 |
array_push(group->texcoords, uv0.x); array_push(group->texcoords, uv0.y); |
|
|
| 179 |
array_push(group->texcoords, uv1.x); array_push(group->texcoords, uv1.y); |
|
|
| 180 |
array_push(group->texcoords, uv2.x); array_push(group->texcoords, uv2.y); |
|
|
| 181 |
Vector3 out_normal = Vector3Scale(plane.normal, -1.0f); |
|
|
| 182 |
array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z); |
|
|
| 183 |
array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z); |
|
|
| 184 |
array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z); |
|
|
| 185 |
} |
|
|
| 186 |
} |
|
|
| 187 |
PolyFree(poly); |
|
|
| 188 |
} |
|
|
| 189 |
} |
|
|
| 190 |
} |
|
|
| 191 |
} |
|
|
| 192 |
|
|
|
| 193 |
array(Model) final_models; |
|
|
| 194 |
array_init(final_models); |
|
|
| 195 |
|
|
|
| 196 |
for (int i = 0; i < groups.length; i++) { |
|
|
| 197 |
TextureGroup *g = &groups.data[i]; |
|
|
| 198 |
if (g->vertices.length == 0) continue; |
|
|
| 199 |
Mesh mesh = { 0 }; |
|
|
| 200 |
mesh.vertexCount = (int)g->vertices.length / 3; |
|
|
| 201 |
mesh.triangleCount = mesh.vertexCount / 3; |
|
|
| 202 |
mesh.vertices = (float *)malloc(g->vertices.length * sizeof(float)); |
|
|
| 203 |
memcpy(mesh.vertices, g->vertices.data, g->vertices.length * sizeof(float)); |
|
|
| 204 |
mesh.texcoords = (float *)malloc(g->texcoords.length * sizeof(float)); |
|
|
| 205 |
memcpy(mesh.texcoords, g->texcoords.data, g->texcoords.length * sizeof(float)); |
|
|
| 206 |
mesh.normals = (float *)malloc(g->normals.length * sizeof(float)); |
|
|
| 207 |
memcpy(mesh.normals, g->normals.data, g->normals.length * sizeof(float)); |
|
|
| 208 |
UploadMesh(&mesh, false); |
|
|
| 209 |
Model model = LoadModelFromMesh(mesh); |
|
|
| 210 |
model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = GetTexture(g->texture); |
|
|
| 211 |
array_push(final_models, model); |
|
|
| 212 |
array_free(g->vertices); |
|
|
| 213 |
array_free(g->texcoords); |
|
|
| 214 |
array_free(g->normals); |
|
|
| 215 |
} |
|
|
| 216 |
|
|
|
| 217 |
game.world_models = final_models.data; |
|
|
| 218 |
game.world_model_count = (int)final_models.length; |
|
|
| 219 |
array_free(groups); |
|
|
| 220 |
|
|
|
| 221 |
FreeMap(map); |
|
|
| 222 |
TraceLog(LOG_INFO, "Processed %d brushes into %d models", total_brushes, game.world_model_count); |
|
|
| 223 |
return true; |
|
|
| 224 |
} |
|
|
| 225 |
|
6 |
|
| 226 |
void InitGame(void) { |
7 |
void InitGame(void) { |
| 227 |
array_init(texture_cache); |
8 |
memset(&game, 0, sizeof(game)); |
| 228 |
game.world_models = NULL; |
|
|
| 229 |
game.world_model_count = 0; |
|
|
| 230 |
|
|
|
| 231 |
// Load UI Font |
|
|
| 232 |
size_t font_size = 0; |
|
|
| 233 |
void *font_data = vfs_read("fonts/LiberationSans-Bold.ttf", &font_size); |
|
|
| 234 |
if (font_data) { |
|
|
| 235 |
game.font_ui = LoadFontFromMemory(".ttf", font_data, (int)font_size, 20, NULL, 0); |
|
|
| 236 |
vfs_free(font_data); |
|
|
| 237 |
} else { |
|
|
| 238 |
game.font_ui = GetFontDefault(); |
|
|
| 239 |
} |
|
|
| 240 |
|
9 |
|
| 241 |
game.mode = STATE_TITLE; |
10 |
game.font_ui = LoadFontVFS(UI_FONT_PATH, UI_FONT_SIZE); |
|
|
11 |
game.mode = STATE_MENU; |
|
|
12 |
game.cursor_captured = false; |
|
|
13 |
EnableCursor(); |
| 242 |
|
14 |
|
| 243 |
game.cursor_captured = false; |
15 |
game.player.move_mode = MOVE_NORMAL; |
| 244 |
EnableCursor(); |
16 |
game.player.pos = (Vector3){ 0, 0, 0 }; |
|
|
17 |
game.player.velocity = (Vector3){ 0, 0, 0 }; |
|
|
18 |
game.player.is_grounded = false; |
| 245 |
|
19 |
|
| 246 |
game.move_mode = MOVE_NORMAL; |
20 |
// Camera setup |
| 247 |
game.velocity = (Vector3){ 0, 0, 0 }; |
21 |
game.camera.up = (Vector3){ 0, 1, 0 }; |
| 248 |
game.is_grounded = false; |
22 |
game.camera.fovy = PLAYER_FOV; |
|
|
23 |
game.camera.projection = CAMERA_PERSPECTIVE; |
| 249 |
} |
24 |
} |
| 250 |
|
25 |
|
| 251 |
void SetMap(const char *path) { |
26 |
void SetMap(const char *path) { |
| 252 |
strncpy(game.map_path, path, sizeof(game.map_path) - 1); |
27 |
strncpy(game.map_path, path, sizeof(game.map_path) - 1); |
| 253 |
game.map_path[sizeof(game.map_path) - 1] = '\0'; |
28 |
game.map_path[sizeof(game.map_path) - 1] = '\0'; |
| 254 |
} |
29 |
} |
| 255 |
|
30 |
|
| 256 |
void UpdateGame(void) { |
31 |
void UpdateGame(void) { |
| 257 |
if (game.mode == STATE_TITLE) { |
32 |
if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { |
| 258 |
if (IsKeyPressed(KEY_ENTER)) { |
33 |
game.cursor_captured = !game.cursor_captured; |
| 259 |
if (LoadMap(game.map_path)) { |
34 |
if (game.cursor_captured) DisableCursor(); |
| 260 |
game.mode = STATE_PLAYING; |
35 |
else EnableCursor(); |
| 261 |
} |
36 |
} |
| 262 |
} |
|
|
| 263 |
return; |
|
|
| 264 |
} |
|
|
| 265 |
|
|
|
| 266 |
if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { |
|
|
| 267 |
game.cursor_captured = !game.cursor_captured; |
|
|
| 268 |
if (game.cursor_captured) DisableCursor(); |
|
|
| 269 |
else EnableCursor(); |
|
|
| 270 |
} |
|
|
| 271 |
|
|
|
| 272 |
if (IsKeyPressed(KEY_ONE)) LoadMap("maps/demo1.map"); |
|
|
| 273 |
if (IsKeyPressed(KEY_TWO)) LoadMap("maps/demo2.map"); |
|
|
| 274 |
if (IsKeyPressed(KEY_THREE)) LoadMap("maps/demo3.map"); |
|
|
| 275 |
|
37 |
|
| 276 |
if (IsKeyPressed(KEY_V)) { |
38 |
if (IsKeyPressed(KEY_V)) { |
| 277 |
game.vsync = !game.vsync; |
39 |
game.vsync = !game.vsync; |
| 278 |
if (game.vsync) { |
40 |
if (game.vsync) { |
| 279 |
SetWindowState(FLAG_VSYNC_HINT); |
41 |
SetWindowState(FLAG_VSYNC_HINT); |
| 280 |
SetTargetFPS(GetMonitorRefreshRate(GetCurrentMonitor())); |
42 |
SetTargetFPS(GetMonitorRefreshRate(GetCurrentMonitor())); |
| 281 |
} else { |
43 |
} else { |
| 282 |
ClearWindowState(FLAG_VSYNC_HINT); |
44 |
ClearWindowState(FLAG_VSYNC_HINT); |
| 283 |
SetTargetFPS(0); |
45 |
SetTargetFPS(0); |
| 284 |
} |
46 |
} |
| 285 |
} |
47 |
} |
| 286 |
|
48 |
|
| 287 |
UpdatePlayer(); |
49 |
UpdatePlayer(); |
| 288 |
} |
50 |
} |
| 289 |
|
51 |
|
| 290 |
void DrawGame(void) { |
52 |
void DrawGame(void) { |
| 291 |
BeginDrawing(); |
53 |
BeginDrawing(); |
| 292 |
ClearBackground(BLACK); |
54 |
ClearBackground(DARKGRAY); |
| 293 |
|
55 |
|
| 294 |
if (game.mode == STATE_TITLE) { |
56 |
BeginMode3D(game.camera); |
| 295 |
int screenWidth = GetScreenWidth(); |
|
|
| 296 |
int screenHeight = GetScreenHeight(); |
|
|
| 297 |
|
57 |
|
| 298 |
const char *title = "STALAG"; |
58 |
rlEnableBackfaceCulling(); |
| 299 |
const char *sub = "Press ENTER to Start"; |
59 |
for (int i = 0; i < game.map.count; i++) { |
| 300 |
|
60 |
DrawModel(game.map.models[i], (Vector3){ 0, 0, 0 }, 1.0f, WHITE); |
| 301 |
int titleSize = 60; |
61 |
} |
| 302 |
int subSize = 20; |
|
|
| 303 |
|
62 |
|
| 304 |
Vector2 titlePos = { |
63 |
EndMode3D(); |
| 305 |
(float)(screenWidth - MeasureTextEx(game.font_ui, title, (float)titleSize, 4).x) / 2, |
|
|
| 306 |
(float)screenHeight / 2 - 40 |
|
|
| 307 |
}; |
|
|
| 308 |
Vector2 subPos = { |
|
|
| 309 |
(float)(screenWidth - MeasureTextEx(game.font_ui, sub, (float)subSize, 2).x) / 2, |
|
|
| 310 |
(float)screenHeight / 2 + 40 |
|
|
| 311 |
}; |
|
|
| 312 |
|
64 |
|
| 313 |
DrawTextEx(game.font_ui, title, titlePos, (float)titleSize, 4, WHITE); |
65 |
DrawCrosshair(); |
| 314 |
DrawTextEx(game.font_ui, sub, subPos, (float)subSize, 2, GRAY); |
66 |
DrawDebugInfo(); |
| 315 |
} else { |
|
|
| 316 |
ClearBackground(DARKGRAY); |
|
|
| 317 |
BeginMode3D(game.camera); |
|
|
| 318 |
|
|
|
| 319 |
// Enable backface culling to hide interior faces of brushes |
|
|
| 320 |
rlEnableBackfaceCulling(); |
|
|
| 321 |
for (int i = 0; i < game.world_model_count; i++) { |
|
|
| 322 |
DrawModel(game.world_models[i], (Vector3){ 0, 0, 0 }, 1.0f, WHITE); |
|
|
| 323 |
} |
|
|
| 324 |
|
|
|
| 325 |
EndMode3D(); |
|
|
| 326 |
|
|
|
| 327 |
DrawCrosshair(); |
|
|
| 328 |
DrawDebugInfo(); |
|
|
| 329 |
} |
|
|
| 330 |
|
67 |
|
| 331 |
EndDrawing(); |
68 |
EndDrawing(); |
| 332 |
} |
69 |
} |
|
diff --git a/map.c b/map.c
|
| 1 |
#include "all.h" |
1 |
#include "all.h" |
|
|
2 |
|
| 2 |
#include <ctype.h> |
3 |
#include <ctype.h> |
| 3 |
#include <stdlib.h> |
4 |
#include <stdlib.h> |
| 4 |
#include <stdio.h> |
5 |
#include <stdio.h> |
|
|
6 |
#include <string.h> |
|
|
7 |
#include <float.h> |
| 5 |
|
8 |
|
| 6 |
char map_peek(MapParser *p) { |
9 |
char map_peek(MapParser *p) { |
| 7 |
if (p->pos >= p->length) return 0; |
10 |
if (p->pos >= p->length) return 0; |
| 8 |
return p->data[p->pos]; |
11 |
return p->data[p->pos]; |
| 9 |
} |
12 |
} |
| 10 |
|
13 |
|
| 11 |
char map_get(MapParser *p) { |
14 |
char map_get(MapParser *p) { |
| 12 |
if (p->pos >= p->length) return 0; |
15 |
if (p->pos >= p->length) return 0; |
| 13 |
return p->data[p->pos++]; |
16 |
return p->data[p->pos++]; |
| 14 |
} |
17 |
} |
| 15 |
|
18 |
|
| 16 |
void map_skip_whitespace(MapParser *p) { |
19 |
void map_skip_whitespace(MapParser *p) { |
| 17 |
while (true) { |
20 |
while (true) { |
| 18 |
char c = map_peek(p); |
21 |
char c = map_peek(p); |
| 19 |
if (isspace(c)) { |
22 |
if (isspace(c)) { |
| 20 |
map_get(p); |
23 |
map_get(p); |
| 21 |
} else if (c == '/' && p->data[p->pos + 1] == '/') { |
24 |
} else if (c == '/' && p->data[p->pos + 1] == '/') { |
| 22 |
while (map_peek(p) != '\n' && map_peek(p) != 0) map_get(p); |
25 |
while (map_peek(p) != '\n' && map_peek(p) != 0) map_get(p); |
| 23 |
} else { |
26 |
} else { |
| 24 |
break; |
27 |
break; |
| 25 |
} |
28 |
} |
| 26 |
} |
29 |
} |
| 27 |
} |
30 |
} |
| 28 |
|
31 |
|
| 29 |
bool map_expect(MapParser *p, char expected) { |
32 |
bool map_expect(MapParser *p, char expected) { |
| 30 |
map_skip_whitespace(p); |
33 |
map_skip_whitespace(p); |
| 31 |
if (map_get(p) == expected) return true; |
34 |
if (map_get(p) == expected) return true; |
| 32 |
return false; |
35 |
return false; |
| 33 |
} |
36 |
} |
| 34 |
|
37 |
|
| 35 |
void map_parse_token(MapParser *p, char *buffer, int size) { |
38 |
void map_parse_token(MapParser *p, char *buffer, int size) { |
| 36 |
map_skip_whitespace(p); |
39 |
map_skip_whitespace(p); |
| 37 |
int i = 0; |
40 |
int i = 0; |
| 38 |
bool quoted = false; |
41 |
bool quoted = false; |
| 39 |
if (map_peek(p) == '"') { |
42 |
if (map_peek(p) == '"') { |
| 40 |
quoted = true; |
43 |
quoted = true; |
| 41 |
map_get(p); |
44 |
map_get(p); |
| 42 |
} |
45 |
} |
| 43 |
|
46 |
|
| 44 |
while (true) { |
47 |
while (true) { |
| 45 |
char c = map_peek(p); |
48 |
char c = map_peek(p); |
| 46 |
if (c == 0) break; |
49 |
if (c == 0) break; |
| 47 |
if (quoted) { |
50 |
if (quoted) { |
| 48 |
if (c == '"') { |
51 |
if (c == '"') { |
| 49 |
map_get(p); |
52 |
map_get(p); |
| 50 |
break; |
53 |
break; |
| 51 |
} |
54 |
} |
| 52 |
} else { |
55 |
} else { |
| 53 |
if (isspace(c) || c == '(' || c == ')' || c == '{' || c == '}') break; |
56 |
if (isspace(c) || c == '(' || c == ')' || c == '{' || c == '}') break; |
| 54 |
} |
57 |
} |
| 55 |
if (i < size - 1) buffer[i++] = map_get(p); |
58 |
if (i < size - 1) buffer[i++] = map_get(p); |
| 56 |
else map_get(p); |
59 |
else map_get(p); |
| 57 |
} |
60 |
} |
| 58 |
buffer[i] = 0; |
61 |
buffer[i] = 0; |
| 59 |
} |
62 |
} |
| 60 |
|
63 |
|
| 61 |
Vector3 map_parse_vector(MapParser *p) { |
64 |
Vector3 map_parse_vector(MapParser *p) { |
| 62 |
map_expect(p, '('); |
65 |
map_expect(p, '('); |
| 63 |
char buf[64]; |
66 |
char buf[64]; |
| 64 |
map_parse_token(p, buf, sizeof(buf)); |
67 |
map_parse_token(p, buf, sizeof(buf)); |
| 65 |
float x = (float)atof(buf); |
68 |
float x = (float)atof(buf); |
| 66 |
map_parse_token(p, buf, sizeof(buf)); |
69 |
map_parse_token(p, buf, sizeof(buf)); |
| 67 |
float y = (float)atof(buf); |
70 |
float y = (float)atof(buf); |
| 68 |
map_parse_token(p, buf, sizeof(buf)); |
71 |
map_parse_token(p, buf, sizeof(buf)); |
| 69 |
float z = (float)atof(buf); |
72 |
float z = (float)atof(buf); |
| 70 |
map_expect(p, ')'); |
73 |
map_expect(p, ')'); |
| 71 |
return (Vector3){ x, z, -y }; |
74 |
return (Vector3){ x, z, -y }; |
| 72 |
} |
75 |
} |
| 73 |
|
76 |
|
| 74 |
Map ParseMap(const char *filename) { |
77 |
Map ParseMap(const char *filename) { |
| 75 |
size_t size; |
78 |
size_t size; |
| 76 |
char *data = (char *)vfs_read(filename, &size); |
79 |
char *data = (char *)vfs_read(filename, &size); |
| 77 |
Map map = { 0 }; |
80 |
Map map = { 0 }; |
| 78 |
if (!data) return map; |
81 |
if (!data) return map; |
|
|
82 |
|
|
|
83 |
MapParser p = { data, size, 0 }; |
|
|
84 |
array(MapEntity) entities; |
|
|
85 |
array_init(entities); |
| 79 |
|
86 |
|
| 80 |
MapParser p = { data, size, 0 }; |
87 |
while (true) { |
| 81 |
array(MapEntity) entities; |
88 |
map_skip_whitespace(&p); |
| 82 |
array_init(entities); |
89 |
if (map_peek(&p) == 0) break; |
|
|
90 |
|
|
|
91 |
if (map_expect(&p, '{')) { |
|
|
92 |
MapEntity entity = { 0 }; |
|
|
93 |
array(MapProperty) props; |
|
|
94 |
array(MapBrush) brushes; |
|
|
95 |
array_init(props); |
|
|
96 |
array_init(brushes); |
| 83 |
|
97 |
|
| 84 |
while (true) { |
98 |
while (true) { |
| 85 |
map_skip_whitespace(&p); |
99 |
map_skip_whitespace(&p); |
| 86 |
if (map_peek(&p) == 0) break; |
100 |
char c = map_peek(&p); |
|
|
101 |
if (c == '}') { |
|
|
102 |
map_get(&p); |
|
|
103 |
break; |
|
|
104 |
} else if (c == '"') { |
|
|
105 |
MapProperty prop; |
|
|
106 |
map_parse_token(&p, prop.key, sizeof(prop.key)); |
|
|
107 |
map_parse_token(&p, prop.value, sizeof(prop.value)); |
|
|
108 |
array_push(props, prop); |
|
|
109 |
} else if (c == '{') { |
|
|
110 |
map_get(&p); |
|
|
111 |
MapBrush brush = { 0 }; |
|
|
112 |
array(MapPlane) planes; |
|
|
113 |
array_init(planes); |
|
|
114 |
while (true) { |
|
|
115 |
map_skip_whitespace(&p); |
|
|
116 |
if (map_peek(&p) == '}') { |
|
|
117 |
map_get(&p); |
|
|
118 |
break; |
|
|
119 |
} |
|
|
120 |
MapPlane plane; |
|
|
121 |
plane.p[0] = map_parse_vector(&p); |
|
|
122 |
plane.p[1] = map_parse_vector(&p); |
|
|
123 |
plane.p[2] = map_parse_vector(&p); |
|
|
124 |
map_parse_token(&p, plane.texture, sizeof(plane.texture)); |
| 87 |
|
125 |
|
| 88 |
if (map_expect(&p, '{')) { |
126 |
char buf[64]; |
| 89 |
MapEntity entity = { 0 }; |
127 |
map_parse_token(&p, buf, sizeof(buf)); plane.shift[0] = (float)atof(buf); |
| 90 |
array(MapProperty) props; |
128 |
map_parse_token(&p, buf, sizeof(buf)); plane.shift[1] = (float)atof(buf); |
| 91 |
array(MapBrush) brushes; |
129 |
map_parse_token(&p, buf, sizeof(buf)); plane.rotate = (float)atof(buf); |
| 92 |
array_init(props); |
130 |
map_parse_token(&p, buf, sizeof(buf)); plane.scale[0] = (float)atof(buf); |
| 93 |
array_init(brushes); |
131 |
map_parse_token(&p, buf, sizeof(buf)); plane.scale[1] = (float)atof(buf); |
| 94 |
|
132 |
|
| 95 |
while (true) { |
133 |
array_push(planes, plane); |
| 96 |
map_skip_whitespace(&p); |
134 |
} |
| 97 |
char c = map_peek(&p); |
135 |
brush.planes = planes.data; |
| 98 |
if (c == '}') { |
136 |
brush.plane_count = (int)planes.length; |
| 99 |
map_get(&p); |
137 |
array_push(brushes, brush); |
| 100 |
break; |
138 |
} else { |
| 101 |
} else if (c == '"') { |
139 |
map_get(&p); |
| 102 |
MapProperty prop; |
140 |
} |
| 103 |
map_parse_token(&p, prop.key, sizeof(prop.key)); |
141 |
} |
| 104 |
map_parse_token(&p, prop.value, sizeof(prop.value)); |
142 |
entity.properties = props.data; |
| 105 |
array_push(props, prop); |
143 |
entity.property_count = (int)props.length; |
| 106 |
} else if (c == '{') { |
144 |
entity.brushes = brushes.data; |
| 107 |
map_get(&p); |
145 |
entity.brush_count = (int)brushes.length; |
| 108 |
MapBrush brush = { 0 }; |
146 |
array_push(entities, entity); |
| 109 |
array(MapPlane) planes; |
147 |
} |
| 110 |
array_init(planes); |
148 |
} |
| 111 |
while (true) { |
|
|
| 112 |
map_skip_whitespace(&p); |
|
|
| 113 |
if (map_peek(&p) == '}') { |
|
|
| 114 |
map_get(&p); |
|
|
| 115 |
break; |
|
|
| 116 |
} |
|
|
| 117 |
MapPlane plane; |
|
|
| 118 |
plane.p[0] = map_parse_vector(&p); |
|
|
| 119 |
plane.p[1] = map_parse_vector(&p); |
|
|
| 120 |
plane.p[2] = map_parse_vector(&p); |
|
|
| 121 |
map_parse_token(&p, plane.texture, sizeof(plane.texture)); |
|
|
| 122 |
|
|
|
| 123 |
char buf[64]; |
|
|
| 124 |
map_parse_token(&p, buf, sizeof(buf)); plane.shift[0] = (float)atof(buf); |
|
|
| 125 |
map_parse_token(&p, buf, sizeof(buf)); plane.shift[1] = (float)atof(buf); |
|
|
| 126 |
map_parse_token(&p, buf, sizeof(buf)); plane.rotate = (float)atof(buf); |
|
|
| 127 |
map_parse_token(&p, buf, sizeof(buf)); plane.scale[0] = (float)atof(buf); |
|
|
| 128 |
map_parse_token(&p, buf, sizeof(buf)); plane.scale[1] = (float)atof(buf); |
|
|
| 129 |
|
|
|
| 130 |
array_push(planes, plane); |
|
|
| 131 |
} |
|
|
| 132 |
brush.planes = planes.data; |
|
|
| 133 |
brush.plane_count = (int)planes.length; |
|
|
| 134 |
array_push(brushes, brush); |
|
|
| 135 |
} else { |
|
|
| 136 |
map_get(&p); |
|
|
| 137 |
} |
|
|
| 138 |
} |
|
|
| 139 |
entity.properties = props.data; |
|
|
| 140 |
entity.property_count = (int)props.length; |
|
|
| 141 |
entity.brushes = brushes.data; |
|
|
| 142 |
entity.brush_count = (int)brushes.length; |
|
|
| 143 |
array_push(entities, entity); |
|
|
| 144 |
} |
|
|
| 145 |
} |
|
|
| 146 |
|
149 |
|
| 147 |
map.entities = entities.data; |
150 |
map.entities = entities.data; |
| 148 |
map.entity_count = (int)entities.length; |
151 |
map.entity_count = (int)entities.length; |
| 149 |
vfs_free(data); |
152 |
vfs_free(data); |
| 150 |
return map; |
153 |
return map; |
| 151 |
} |
154 |
} |
| 152 |
|
155 |
|
| 153 |
void FreeMap(Map map) { |
156 |
void FreeMap(Map map) { |
| 154 |
for (int i = 0; i < map.entity_count; i++) { |
157 |
for (int i = 0; i < map.entity_count; i++) { |
| 155 |
MapEntity *e = &map.entities[i]; |
158 |
MapEntity *e = &map.entities[i]; |
| 156 |
if (e->properties) free(e->properties); |
159 |
if (e->properties) free(e->properties); |
| 157 |
for (int j = 0; j < e->brush_count; j++) { |
160 |
for (int j = 0; j < e->brush_count; j++) { |
| 158 |
if (e->brushes[j].planes) free(e->brushes[j].planes); |
161 |
if (e->brushes[j].planes) free(e->brushes[j].planes); |
| 159 |
} |
162 |
} |
| 160 |
if (e->brushes) free(e->brushes); |
163 |
if (e->brushes) free(e->brushes); |
| 161 |
} |
164 |
} |
| 162 |
if (map.entities) free(map.entities); |
165 |
if (map.entities) free(map.entities); |
| 163 |
} |
166 |
} |
| 164 |
|
167 |
|
| 165 |
Plane PlaneFromPoints(Vector3 p1, Vector3 p2, Vector3 p3) { |
168 |
Plane PlaneFromPoints(Vector3 p1, Vector3 p2, Vector3 p3) { |
| 166 |
Vector3 v1 = Vector3Subtract(p2, p1); |
169 |
Vector3 v1 = Vector3Subtract(p2, p1); |
| 167 |
Vector3 v2 = Vector3Subtract(p3, p1); |
170 |
Vector3 v2 = Vector3Subtract(p3, p1); |
| 168 |
Vector3 normal = Vector3Normalize(Vector3CrossProduct(v1, v2)); |
171 |
Vector3 normal = Vector3Normalize(Vector3CrossProduct(v1, v2)); |
| 169 |
return (Plane){ normal, Vector3DotProduct(normal, p1) }; |
172 |
return (Plane){ normal, Vector3DotProduct(normal, p1) }; |
| 170 |
} |
173 |
} |
| 171 |
|
174 |
|
| 172 |
void PolyFree(Polygon p) { |
175 |
void PolyFree(Polygon p) { |
| 173 |
if (p.verts) free(p.verts); |
176 |
if (p.verts) free(p.verts); |
| 174 |
} |
177 |
} |
| 175 |
|
178 |
|
| 176 |
Polygon PolyClip(Polygon poly, Plane plane) { |
179 |
Polygon PolyClip(Polygon poly, Plane plane) { |
| 177 |
if (poly.count == 0) return poly; |
180 |
if (poly.count == 0) return poly; |
| 178 |
|
181 |
|
| 179 |
array(Vector3) out_verts; |
182 |
array(Vector3) out_verts; |
| 180 |
array_init(out_verts); |
183 |
array_init(out_verts); |
| 181 |
|
184 |
|
| 182 |
for (int i = 0; i < poly.count; i++) { |
185 |
for (int i = 0; i < poly.count; i++) { |
| 183 |
Vector3 p1 = poly.verts[i]; |
186 |
Vector3 p1 = poly.verts[i]; |
| 184 |
Vector3 p2 = poly.verts[(i + 1) % poly.count]; |
187 |
Vector3 p2 = poly.verts[(i + 1) % poly.count]; |
| 185 |
|
188 |
|
| 186 |
float d1 = Vector3DotProduct(plane.normal, p1) - plane.dist; |
189 |
float d1 = Vector3DotProduct(plane.normal, p1) - plane.dist; |
| 187 |
float d2 = Vector3DotProduct(plane.normal, p2) - plane.dist; |
190 |
float d2 = Vector3DotProduct(plane.normal, p2) - plane.dist; |
| 188 |
|
191 |
|
| 189 |
// Using a smaller epsilon and more robust logic |
192 |
const float eps = 0.001f; |
| 190 |
const float eps = 0.001f; |
|
|
| 191 |
|
193 |
|
| 192 |
if (d1 >= -eps) { |
194 |
if (d1 >= -eps) { |
| 193 |
if (d2 >= -eps) { |
195 |
if (d2 >= -eps) { |
| 194 |
array_push(out_verts, p2); |
196 |
array_push(out_verts, p2); |
| 195 |
} else { |
197 |
} else { |
| 196 |
float t = d1 / (d1 - d2); |
198 |
float t = d1 / (d1 - d2); |
| 197 |
Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t)); |
199 |
Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t)); |
| 198 |
array_push(out_verts, intersect); |
200 |
array_push(out_verts, intersect); |
| 199 |
} |
201 |
} |
| 200 |
} else { |
202 |
} else { |
| 201 |
if (d2 >= -eps) { |
203 |
if (d2 >= -eps) { |
| 202 |
float t = d1 / (d1 - d2); |
204 |
float t = d1 / (d1 - d2); |
| 203 |
Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t)); |
205 |
Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t)); |
| 204 |
array_push(out_verts, intersect); |
206 |
array_push(out_verts, intersect); |
| 205 |
array_push(out_verts, p2); |
207 |
array_push(out_verts, p2); |
| 206 |
} |
208 |
} |
| 207 |
} |
209 |
} |
| 208 |
} |
210 |
} |
| 209 |
|
211 |
|
| 210 |
Polygon res; |
212 |
Polygon res; |
| 211 |
res.verts = out_verts.data; |
213 |
res.verts = out_verts.data; |
| 212 |
res.count = (int)out_verts.length; |
214 |
res.count = (int)out_verts.length; |
| 213 |
return res; |
215 |
return res; |
| 214 |
} |
216 |
} |
| 215 |
|
217 |
|
| 216 |
Polygon CreateLargeQuad(Plane plane) { |
218 |
Polygon CreateLargeQuad(Plane plane) { |
| 217 |
Vector3 n = plane.normal; |
219 |
Vector3 n = plane.normal; |
| 218 |
Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 }; |
220 |
Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 }; |
| 219 |
Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n)); |
221 |
Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n)); |
| 220 |
up = Vector3CrossProduct(n, right); |
222 |
up = Vector3CrossProduct(n, right); |
| 221 |
|
223 |
|
| 222 |
Vector3 center = Vector3Scale(n, plane.dist); |
224 |
Vector3 center = Vector3Scale(n, plane.dist); |
| 223 |
float size = 10000.0f; |
225 |
float size = 10000.0f; |
| 224 |
|
226 |
|
| 225 |
Polygon poly; |
227 |
Polygon poly; |
| 226 |
poly.verts = ALLOC(Vector3, 4); |
228 |
poly.verts = ALLOC(Vector3, 4); |
| 227 |
poly.count = 4; |
229 |
poly.count = 4; |
| 228 |
poly.verts[0] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, size))); |
230 |
poly.verts[0] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, size))); |
| 229 |
poly.verts[1] = Vector3Add(center, Vector3Add(Vector3Scale(right, -size), Vector3Scale(up, size))); |
231 |
poly.verts[1] = Vector3Add(center, Vector3Add(Vector3Scale(right, -size), Vector3Scale(up, size))); |
| 230 |
poly.verts[2] = Vector3Add(center, Vector3Add(Vector3Scale(right, -size), Vector3Scale(up, -size))); |
232 |
poly.verts[2] = Vector3Add(center, Vector3Add(Vector3Scale(right, -size), Vector3Scale(up, -size))); |
| 231 |
poly.verts[3] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, -size))); |
233 |
poly.verts[3] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, -size))); |
| 232 |
|
234 |
|
| 233 |
return poly; |
235 |
return poly; |
| 234 |
} |
236 |
} |
| 235 |
|
237 |
|
| 236 |
Vector2 GetUV(Vector3 p, MapPlane *mp, Plane plane) { |
238 |
Vector2 GetUV(Vector3 p, MapPlane *mp, Plane plane) { |
| 237 |
Vector3 n = plane.normal; |
239 |
Vector3 n = plane.normal; |
| 238 |
Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 }; |
240 |
Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 }; |
| 239 |
Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n)); |
241 |
Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n)); |
| 240 |
up = Vector3CrossProduct(n, right); |
242 |
up = Vector3CrossProduct(n, right); |
| 241 |
|
243 |
|
| 242 |
float u = Vector3DotProduct(p, right) * (1.0f / (mp->scale[0] ? mp->scale[0] : 1.0f)) + mp->shift[0]; |
244 |
float u = Vector3DotProduct(p, right) * (1.0f / (mp->scale[0] ? mp->scale[0] : 1.0f)) + mp->shift[0]; |
| 243 |
float v = Vector3DotProduct(p, up) * (1.0f / (mp->scale[1] ? mp->scale[1] : 1.0f)) + mp->shift[1]; |
245 |
float v = Vector3DotProduct(p, up) * (1.0f / (mp->scale[1] ? mp->scale[1] : 1.0f)) + mp->shift[1]; |
| 244 |
|
246 |
|
| 245 |
return (Vector2){ u / 64.0f, v / 64.0f }; |
247 |
return (Vector2){ u / 64.0f, v / 64.0f }; |
|
|
248 |
} |
|
|
249 |
|
|
|
250 |
void UnloadMap(void) { |
|
|
251 |
if (game.map.models) { |
|
|
252 |
for (int i = 0; i < game.map.count; i++) { |
|
|
253 |
UnloadModel(game.map.models[i]); |
|
|
254 |
} |
|
|
255 |
free(game.map.models); |
|
|
256 |
game.map.models = NULL; |
|
|
257 |
game.map.count = 0; |
|
|
258 |
} |
|
|
259 |
} |
|
|
260 |
|
|
|
261 |
bool LoadMap(const char *filename) { |
|
|
262 |
TraceLog(LOG_INFO, "Loading map: %s", filename); |
|
|
263 |
|
|
|
264 |
Map map = ParseMap(filename); |
|
|
265 |
if (map.entity_count == 0) { |
|
|
266 |
TraceLog(LOG_ERROR, "Failed to load map or map is empty: %s", filename); |
|
|
267 |
return false; |
|
|
268 |
} |
|
|
269 |
|
|
|
270 |
UnloadMap(); |
|
|
271 |
|
|
|
272 |
array(TextureGroup) groups; |
|
|
273 |
array_init(groups); |
|
|
274 |
|
|
|
275 |
// Default player state if no start found |
|
|
276 |
game.player.pos = (Vector3){ 0, 10, 0 }; |
|
|
277 |
game.player.yaw = 0; |
|
|
278 |
game.player.pitch = 0; |
|
|
279 |
|
|
|
280 |
int total_brushes = 0; |
|
|
281 |
for (int i = 0; i < map.entity_count; i++) { |
|
|
282 |
MapEntity *e = &map.entities[i]; |
|
|
283 |
bool is_world = false; |
|
|
284 |
const char *classname = ""; |
|
|
285 |
|
|
|
286 |
for (int j = 0; j < e->property_count; j++) { |
|
|
287 |
if (strcmp(e->properties[j].key, "classname") == 0) { |
|
|
288 |
classname = e->properties[j].value; |
|
|
289 |
if (strcmp(classname, "worldspawn") == 0) is_world = true; |
|
|
290 |
} |
|
|
291 |
} |
|
|
292 |
|
|
|
293 |
// Handle entities (like player start) |
|
|
294 |
for (int j = 0; j < e->property_count; j++) { |
|
|
295 |
if (strcmp(e->properties[j].key, "origin") == 0) { |
|
|
296 |
float x, y, z; |
|
|
297 |
sscanf(e->properties[j].value, "%f %f %f", &x, &y, &z); |
|
|
298 |
if (strcmp(classname, "info_player_start") == 0) { |
|
|
299 |
game.player.pos = (Vector3){ x, z, -y }; |
|
|
300 |
float angle = 0; |
|
|
301 |
for (int k = 0; k < e->property_count; k++) { |
|
|
302 |
if (strcmp(e->properties[k].key, "angle") == 0) { |
|
|
303 |
angle = (float)atof(e->properties[k].value); |
|
|
304 |
} |
|
|
305 |
} |
|
|
306 |
game.player.yaw = (angle + 90.0f) * DEG2RAD; |
|
|
307 |
game.player.pitch = 0; |
|
|
308 |
TraceLog(LOG_INFO, "Player spawn set to: %f, %f, %f", game.player.pos.x, game.player.pos.y, game.player.pos.z); |
|
|
309 |
} |
|
|
310 |
} |
|
|
311 |
} |
|
|
312 |
|
|
|
313 |
if (is_world) { |
|
|
314 |
total_brushes += e->brush_count; |
|
|
315 |
for (int j = 0; j < e->brush_count; j++) { |
|
|
316 |
MapBrush *brush = &e->brushes[j]; |
|
|
317 |
for (int p_idx = 0; p_idx < brush->plane_count; p_idx++) { |
|
|
318 |
MapPlane *mp = &brush->planes[p_idx]; |
|
|
319 |
Plane plane = PlaneFromPoints(mp->p[0], mp->p[1], mp->p[2]); |
|
|
320 |
|
|
|
321 |
if (Vector3Length(plane.normal) < 0.5f) continue; |
|
|
322 |
|
|
|
323 |
Polygon poly = CreateLargeQuad(plane); |
|
|
324 |
for (int k = 0; k < brush->plane_count; k++) { |
|
|
325 |
if (p_idx == k) continue; |
|
|
326 |
Plane clipPlane = PlaneFromPoints(brush->planes[k].p[0], brush->planes[k].p[1], brush->planes[k].p[2]); |
|
|
327 |
if (Vector3Length(clipPlane.normal) < 0.5f) continue; |
|
|
328 |
|
|
|
329 |
Polygon next = PolyClip(poly, clipPlane); |
|
|
330 |
PolyFree(poly); |
|
|
331 |
poly = next; |
|
|
332 |
} |
|
|
333 |
|
|
|
334 |
if (poly.count >= 3) { |
|
|
335 |
TextureGroup *group = NULL; |
|
|
336 |
for (int g = 0; g < groups.length; g++) { |
|
|
337 |
if (strcmp(groups.data[g].texture, mp->texture) == 0) { |
|
|
338 |
group = &groups.data[g]; |
|
|
339 |
break; |
|
|
340 |
} |
|
|
341 |
} |
|
|
342 |
if (!group) { |
|
|
343 |
TextureGroup new_group = { 0 }; |
|
|
344 |
strncpy(new_group.texture, mp->texture, sizeof(new_group.texture)); |
|
|
345 |
array_init(new_group.vertices); |
|
|
346 |
array_init(new_group.texcoords); |
|
|
347 |
array_init(new_group.normals); |
|
|
348 |
array_push(groups, new_group); |
|
|
349 |
group = &groups.data[groups.length - 1]; |
|
|
350 |
} |
|
|
351 |
for (int v = 1; v < poly.count - 1; v++) { |
|
|
352 |
Vector3 v0 = poly.verts[0]; |
|
|
353 |
Vector3 v1 = poly.verts[v+1]; |
|
|
354 |
Vector3 v2 = poly.verts[v]; |
|
|
355 |
array_push(group->vertices, v0.x); array_push(group->vertices, v0.y); array_push(group->vertices, v0.z); |
|
|
356 |
array_push(group->vertices, v1.x); array_push(group->vertices, v1.y); array_push(group->vertices, v1.z); |
|
|
357 |
array_push(group->vertices, v2.x); array_push(group->vertices, v2.y); array_push(group->vertices, v2.z); |
|
|
358 |
Vector2 uv0 = GetUV(v0, mp, plane); |
|
|
359 |
Vector2 uv1 = GetUV(v1, mp, plane); |
|
|
360 |
Vector2 uv2 = GetUV(v2, mp, plane); |
|
|
361 |
array_push(group->texcoords, uv0.x); array_push(group->texcoords, uv0.y); |
|
|
362 |
array_push(group->texcoords, uv1.x); array_push(group->texcoords, uv1.y); |
|
|
363 |
array_push(group->texcoords, uv2.x); array_push(group->texcoords, uv2.y); |
|
|
364 |
Vector3 out_normal = Vector3Scale(plane.normal, -1.0f); |
|
|
365 |
array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z); |
|
|
366 |
array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z); |
|
|
367 |
array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z); |
|
|
368 |
} |
|
|
369 |
} |
|
|
370 |
PolyFree(poly); |
|
|
371 |
} |
|
|
372 |
} |
|
|
373 |
} |
|
|
374 |
} |
|
|
375 |
|
|
|
376 |
array(Model) final_models; |
|
|
377 |
array_init(final_models); |
|
|
378 |
|
|
|
379 |
for (int i = 0; i < groups.length; i++) { |
|
|
380 |
TextureGroup *g = &groups.data[i]; |
|
|
381 |
if (g->vertices.length == 0) continue; |
|
|
382 |
Mesh mesh = { 0 }; |
|
|
383 |
mesh.vertexCount = (int)g->vertices.length / 3; |
|
|
384 |
mesh.triangleCount = mesh.vertexCount / 3; |
|
|
385 |
mesh.vertices = (float *)malloc(g->vertices.length * sizeof(float)); |
|
|
386 |
memcpy(mesh.vertices, g->vertices.data, g->vertices.length * sizeof(float)); |
|
|
387 |
mesh.texcoords = (float *)malloc(g->texcoords.length * sizeof(float)); |
|
|
388 |
memcpy(mesh.texcoords, g->texcoords.data, g->texcoords.length * sizeof(float)); |
|
|
389 |
mesh.normals = (float *)malloc(g->normals.length * sizeof(float)); |
|
|
390 |
memcpy(mesh.normals, g->normals.data, g->normals.length * sizeof(float)); |
|
|
391 |
UploadMesh(&mesh, false); |
|
|
392 |
Model model = LoadModelFromMesh(mesh); |
|
|
393 |
model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = GetTexture(g->texture); |
|
|
394 |
array_push(final_models, model); |
|
|
395 |
array_free(g->vertices); |
|
|
396 |
array_free(g->texcoords); |
|
|
397 |
array_free(g->normals); |
|
|
398 |
} |
|
|
399 |
|
|
|
400 |
game.map.models = final_models.data; |
|
|
401 |
game.map.count = (int)final_models.length; |
|
|
402 |
array_free(groups); |
|
|
403 |
|
|
|
404 |
FreeMap(map); |
|
|
405 |
TraceLog(LOG_INFO, "Processed %d brushes into %d models", total_brushes, game.map.count); |
|
|
406 |
return true; |
|
|
407 |
} |
|
|
408 |
|
|
|
409 |
bool CheckMapCollision(Vector3 start, Vector3 end, RayCollision *outCollision) { |
|
|
410 |
Vector3 diff = Vector3Subtract(end, start); |
|
|
411 |
float maxDist = Vector3Length(diff); |
|
|
412 |
if (maxDist < 0.001f) return false; |
|
|
413 |
|
|
|
414 |
Ray ray = { 0 }; |
|
|
415 |
ray.position = start; |
|
|
416 |
ray.direction = Vector3Scale(diff, 1.0f / maxDist); |
|
|
417 |
|
|
|
418 |
RayCollision closest = { 0 }; |
|
|
419 |
closest.distance = FLT_MAX; |
|
|
420 |
closest.hit = false; |
|
|
421 |
|
|
|
422 |
for (int i = 0; i < game.map.count; i++) { |
|
|
423 |
Model model = game.map.models[i]; |
|
|
424 |
for (int m = 0; m < model.meshCount; m++) { |
|
|
425 |
RayCollision col = GetRayCollisionMesh(ray, model.meshes[m], model.transform); |
|
|
426 |
if (col.hit && col.distance < closest.distance && col.distance <= maxDist) { |
|
|
427 |
closest = col; |
|
|
428 |
} |
|
|
429 |
} |
|
|
430 |
} |
|
|
431 |
|
|
|
432 |
if (closest.hit) { |
|
|
433 |
if (outCollision) *outCollision = closest; |
|
|
434 |
return true; |
|
|
435 |
} |
|
|
436 |
return false; |
| 246 |
} |
437 |
} |
|
diff --git a/player.c b/player.c
|
| 1 |
#include "all.h" |
1 |
#include "all.h" |
| 2 |
#include "raymath.h" |
2 |
#include "raymath.h" |
|
|
3 |
|
| 3 |
#include <float.h> |
4 |
#include <float.h> |
| 4 |
|
5 |
|
| 5 |
static void PlayerRotate(void) { |
6 |
static void PlayerRotate(void) { |
| 6 |
if (!game.cursor_captured) return; |
7 |
if (!game.cursor_captured) return; |
| 7 |
|
8 |
|
| 8 |
Vector2 mouseDelta = GetMouseDelta(); |
9 |
Vector2 mouseDelta = GetMouseDelta(); |
| 9 |
game.yaw += -mouseDelta.x * PLAYER_MOUSE_SENSITIVITY; |
10 |
game.player.yaw += -mouseDelta.x * PLAYER_MOUSE_SENSITIVITY; |
| 10 |
game.pitch += mouseDelta.y * PLAYER_MOUSE_SENSITIVITY; |
11 |
game.player.pitch += mouseDelta.y * PLAYER_MOUSE_SENSITIVITY; |
| 11 |
|
|
|
| 12 |
// Clamp pitch to avoid gimbal lock/flipping (approx 89 degrees) |
|
|
| 13 |
if (game.pitch > 89.0f * DEG2RAD) game.pitch = 89.0f * DEG2RAD; |
|
|
| 14 |
if (game.pitch < -89.0f * DEG2RAD) game.pitch = -89.0f * DEG2RAD; |
|
|
| 15 |
} |
|
|
| 16 |
|
|
|
| 17 |
static bool CheckWorldCollision(Vector3 start, Vector3 end, RayCollision *outCollision) { |
|
|
| 18 |
Vector3 diff = Vector3Subtract(end, start); |
|
|
| 19 |
float maxDist = Vector3Length(diff); |
|
|
| 20 |
if (maxDist < 0.001f) return false; |
|
|
| 21 |
|
|
|
| 22 |
Ray ray = { 0 }; |
|
|
| 23 |
ray.position = start; |
|
|
| 24 |
ray.direction = Vector3Scale(diff, 1.0f / maxDist); |
|
|
| 25 |
|
|
|
| 26 |
RayCollision closest = { 0 }; |
|
|
| 27 |
closest.distance = FLT_MAX; |
|
|
| 28 |
closest.hit = false; |
|
|
| 29 |
|
|
|
| 30 |
for (int i = 0; i < game.world_model_count; i++) { |
|
|
| 31 |
Model model = game.world_models[i]; |
|
|
| 32 |
for (int m = 0; m < model.meshCount; m++) { |
|
|
| 33 |
RayCollision col = GetRayCollisionMesh(ray, model.meshes[m], model.transform); |
|
|
| 34 |
if (col.hit && col.distance < closest.distance && col.distance <= maxDist) { |
|
|
| 35 |
closest = col; |
|
|
| 36 |
} |
|
|
| 37 |
} |
|
|
| 38 |
} |
|
|
| 39 |
|
12 |
|
| 40 |
if (closest.hit) { |
13 |
if (game.player.pitch > 89.0f * DEG2RAD) game.player.pitch = 89.0f * DEG2RAD; |
| 41 |
if (outCollision) *outCollision = closest; |
14 |
if (game.player.pitch < -89.0f * DEG2RAD) game.player.pitch = -89.0f * DEG2RAD; |
| 42 |
return true; |
|
|
| 43 |
} |
|
|
| 44 |
return false; |
|
|
| 45 |
} |
15 |
} |
| 46 |
|
16 |
|
| 47 |
static void MoveNormal(float dt) { |
17 |
static void MoveNormal(float dt) { |
| 48 |
float eyeHeight = PLAYER_EYE_HEIGHT - (game.crouch_amount * PLAYER_CROUCH_OFFSET); |
18 |
float eyeHeight = PLAYER_EYE_HEIGHT - (game.player.crouch_amount * PLAYER_CROUCH_OFFSET); |
| 49 |
|
19 |
|
| 50 |
// Movement vectors based on yaw |
20 |
Vector3 forward = { sinf(game.player.yaw), 0, cosf(game.player.yaw) }; |
| 51 |
Vector3 forward = { sinf(game.yaw), 0, cosf(game.yaw) }; |
21 |
Vector3 right = { sinf(game.player.yaw - PI/2.0f), 0, cosf(game.player.yaw - PI/2.0f) }; |
| 52 |
Vector3 right = { sinf(game.yaw - PI/2.0f), 0, cosf(game.yaw - PI/2.0f) }; |
|
|
| 53 |
|
22 |
|
| 54 |
Vector3 moveDir = { 0 }; |
23 |
Vector3 moveDir = { 0 }; |
| 55 |
if (IsKeyDown(KEY_W)) moveDir = Vector3Add(moveDir, forward); |
24 |
if (IsKeyDown(KEY_W)) moveDir = Vector3Add(moveDir, forward); |
| ... |
| 59 |
|
28 |
|
| 60 |
float speed = PLAYER_MOVE_SPEED; |
29 |
float speed = PLAYER_MOVE_SPEED; |
| 61 |
if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) speed *= PLAYER_SPRINT_MULTIPLIER; |
30 |
if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) speed *= PLAYER_SPRINT_MULTIPLIER; |
| 62 |
|
31 |
speed *= Lerp(1.0f, PLAYER_CROUCH_MOVE_MULTIPLIER, game.player.crouch_amount); |
| 63 |
// Apply crouch speed penalty |
|
|
| 64 |
speed *= Lerp(1.0f, PLAYER_CROUCH_MOVE_MULTIPLIER, game.crouch_amount); |
|
|
| 65 |
|
32 |
|
| 66 |
if (Vector3Length(moveDir) > 0) { |
33 |
if (Vector3Length(moveDir) > 0) { |
| 67 |
moveDir = Vector3Scale(Vector3Normalize(moveDir), speed * dt); |
34 |
moveDir = Vector3Scale(Vector3Normalize(moveDir), speed * dt); |
| 68 |
} |
35 |
} |
| 69 |
|
36 |
|
| 70 |
// Apply gravity |
37 |
game.player.velocity.y -= PLAYER_GRAVITY * dt; |
| 71 |
game.velocity.y -= PLAYER_GRAVITY * dt; |
|
|
| 72 |
|
38 |
|
| 73 |
// Jump |
39 |
if (game.player.is_grounded && IsKeyPressed(KEY_SPACE)) { |
| 74 |
if (game.is_grounded && IsKeyPressed(KEY_SPACE)) { |
40 |
game.player.velocity.y = PLAYER_JUMP_FORCE; |
| 75 |
game.velocity.y = PLAYER_JUMP_FORCE; |
41 |
game.player.is_grounded = false; |
| 76 |
game.is_grounded = false; |
|
|
| 77 |
} |
42 |
} |
| 78 |
|
43 |
|
| 79 |
// Horizontal movement with sliding collision |
|
|
| 80 |
Vector3 remainingMove = moveDir; |
44 |
Vector3 remainingMove = moveDir; |
| 81 |
for (int iter = 0; iter < 4 && Vector3Length(remainingMove) > 0.001f; iter++) { |
45 |
for (int iter = 0; iter < 4 && Vector3Length(remainingMove) > 0.001f; iter++) { |
| 82 |
RayCollision closestHit = { 0 }; |
46 |
RayCollision closestHit = { 0 }; |
| ... |
| 84 |
bool hitFound = false; |
48 |
bool hitFound = false; |
| 85 |
|
49 |
|
| 86 |
float heights[] = { -eyeHeight + 10.0f, -eyeHeight / 2.0f, 0.0f }; |
50 |
float heights[] = { -eyeHeight + 10.0f, -eyeHeight / 2.0f, 0.0f }; |
| 87 |
Vector3 currentPos = game.pos; |
51 |
Vector3 currentPos = game.player.pos; |
| 88 |
|
52 |
|
| 89 |
for (int i = 0; i < 3; i++) { |
53 |
for (int i = 0; i < 3; i++) { |
| 90 |
Vector3 start = Vector3Add(currentPos, (Vector3){0, heights[i], 0}); |
54 |
Vector3 start = Vector3Add(currentPos, (Vector3){0, heights[i], 0}); |
| ... |
| 93 |
Vector3 end = Vector3Add(start, Vector3Scale(dir, dist)); |
57 |
Vector3 end = Vector3Add(start, Vector3Scale(dir, dist)); |
| 94 |
|
58 |
|
| 95 |
RayCollision col; |
59 |
RayCollision col; |
| 96 |
if (CheckWorldCollision(start, end, &col)) { |
60 |
if (CheckMapCollision(start, end, &col)) { |
| 97 |
// Adjust distance to be relative to the player boundary |
|
|
| 98 |
float adjustedDist = col.distance - PLAYER_RADIUS; |
61 |
float adjustedDist = col.distance - PLAYER_RADIUS; |
| 99 |
if (adjustedDist < closestHit.distance) { |
62 |
if (adjustedDist < closestHit.distance) { |
| 100 |
closestHit = col; |
63 |
closestHit = col; |
| ... |
| 105 |
} |
68 |
} |
| 106 |
|
69 |
|
| 107 |
if (hitFound) { |
70 |
if (hitFound) { |
| 108 |
// Move as far as possible |
|
|
| 109 |
float moveDist = fmaxf(0, closestHit.distance - 0.1f); |
71 |
float moveDist = fmaxf(0, closestHit.distance - 0.1f); |
| 110 |
Vector3 moveStep = Vector3Scale(Vector3Normalize(remainingMove), moveDist); |
72 |
Vector3 moveStep = Vector3Scale(Vector3Normalize(remainingMove), moveDist); |
| 111 |
game.pos.x += moveStep.x; |
73 |
game.player.pos.x += moveStep.x; |
| 112 |
game.pos.z += moveStep.z; |
74 |
game.player.pos.z += moveStep.z; |
| 113 |
|
75 |
|
| 114 |
// Project remaining movement onto the plane of the wall |
|
|
| 115 |
Vector3 slideNormal = { closestHit.normal.x, 0, closestHit.normal.z }; |
76 |
Vector3 slideNormal = { closestHit.normal.x, 0, closestHit.normal.z }; |
| 116 |
if (Vector3Length(slideNormal) > 0.001f) { |
77 |
if (Vector3Length(slideNormal) > 0.001f) { |
| 117 |
slideNormal = Vector3Normalize(slideNormal); |
78 |
slideNormal = Vector3Normalize(slideNormal); |
| ... |
| 122 |
remainingMove = (Vector3){0, 0, 0}; |
83 |
remainingMove = (Vector3){0, 0, 0}; |
| 123 |
} |
84 |
} |
| 124 |
} else { |
85 |
} else { |
| 125 |
// No collision found, move the rest of the way |
86 |
game.player.pos.x += remainingMove.x; |
| 126 |
game.pos.x += remainingMove.x; |
87 |
game.player.pos.z += remainingMove.z; |
| 127 |
game.pos.z += remainingMove.z; |
|
|
| 128 |
break; |
88 |
break; |
| 129 |
} |
89 |
} |
| 130 |
} |
90 |
} |
| 131 |
|
91 |
|
| 132 |
// Vertical movement with collision |
92 |
float verticalMove = game.player.velocity.y * dt; |
| 133 |
float verticalMove = game.velocity.y * dt; |
93 |
Vector3 vStart = game.player.pos; |
| 134 |
Vector3 vStart = game.pos; |
|
|
| 135 |
|
94 |
|
| 136 |
if (verticalMove < 0) { // Falling/Down |
95 |
if (verticalMove < 0) { |
| 137 |
Vector3 start = vStart; |
96 |
Vector3 start = vStart; |
| 138 |
// Check slightly below feet |
|
|
| 139 |
Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove - eyeHeight, 0}); |
97 |
Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove - eyeHeight, 0}); |
| 140 |
RayCollision vCol; |
98 |
RayCollision vCol; |
| 141 |
if (CheckWorldCollision(start, end, &vCol) && vCol.normal.y > 0.5f) { |
99 |
if (CheckMapCollision(start, end, &vCol) && vCol.normal.y > 0.5f) { |
| 142 |
game.pos.y = vCol.point.y + eyeHeight; |
100 |
game.player.pos.y = vCol.point.y + eyeHeight; |
| 143 |
game.velocity.y = 0; |
101 |
game.player.velocity.y = 0; |
| 144 |
game.is_grounded = true; |
102 |
game.player.is_grounded = true; |
| 145 |
} else { |
103 |
} else { |
| 146 |
game.pos.y += verticalMove; |
104 |
game.player.pos.y += verticalMove; |
| 147 |
game.is_grounded = false; |
105 |
game.player.is_grounded = false; |
| 148 |
} |
106 |
} |
| 149 |
} else if (verticalMove > 0) { // Jumping/Up |
107 |
} else if (verticalMove > 0) { |
| 150 |
Vector3 start = vStart; |
108 |
Vector3 start = vStart; |
| 151 |
// Check above head |
|
|
| 152 |
float headHeight = PLAYER_HEIGHT - PLAYER_EYE_HEIGHT; |
109 |
float headHeight = PLAYER_HEIGHT - PLAYER_EYE_HEIGHT; |
| 153 |
Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove + headHeight, 0}); |
110 |
Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove + headHeight, 0}); |
| 154 |
RayCollision vCol; |
111 |
RayCollision vCol; |
| 155 |
if (CheckWorldCollision(start, end, &vCol)) { |
112 |
if (CheckMapCollision(start, end, &vCol)) { |
| 156 |
game.pos.y = vCol.point.y - headHeight - 1.0f; |
113 |
game.player.pos.y = vCol.point.y - headHeight - 1.0f; |
| 157 |
game.velocity.y = 0; |
114 |
game.player.velocity.y = 0; |
| 158 |
} else { |
115 |
} else { |
| 159 |
game.pos.y += verticalMove; |
116 |
game.player.pos.y += verticalMove; |
| 160 |
} |
117 |
} |
| 161 |
game.is_grounded = false; |
118 |
game.player.is_grounded = false; |
| 162 |
} else { |
119 |
} else { |
| 163 |
// Not moving vertically, but check if we are still on ground |
|
|
| 164 |
Vector3 start = vStart; |
120 |
Vector3 start = vStart; |
| 165 |
Vector3 end = Vector3Add(vStart, (Vector3){0, -eyeHeight - 2.0f, 0}); |
121 |
Vector3 end = Vector3Add(vStart, (Vector3){0, -eyeHeight - 2.0f, 0}); |
| 166 |
RayCollision vCol; |
122 |
RayCollision vCol; |
| 167 |
if (CheckWorldCollision(start, end, &vCol) && vCol.normal.y > 0.5f) { |
123 |
if (CheckMapCollision(start, end, &vCol) && vCol.normal.y > 0.5f) { |
| 168 |
game.is_grounded = true; |
124 |
game.player.is_grounded = true; |
| 169 |
// Snap to floor if very close |
|
|
| 170 |
if (vCol.distance < eyeHeight + 1.0f) { |
125 |
if (vCol.distance < eyeHeight + 1.0f) { |
| 171 |
game.pos.y = vCol.point.y + eyeHeight; |
126 |
game.player.pos.y = vCol.point.y + eyeHeight; |
| 172 |
} |
127 |
} |
| 173 |
} else { |
128 |
} else { |
| 174 |
game.is_grounded = false; |
129 |
game.player.is_grounded = false; |
| 175 |
} |
130 |
} |
| 176 |
} |
131 |
} |
| 177 |
} |
132 |
} |
| 178 |
|
133 |
|
| 179 |
static void MoveFly(float dt) { |
134 |
static void MoveFly(float dt) { |
| 180 |
// Full 3D movement based on yaw/pitch |
|
|
| 181 |
Vector3 forward = { |
135 |
Vector3 forward = { |
| 182 |
cosf(game.pitch) * sinf(game.yaw), |
136 |
cosf(game.player.pitch) * sinf(game.player.yaw), |
| 183 |
-sinf(game.pitch), |
137 |
-sinf(game.player.pitch), |
| 184 |
cosf(game.pitch) * cosf(game.yaw) |
138 |
cosf(game.player.pitch) * cosf(game.player.yaw) |
| 185 |
}; |
139 |
}; |
| 186 |
Vector3 right = { sinf(game.yaw - PI/2.0f), 0, cosf(game.yaw - PI/2.0f) }; |
140 |
Vector3 right = { sinf(game.player.yaw - PI/2.0f), 0, cosf(game.player.yaw - PI/2.0f) }; |
| 187 |
|
141 |
|
| 188 |
Vector3 move = { 0 }; |
142 |
Vector3 move = { 0 }; |
| 189 |
if (IsKeyDown(KEY_W)) move = Vector3Add(move, forward); |
143 |
if (IsKeyDown(KEY_W)) move = Vector3Add(move, forward); |
| ... |
| 191 |
if (IsKeyDown(KEY_D)) move = Vector3Add(move, right); |
145 |
if (IsKeyDown(KEY_D)) move = Vector3Add(move, right); |
| 192 |
if (IsKeyDown(KEY_A)) move = Vector3Subtract(move, right); |
146 |
if (IsKeyDown(KEY_A)) move = Vector3Subtract(move, right); |
| 193 |
|
147 |
|
| 194 |
// Fly up/down |
|
|
| 195 |
if (IsKeyDown(KEY_SPACE)) move.y += 1.0f; |
148 |
if (IsKeyDown(KEY_SPACE)) move.y += 1.0f; |
| 196 |
if (IsKeyDown(KEY_LEFT_CONTROL)) move.y -= 1.0f; |
149 |
if (IsKeyDown(KEY_LEFT_CONTROL)) move.y -= 1.0f; |
| 197 |
|
150 |
|
| ... |
| 200 |
|
153 |
|
| 201 |
if (Vector3Length(move) > 0) { |
154 |
if (Vector3Length(move) > 0) { |
| 202 |
move = Vector3Scale(Vector3Normalize(move), speed * dt); |
155 |
move = Vector3Scale(Vector3Normalize(move), speed * dt); |
| 203 |
game.pos = Vector3Add(game.pos, move); |
156 |
game.player.pos = Vector3Add(game.player.pos, move); |
| 204 |
} |
157 |
} |
| 205 |
|
158 |
|
| 206 |
game.velocity = (Vector3){0, 0, 0}; |
159 |
game.player.velocity = (Vector3){0, 0, 0}; |
| 207 |
game.is_grounded = false; |
160 |
game.player.is_grounded = false; |
| 208 |
} |
161 |
} |
| 209 |
|
162 |
|
| 210 |
void UpdatePlayer(void) { |
163 |
void UpdatePlayer(void) { |
| 211 |
float dt = GetFrameTime(); |
164 |
float dt = GetFrameTime(); |
| 212 |
Vector3 oldPos = game.pos; |
165 |
Vector3 oldPos = game.player.pos; |
| 213 |
|
166 |
|
| 214 |
if (IsKeyPressed(KEY_F)) { |
167 |
if (IsKeyPressed(KEY_F)) { |
| 215 |
game.move_mode = (game.move_mode == MOVE_NORMAL) ? MOVE_FLY : MOVE_NORMAL; |
168 |
game.player.move_mode = (game.player.move_mode == MOVE_NORMAL) ? MOVE_FLY : MOVE_NORMAL; |
| 216 |
TraceLog(LOG_INFO, "Movement mode: %s", (game.move_mode == MOVE_NORMAL) ? "NORMAL" : "FLY"); |
|
|
| 217 |
} |
169 |
} |
| 218 |
|
170 |
|
| 219 |
PlayerRotate(); |
171 |
PlayerRotate(); |
| 220 |
|
172 |
|
| 221 |
// Crouching logic |
|
|
| 222 |
bool canStand = true; |
173 |
bool canStand = true; |
| 223 |
if (game.crouch_amount > 0.1f) { |
174 |
if (game.player.crouch_amount > 0.1f) { |
| 224 |
Vector3 headPos = game.pos; |
175 |
Vector3 headPos = game.player.pos; |
| 225 |
Vector3 headEnd = Vector3Add(headPos, (Vector3){0, PLAYER_HEIGHT - PLAYER_EYE_HEIGHT + PLAYER_CROUCH_OFFSET, 0}); |
176 |
Vector3 headEnd = Vector3Add(headPos, (Vector3){0, PLAYER_HEIGHT - PLAYER_EYE_HEIGHT + PLAYER_CROUCH_OFFSET, 0}); |
| 226 |
RayCollision col; |
177 |
RayCollision col; |
| 227 |
if (CheckWorldCollision(headPos, headEnd, &col)) { |
178 |
if (CheckMapCollision(headPos, headEnd, &col)) { |
| 228 |
canStand = false; |
179 |
canStand = false; |
| 229 |
} |
180 |
} |
| 230 |
} |
181 |
} |
| 231 |
|
182 |
|
| 232 |
float targetCrouch = 0.0f; |
183 |
float targetCrouch = 0.0f; |
| 233 |
if (IsKeyDown(KEY_LEFT_CONTROL) || !canStand) targetCrouch = 1.0f; |
184 |
if (IsKeyDown(KEY_LEFT_CONTROL) || !canStand) targetCrouch = 1.0f; |
| 234 |
game.crouch_amount = Lerp(game.crouch_amount, targetCrouch, PLAYER_CROUCH_SPEED * dt); |
185 |
game.player.crouch_amount = Lerp(game.player.crouch_amount, targetCrouch, PLAYER_CROUCH_SPEED * dt); |
| 235 |
|
186 |
|
| 236 |
// Leaning logic |
|
|
| 237 |
float targetLean = 0.0f; |
187 |
float targetLean = 0.0f; |
| 238 |
if (IsKeyDown(KEY_Q)) targetLean -= 1.0f; |
188 |
if (IsKeyDown(KEY_Q)) targetLean -= 1.0f; |
| 239 |
if (IsKeyDown(KEY_E)) targetLean += 1.0f; |
189 |
if (IsKeyDown(KEY_E)) targetLean += 1.0f; |
| 240 |
|
190 |
game.player.lean_amount = Lerp(game.player.lean_amount, targetLean, PLAYER_LEAN_SPEED * dt); |
| 241 |
game.lean_amount = Lerp(game.lean_amount, targetLean, PLAYER_LEAN_SPEED * dt); |
|
|
| 242 |
|
191 |
|
| 243 |
if (game.move_mode == MOVE_FLY) { |
192 |
if (game.player.move_mode == MOVE_FLY) { |
| 244 |
MoveFly(dt); |
193 |
MoveFly(dt); |
| 245 |
} else { |
194 |
} else { |
| 246 |
MoveNormal(dt); |
195 |
MoveNormal(dt); |
| 247 |
} |
196 |
} |
| 248 |
|
197 |
|
| 249 |
// Apply lean as a pivot from the neck/waist |
198 |
float leanAngle = game.player.lean_amount * PLAYER_LEAN_ANGLE * DEG2RAD; |
| 250 |
float leanAngle = game.lean_amount * PLAYER_LEAN_ANGLE * DEG2RAD; |
199 |
Vector3 bodyForward = { sinf(game.player.yaw), 0, cosf(game.player.yaw) }; |
| 251 |
Vector3 bodyForward = { sinf(game.yaw), 0, cosf(game.yaw) }; |
|
|
| 252 |
|
200 |
|
| 253 |
float currentEyeHeight = PLAYER_EYE_HEIGHT - (game.crouch_amount * PLAYER_CROUCH_OFFSET); |
201 |
float currentEyeHeight = PLAYER_EYE_HEIGHT - (game.player.crouch_amount * PLAYER_CROUCH_OFFSET); |
| 254 |
float pivotDist = fminf(PLAYER_LEAN_PIVOT_DISTANCE, currentEyeHeight * 0.8f); |
202 |
float pivotDist = fminf(PLAYER_LEAN_PIVOT_DISTANCE, currentEyeHeight * 0.8f); |
| 255 |
|
203 |
|
| 256 |
Vector3 neckToEye = { 0, pivotDist, 0 }; |
204 |
Vector3 neckToEye = { 0, pivotDist, 0 }; |
| 257 |
Vector3 rotatedOffset = Vector3RotateByAxisAngle(neckToEye, bodyForward, leanAngle); |
205 |
Vector3 rotatedOffset = Vector3RotateByAxisAngle(neckToEye, bodyForward, leanAngle); |
| 258 |
Vector3 pivot = Vector3Subtract(game.pos, (Vector3){ 0, pivotDist, 0 }); |
206 |
Vector3 pivot = Vector3Subtract(game.player.pos, (Vector3){ 0, pivotDist, 0 }); |
| 259 |
|
207 |
|
| 260 |
// Update camera based on physical pos + visual lean pivot |
|
|
| 261 |
game.camera.position = Vector3Add(pivot, rotatedOffset); |
208 |
game.camera.position = Vector3Add(pivot, rotatedOffset); |
| 262 |
|
209 |
|
| 263 |
// Apply roll to up vector |
|
|
| 264 |
Vector3 forward = { |
210 |
Vector3 forward = { |
| 265 |
cosf(game.pitch) * sinf(game.yaw), |
211 |
cosf(game.player.pitch) * sinf(game.player.yaw), |
| 266 |
-sinf(game.pitch), |
212 |
-sinf(game.player.pitch), |
| 267 |
cosf(game.pitch) * cosf(game.yaw) |
213 |
cosf(game.player.pitch) * cosf(game.player.yaw) |
| 268 |
}; |
214 |
}; |
| 269 |
|
215 |
|
| 270 |
// Use a stable world-up vector and apply lean roll |
|
|
| 271 |
Vector3 up = { 0, 1, 0 }; |
216 |
Vector3 up = { 0, 1, 0 }; |
| 272 |
up = Vector3RotateByAxisAngle(up, forward, leanAngle); |
217 |
up = Vector3RotateByAxisAngle(up, forward, leanAngle); |
| 273 |
game.camera.up = up; |
218 |
game.camera.up = up; |
| 274 |
|
|
|
| 275 |
// Use a longer target distance for better precision in the projection matrix |
|
|
| 276 |
game.camera.target = Vector3Add(game.camera.position, Vector3Scale(forward, 20.0f)); |
219 |
game.camera.target = Vector3Add(game.camera.position, Vector3Scale(forward, 20.0f)); |
| 277 |
|
220 |
|
| 278 |
// Calculate horizontal speed for UI |
|
|
| 279 |
if (dt > 0) { |
221 |
if (dt > 0) { |
| 280 |
Vector2 velH = { game.pos.x - oldPos.x, game.pos.z - oldPos.z }; |
222 |
Vector2 velH = { game.player.pos.x - oldPos.x, game.player.pos.z - oldPos.z }; |
| 281 |
game.horizontal_speed = Vector2Length(velH) / dt; |
223 |
game.player.horizontal_speed = Vector2Length(velH) / dt; |
| 282 |
} else { |
224 |
} else { |
| 283 |
game.horizontal_speed = 0; |
225 |
game.player.horizontal_speed = 0; |
| 284 |
} |
226 |
} |
| 285 |
} |
227 |
} |