1#include "all.h"
2
3#include <ctype.h>
4#include <stdlib.h>
5#include <stdio.h>
6#include <string.h>
7#include <float.h>
8
9char map_peek(MapParser *p) {
10 if (p->pos >= p->length) return 0;
11 return p->data[p->pos];
12}
13
14char map_get(MapParser *p) {
15 if (p->pos >= p->length) return 0;
16 return p->data[p->pos++];
17}
18
19void map_skip_whitespace(MapParser *p) {
20 while (true) {
21 char c = map_peek(p);
22 if (isspace(c)) {
23 map_get(p);
24 } else if (c == '/' && p->data[p->pos + 1] == '/') {
25 while (map_peek(p) != '\n' && map_peek(p) != 0) map_get(p);
26 } else {
27 break;
28 }
29 }
30}
31
32bool map_expect(MapParser *p, char expected) {
33 map_skip_whitespace(p);
34 if (map_get(p) == expected) return true;
35 return false;
36}
37
38void map_parse_token(MapParser *p, char *buffer, int size) {
39 map_skip_whitespace(p);
40 int i = 0;
41 bool quoted = false;
42 if (map_peek(p) == '"') {
43 quoted = true;
44 map_get(p);
45 }
46
47 while (true) {
48 char c = map_peek(p);
49 if (c == 0) break;
50 if (quoted) {
51 if (c == '"') {
52 map_get(p);
53 break;
54 }
55 } else {
56 if (isspace(c) || c == '(' || c == ')' || c == '{' || c == '}') break;
57 }
58 if (i < size - 1) buffer[i++] = map_get(p);
59 else map_get(p);
60 }
61 buffer[i] = 0;
62}
63
64Vector3 map_parse_vector(MapParser *p) {
65 map_expect(p, '(');
66 char buf[64];
67 map_parse_token(p, buf, sizeof(buf));
68 float x = (float)atof(buf);
69 map_parse_token(p, buf, sizeof(buf));
70 float y = (float)atof(buf);
71 map_parse_token(p, buf, sizeof(buf));
72 float z = (float)atof(buf);
73 map_expect(p, ')');
74 return (Vector3){ x, z, -y };
75}
76
77Map parse_map(const char *filename) {
78 size_t size;
79 char *data = (char *)vfs_read(filename, &size);
80 Map map = { 0 };
81 if (!data) return map;
82
83 MapParser p = { data, size, 0 };
84 array(MapEntity) entities;
85 array_init(entities);
86
87 while (true) {
88 map_skip_whitespace(&p);
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);
97
98 while (true) {
99 map_skip_whitespace(&p);
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));
125
126 char buf[64];
127 map_parse_token(&p, buf, sizeof(buf)); plane.shift[0] = (float)atof(buf);
128 map_parse_token(&p, buf, sizeof(buf)); plane.shift[1] = (float)atof(buf);
129 map_parse_token(&p, buf, sizeof(buf)); plane.rotate = (float)atof(buf);
130 map_parse_token(&p, buf, sizeof(buf)); plane.scale[0] = (float)atof(buf);
131 map_parse_token(&p, buf, sizeof(buf)); plane.scale[1] = (float)atof(buf);
132
133 array_push(planes, plane);
134 }
135 brush.planes = planes.data;
136 brush.plane_count = (int)planes.length;
137 array_push(brushes, brush);
138 } else {
139 map_get(&p);
140 }
141 }
142 entity.properties = props.data;
143 entity.property_count = (int)props.length;
144 entity.brushes = brushes.data;
145 entity.brush_count = (int)brushes.length;
146 array_push(entities, entity);
147 }
148 }
149
150 map.entities = entities.data;
151 map.entity_count = (int)entities.length;
152 vfs_free(data);
153 return map;
154}
155
156void free_map(Map map) {
157 for (int i = 0; i < map.entity_count; i++) {
158 MapEntity *e = &map.entities[i];
159 if (e->properties) free(e->properties);
160 for (int j = 0; j < e->brush_count; j++) {
161 if (e->brushes[j].planes) free(e->brushes[j].planes);
162 }
163 if (e->brushes) free(e->brushes);
164 }
165 if (map.entities) free(map.entities);
166}
167
168Plane plane_from_points(Vector3 p1, Vector3 p2, Vector3 p3) {
169 Vector3 v1 = Vector3Subtract(p2, p1);
170 Vector3 v2 = Vector3Subtract(p3, p1);
171 Vector3 normal = Vector3Normalize(Vector3CrossProduct(v1, v2));
172 return (Plane){ normal, Vector3DotProduct(normal, p1) };
173}
174
175void poly_free(Polygon p) {
176 if (p.verts) free(p.verts);
177}
178
179Polygon poly_clip(Polygon poly, Plane plane) {
180 if (poly.count == 0) return poly;
181
182 array(Vector3) out_verts;
183 array_init(out_verts);
184
185 for (int i = 0; i < poly.count; i++) {
186 Vector3 p1 = poly.verts[i];
187 Vector3 p2 = poly.verts[(i + 1) % poly.count];
188
189 float d1 = Vector3DotProduct(plane.normal, p1) - plane.dist;
190 float d2 = Vector3DotProduct(plane.normal, p2) - plane.dist;
191
192 const float eps = 0.001f;
193
194 if (d1 >= -eps) {
195 if (d2 >= -eps) {
196 array_push(out_verts, p2);
197 } else {
198 float t = d1 / (d1 - d2);
199 Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t));
200 array_push(out_verts, intersect);
201 }
202 } else {
203 if (d2 >= -eps) {
204 float t = d1 / (d1 - d2);
205 Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t));
206 array_push(out_verts, intersect);
207 array_push(out_verts, p2);
208 }
209 }
210 }
211
212 Polygon res;
213 res.verts = out_verts.data;
214 res.count = (int)out_verts.length;
215 return res;
216}
217
218Polygon create_large_quad(Plane plane) {
219 Vector3 n = plane.normal;
220 Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 };
221 Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n));
222 up = Vector3CrossProduct(n, right);
223
224 Vector3 center = Vector3Scale(n, plane.dist);
225 float size = 10000.0f;
226
227 Polygon poly;
228 poly.verts = ALLOC(Vector3, 4);
229 poly.count = 4;
230 poly.verts[0] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, size)));
231 poly.verts[1] = Vector3Add(center, Vector3Add(Vector3Scale(right, -size), Vector3Scale(up, size)));
232 poly.verts[2] = Vector3Add(center, Vector3Add(Vector3Scale(right, -size), Vector3Scale(up, -size)));
233 poly.verts[3] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, -size)));
234
235 return poly;
236}
237
238Vector2 get_uv(Vector3 p, MapPlane *mp, Plane plane, int texWidth, int texHeight) {
239 // Map coordinate system: X right, Y forward, Z up
240 // Raylib coordinate system: X right, Y up, Z back
241 // Conversion: Raylib.x = Map.x, Raylib.y = Map.z, Raylib.z = -Map.y
242
243 // Map coordinates from Raylib coordinates:
244 float mx = p.x;
245 float my = -p.z;
246 float mz = p.y;
247
248 // Map normal from Raylib normal:
249 Vector3 n = plane.normal;
250 float nx = n.x;
251 float ny = -n.z;
252 float nz = n.y;
253
254 float u = 0, v = 0;
255
256 // Quake Standard axis projection
257 if (fabsf(nz) >= fabsf(nx) && fabsf(nz) >= fabsf(ny)) {
258 u = mx;
259 v = -my;
260 } else if (fabsf(nx) >= fabsf(ny) && fabsf(nx) >= fabsf(nz)) {
261 u = my;
262 v = -mz;
263 } else { // ny is dominant
264 u = mx;
265 v = -mz;
266 }
267
268 // Apply rotation
269 if (mp->rotate != 0.0f) {
270 float angle = mp->rotate * (PI / 180.0f);
271 float sinA = sinf(angle);
272 float cosA = cosf(angle);
273 float ru = u * cosA - v * sinA;
274 float rv = u * sinA + v * cosA;
275 u = ru;
276 v = rv;
277 }
278
279 // Apply scale, shift
280 float scaleU = mp->scale[0] ? mp->scale[0] : 1.0f;
281 float scaleV = mp->scale[1] ? mp->scale[1] : 1.0f;
282
283 u = u / scaleU + mp->shift[0];
284 v = v / scaleV + mp->shift[1];
285
286 return (Vector2){ u / (float)texWidth, v / (float)texHeight };
287}
288
289void unload_map(void) {
290 if (game.map.models) {
291 for (int i = 0; i < game.map.count; i++) {
292 UnloadModel(game.map.models[i]);
293 }
294 free(game.map.models);
295 game.map.models = NULL;
296 game.map.count = 0;
297 }
298}
299
300bool load_map(const char *filename) {
301 TraceLog(LOG_INFO, "Loading map: %s", filename);
302
303 Map map = parse_map(filename);
304 if (map.entity_count == 0) {
305 TraceLog(LOG_ERROR, "Failed to load map or map is empty: %s", filename);
306 return false;
307 }
308
309 unload_map();
310
311 array(TextureGroup) groups;
312 array_init(groups);
313
314 // Default player state if no start found
315 game.player.pos = (Vector3){ 0, 10, 0 };
316 game.player.yaw = 0;
317 game.player.pitch = 0;
318
319 int total_brushes = 0;
320 for (int i = 0; i < map.entity_count; i++) {
321 MapEntity *e = &map.entities[i];
322 bool is_world = false;
323 const char *classname = "";
324
325 for (int j = 0; j < e->property_count; j++) {
326 if (strcmp(e->properties[j].key, "classname") == 0) {
327 classname = e->properties[j].value;
328 if (strcmp(classname, "worldspawn") == 0) is_world = true;
329 }
330 }
331
332 // Handle entities (like player start)
333 for (int j = 0; j < e->property_count; j++) {
334 if (strcmp(e->properties[j].key, "origin") == 0) {
335 float x, y, z;
336 sscanf(e->properties[j].value, "%f %f %f", &x, &y, &z);
337 if (strcmp(classname, "info_player_start") == 0) {
338 game.player.pos = (Vector3){ x, z, -y };
339 float angle = 0;
340 for (int k = 0; k < e->property_count; k++) {
341 if (strcmp(e->properties[k].key, "angle") == 0) {
342 angle = (float)atof(e->properties[k].value);
343 }
344 }
345 game.player.yaw = (angle + 90.0f) * DEG2RAD;
346 game.player.pitch = 0;
347 TraceLog(LOG_INFO, "Player spawn set to: %f, %f, %f", game.player.pos.x, game.player.pos.y, game.player.pos.z);
348 }
349 }
350 }
351
352 if (is_world) {
353 total_brushes += e->brush_count;
354 for (int j = 0; j < e->brush_count; j++) {
355 MapBrush *brush = &e->brushes[j];
356 for (int p_idx = 0; p_idx < brush->plane_count; p_idx++) {
357 MapPlane *mp = &brush->planes[p_idx];
358 Plane plane = plane_from_points(mp->p[0], mp->p[1], mp->p[2]);
359
360 if (Vector3Length(plane.normal) < 0.5f) continue;
361
362 Polygon poly = create_large_quad(plane);
363 for (int k = 0; k < brush->plane_count; k++) {
364 if (p_idx == k) continue;
365 Plane clipPlane = plane_from_points(brush->planes[k].p[0], brush->planes[k].p[1], brush->planes[k].p[2]);
366 if (Vector3Length(clipPlane.normal) < 0.5f) continue;
367
368 Polygon next = poly_clip(poly, clipPlane);
369 poly_free(poly);
370 poly = next;
371 }
372
373 if (poly.count >= 3) {
374 TextureGroup *group = NULL;
375 for (int g = 0; g < groups.length; g++) {
376 if (strcmp(groups.data[g].texture, mp->texture) == 0) {
377 group = &groups.data[g];
378 break;
379 }
380 }
381 if (!group) {
382 TextureGroup new_group = { 0 };
383 strncpy(new_group.texture, mp->texture, sizeof(new_group.texture));
384 array_init(new_group.vertices);
385 array_init(new_group.texcoords);
386 array_init(new_group.normals);
387 array_push(groups, new_group);
388 group = &groups.data[groups.length - 1];
389 }
390 for (int v = 1; v < poly.count - 1; v++) {
391 Vector3 v0 = poly.verts[0];
392 Vector3 v1 = poly.verts[v+1];
393 Vector3 v2 = poly.verts[v];
394 array_push(group->vertices, v0.x); array_push(group->vertices, v0.y); array_push(group->vertices, v0.z);
395 array_push(group->vertices, v1.x); array_push(group->vertices, v1.y); array_push(group->vertices, v1.z);
396 array_push(group->vertices, v2.x); array_push(group->vertices, v2.y); array_push(group->vertices, v2.z);
397 Texture2D tex = get_texture(mp->texture);
398 Vector2 uv0 = get_uv(v0, mp, plane, tex.width, tex.height);
399 Vector2 uv1 = get_uv(v1, mp, plane, tex.width, tex.height);
400 Vector2 uv2 = get_uv(v2, mp, plane, tex.width, tex.height);
401 array_push(group->texcoords, uv0.x); array_push(group->texcoords, uv0.y);
402 array_push(group->texcoords, uv1.x); array_push(group->texcoords, uv1.y);
403 array_push(group->texcoords, uv2.x); array_push(group->texcoords, uv2.y);
404 Vector3 out_normal = Vector3Scale(plane.normal, -1.0f);
405 array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
406 array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
407 array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z);
408 }
409 }
410 poly_free(poly);
411 }
412 }
413 }
414 }
415
416 array(Model) final_models;
417 array_init(final_models);
418
419 for (int i = 0; i < groups.length; i++) {
420 TextureGroup *g = &groups.data[i];
421 if (g->vertices.length == 0) continue;
422 Mesh mesh = { 0 };
423 mesh.vertexCount = (int)g->vertices.length / 3;
424 mesh.triangleCount = mesh.vertexCount / 3;
425 mesh.vertices = (float *)malloc(g->vertices.length * sizeof(float));
426 memcpy(mesh.vertices, g->vertices.data, g->vertices.length * sizeof(float));
427 mesh.texcoords = (float *)malloc(g->texcoords.length * sizeof(float));
428 memcpy(mesh.texcoords, g->texcoords.data, g->texcoords.length * sizeof(float));
429 mesh.normals = (float *)malloc(g->normals.length * sizeof(float));
430 memcpy(mesh.normals, g->normals.data, g->normals.length * sizeof(float));
431 UploadMesh(&mesh, false);
432 Model model = LoadModelFromMesh(mesh);
433 model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = get_texture(g->texture);
434 array_push(final_models, model);
435 array_free(g->vertices);
436 array_free(g->texcoords);
437 array_free(g->normals);
438 }
439
440 game.map.models = final_models.data;
441 game.map.count = (int)final_models.length;
442 array_free(groups);
443
444 free_map(map);
445 TraceLog(LOG_INFO, "Processed %d brushes into %d models", total_brushes, game.map.count);
446 return true;
447}
448
449bool check_map_collision(Vector3 start, Vector3 end, RayCollision *outCollision) {
450 Vector3 diff = Vector3Subtract(end, start);
451 float maxDist = Vector3Length(diff);
452 if (maxDist < 0.001f) return false;
453
454 Ray ray = { 0 };
455 ray.position = start;
456 ray.direction = Vector3Scale(diff, 1.0f / maxDist);
457
458 RayCollision closest = { 0 };
459 closest.distance = FLT_MAX;
460 closest.hit = false;
461
462 for (int i = 0; i < game.map.count; i++) {
463 Model model = game.map.models[i];
464 for (int m = 0; m < model.meshCount; m++) {
465 RayCollision col = GetRayCollisionMesh(ray, model.meshes[m], model.transform);
466 if (col.hit && col.distance < closest.distance && col.distance <= maxDist) {
467 closest = col;
468 }
469 }
470 }
471
472 if (closest.hit) {
473 if (outCollision) *outCollision = closest;
474 return true;
475 }
476 return false;
477}