#include "all.h" #include #include #include #include #include char map_peek(MapParser *p) { if (p->pos >= p->length) return 0; return p->data[p->pos]; } char map_get(MapParser *p) { if (p->pos >= p->length) return 0; return p->data[p->pos++]; } void map_skip_whitespace(MapParser *p) { while (true) { char c = map_peek(p); if (isspace(c)) { map_get(p); } else if (c == '/' && p->data[p->pos + 1] == '/') { while (map_peek(p) != '\n' && map_peek(p) != 0) map_get(p); } else { break; } } } bool map_expect(MapParser *p, char expected) { map_skip_whitespace(p); if (map_get(p) == expected) return true; return false; } void map_parse_token(MapParser *p, char *buffer, int size) { map_skip_whitespace(p); int i = 0; bool quoted = false; if (map_peek(p) == '"') { quoted = true; map_get(p); } while (true) { char c = map_peek(p); if (c == 0) break; if (quoted) { if (c == '"') { map_get(p); break; } } else { if (isspace(c) || c == '(' || c == ')' || c == '{' || c == '}') break; } if (i < size - 1) buffer[i++] = map_get(p); else map_get(p); } buffer[i] = 0; } Vector3 map_parse_vector(MapParser *p) { map_expect(p, '('); char buf[64]; map_parse_token(p, buf, sizeof(buf)); float x = (float)atof(buf); map_parse_token(p, buf, sizeof(buf)); float y = (float)atof(buf); map_parse_token(p, buf, sizeof(buf)); float z = (float)atof(buf); map_expect(p, ')'); return (Vector3){ x, z, -y }; } Map ParseMap(const char *filename) { size_t size; char *data = (char *)vfs_read(filename, &size); Map map = { 0 }; if (!data) return map; MapParser p = { data, size, 0 }; array(MapEntity) entities; array_init(entities); while (true) { map_skip_whitespace(&p); if (map_peek(&p) == 0) break; if (map_expect(&p, '{')) { MapEntity entity = { 0 }; array(MapProperty) props; array(MapBrush) brushes; array_init(props); array_init(brushes); while (true) { map_skip_whitespace(&p); char c = map_peek(&p); if (c == '}') { map_get(&p); break; } else if (c == '"') { MapProperty prop; map_parse_token(&p, prop.key, sizeof(prop.key)); map_parse_token(&p, prop.value, sizeof(prop.value)); array_push(props, prop); } else if (c == '{') { map_get(&p); MapBrush brush = { 0 }; array(MapPlane) planes; array_init(planes); while (true) { map_skip_whitespace(&p); if (map_peek(&p) == '}') { map_get(&p); break; } MapPlane plane; plane.p[0] = map_parse_vector(&p); plane.p[1] = map_parse_vector(&p); plane.p[2] = map_parse_vector(&p); map_parse_token(&p, plane.texture, sizeof(plane.texture)); char buf[64]; map_parse_token(&p, buf, sizeof(buf)); plane.shift[0] = (float)atof(buf); map_parse_token(&p, buf, sizeof(buf)); plane.shift[1] = (float)atof(buf); map_parse_token(&p, buf, sizeof(buf)); plane.rotate = (float)atof(buf); map_parse_token(&p, buf, sizeof(buf)); plane.scale[0] = (float)atof(buf); map_parse_token(&p, buf, sizeof(buf)); plane.scale[1] = (float)atof(buf); array_push(planes, plane); } brush.planes = planes.data; brush.plane_count = (int)planes.length; array_push(brushes, brush); } else { map_get(&p); } } entity.properties = props.data; entity.property_count = (int)props.length; entity.brushes = brushes.data; entity.brush_count = (int)brushes.length; array_push(entities, entity); } } map.entities = entities.data; map.entity_count = (int)entities.length; vfs_free(data); return map; } void FreeMap(Map map) { for (int i = 0; i < map.entity_count; i++) { MapEntity *e = &map.entities[i]; if (e->properties) free(e->properties); for (int j = 0; j < e->brush_count; j++) { if (e->brushes[j].planes) free(e->brushes[j].planes); } if (e->brushes) free(e->brushes); } if (map.entities) free(map.entities); } Plane PlaneFromPoints(Vector3 p1, Vector3 p2, Vector3 p3) { Vector3 v1 = Vector3Subtract(p2, p1); Vector3 v2 = Vector3Subtract(p3, p1); Vector3 normal = Vector3Normalize(Vector3CrossProduct(v1, v2)); return (Plane){ normal, Vector3DotProduct(normal, p1) }; } void PolyFree(Polygon p) { if (p.verts) free(p.verts); } Polygon PolyClip(Polygon poly, Plane plane) { if (poly.count == 0) return poly; array(Vector3) out_verts; array_init(out_verts); for (int i = 0; i < poly.count; i++) { Vector3 p1 = poly.verts[i]; Vector3 p2 = poly.verts[(i + 1) % poly.count]; float d1 = Vector3DotProduct(plane.normal, p1) - plane.dist; float d2 = Vector3DotProduct(plane.normal, p2) - plane.dist; const float eps = 0.001f; if (d1 >= -eps) { if (d2 >= -eps) { array_push(out_verts, p2); } else { float t = d1 / (d1 - d2); Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t)); array_push(out_verts, intersect); } } else { if (d2 >= -eps) { float t = d1 / (d1 - d2); Vector3 intersect = Vector3Add(p1, Vector3Scale(Vector3Subtract(p2, p1), t)); array_push(out_verts, intersect); array_push(out_verts, p2); } } } Polygon res; res.verts = out_verts.data; res.count = (int)out_verts.length; return res; } Polygon CreateLargeQuad(Plane plane) { Vector3 n = plane.normal; Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 }; Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n)); up = Vector3CrossProduct(n, right); Vector3 center = Vector3Scale(n, plane.dist); float size = 10000.0f; Polygon poly; poly.verts = ALLOC(Vector3, 4); poly.count = 4; poly.verts[0] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, size))); poly.verts[1] = Vector3Add(center, Vector3Add(Vector3Scale(right, -size), Vector3Scale(up, size))); poly.verts[2] = Vector3Add(center, Vector3Add(Vector3Scale(right, -size), Vector3Scale(up, -size))); poly.verts[3] = Vector3Add(center, Vector3Add(Vector3Scale(right, size), Vector3Scale(up, -size))); return poly; } Vector2 GetUV(Vector3 p, MapPlane *mp, Plane plane) { Vector3 n = plane.normal; Vector3 up = (fabsf(n.y) < 0.999f) ? (Vector3){ 0, 1, 0 } : (Vector3){ 1, 0, 0 }; Vector3 right = Vector3Normalize(Vector3CrossProduct(up, n)); up = Vector3CrossProduct(n, right); float u = Vector3DotProduct(p, right) * (1.0f / (mp->scale[0] ? mp->scale[0] : 1.0f)) + mp->shift[0]; float v = Vector3DotProduct(p, up) * (1.0f / (mp->scale[1] ? mp->scale[1] : 1.0f)) + mp->shift[1]; return (Vector2){ u / 64.0f, v / 64.0f }; } void UnloadMap(void) { if (game.map.models) { for (int i = 0; i < game.map.count; i++) { UnloadModel(game.map.models[i]); } free(game.map.models); game.map.models = NULL; game.map.count = 0; } } bool LoadMap(const char *filename) { TraceLog(LOG_INFO, "Loading map: %s", filename); Map map = ParseMap(filename); if (map.entity_count == 0) { TraceLog(LOG_ERROR, "Failed to load map or map is empty: %s", filename); return false; } UnloadMap(); array(TextureGroup) groups; array_init(groups); // Default player state if no start found game.player.pos = (Vector3){ 0, 10, 0 }; game.player.yaw = 0; game.player.pitch = 0; int total_brushes = 0; for (int i = 0; i < map.entity_count; i++) { MapEntity *e = &map.entities[i]; bool is_world = false; const char *classname = ""; for (int j = 0; j < e->property_count; j++) { if (strcmp(e->properties[j].key, "classname") == 0) { classname = e->properties[j].value; if (strcmp(classname, "worldspawn") == 0) is_world = true; } } // Handle entities (like player start) for (int j = 0; j < e->property_count; j++) { if (strcmp(e->properties[j].key, "origin") == 0) { float x, y, z; sscanf(e->properties[j].value, "%f %f %f", &x, &y, &z); if (strcmp(classname, "info_player_start") == 0) { game.player.pos = (Vector3){ x, z, -y }; float angle = 0; for (int k = 0; k < e->property_count; k++) { if (strcmp(e->properties[k].key, "angle") == 0) { angle = (float)atof(e->properties[k].value); } } game.player.yaw = (angle + 90.0f) * DEG2RAD; game.player.pitch = 0; TraceLog(LOG_INFO, "Player spawn set to: %f, %f, %f", game.player.pos.x, game.player.pos.y, game.player.pos.z); } } } if (is_world) { total_brushes += e->brush_count; for (int j = 0; j < e->brush_count; j++) { MapBrush *brush = &e->brushes[j]; for (int p_idx = 0; p_idx < brush->plane_count; p_idx++) { MapPlane *mp = &brush->planes[p_idx]; Plane plane = PlaneFromPoints(mp->p[0], mp->p[1], mp->p[2]); if (Vector3Length(plane.normal) < 0.5f) continue; Polygon poly = CreateLargeQuad(plane); for (int k = 0; k < brush->plane_count; k++) { if (p_idx == k) continue; Plane clipPlane = PlaneFromPoints(brush->planes[k].p[0], brush->planes[k].p[1], brush->planes[k].p[2]); if (Vector3Length(clipPlane.normal) < 0.5f) continue; Polygon next = PolyClip(poly, clipPlane); PolyFree(poly); poly = next; } if (poly.count >= 3) { TextureGroup *group = NULL; for (int g = 0; g < groups.length; g++) { if (strcmp(groups.data[g].texture, mp->texture) == 0) { group = &groups.data[g]; break; } } if (!group) { TextureGroup new_group = { 0 }; strncpy(new_group.texture, mp->texture, sizeof(new_group.texture)); array_init(new_group.vertices); array_init(new_group.texcoords); array_init(new_group.normals); array_push(groups, new_group); group = &groups.data[groups.length - 1]; } for (int v = 1; v < poly.count - 1; v++) { Vector3 v0 = poly.verts[0]; Vector3 v1 = poly.verts[v+1]; Vector3 v2 = poly.verts[v]; array_push(group->vertices, v0.x); array_push(group->vertices, v0.y); array_push(group->vertices, v0.z); array_push(group->vertices, v1.x); array_push(group->vertices, v1.y); array_push(group->vertices, v1.z); array_push(group->vertices, v2.x); array_push(group->vertices, v2.y); array_push(group->vertices, v2.z); Vector2 uv0 = GetUV(v0, mp, plane); Vector2 uv1 = GetUV(v1, mp, plane); Vector2 uv2 = GetUV(v2, mp, plane); array_push(group->texcoords, uv0.x); array_push(group->texcoords, uv0.y); array_push(group->texcoords, uv1.x); array_push(group->texcoords, uv1.y); array_push(group->texcoords, uv2.x); array_push(group->texcoords, uv2.y); Vector3 out_normal = Vector3Scale(plane.normal, -1.0f); array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z); array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z); array_push(group->normals, out_normal.x); array_push(group->normals, out_normal.y); array_push(group->normals, out_normal.z); } } PolyFree(poly); } } } } array(Model) final_models; array_init(final_models); for (int i = 0; i < groups.length; i++) { TextureGroup *g = &groups.data[i]; if (g->vertices.length == 0) continue; Mesh mesh = { 0 }; mesh.vertexCount = (int)g->vertices.length / 3; mesh.triangleCount = mesh.vertexCount / 3; mesh.vertices = (float *)malloc(g->vertices.length * sizeof(float)); memcpy(mesh.vertices, g->vertices.data, g->vertices.length * sizeof(float)); mesh.texcoords = (float *)malloc(g->texcoords.length * sizeof(float)); memcpy(mesh.texcoords, g->texcoords.data, g->texcoords.length * sizeof(float)); mesh.normals = (float *)malloc(g->normals.length * sizeof(float)); memcpy(mesh.normals, g->normals.data, g->normals.length * sizeof(float)); UploadMesh(&mesh, false); Model model = LoadModelFromMesh(mesh); model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = GetTexture(g->texture); array_push(final_models, model); array_free(g->vertices); array_free(g->texcoords); array_free(g->normals); } game.map.models = final_models.data; game.map.count = (int)final_models.length; array_free(groups); FreeMap(map); TraceLog(LOG_INFO, "Processed %d brushes into %d models", total_brushes, game.map.count); return true; } bool CheckMapCollision(Vector3 start, Vector3 end, RayCollision *outCollision) { Vector3 diff = Vector3Subtract(end, start); float maxDist = Vector3Length(diff); if (maxDist < 0.001f) return false; Ray ray = { 0 }; ray.position = start; ray.direction = Vector3Scale(diff, 1.0f / maxDist); RayCollision closest = { 0 }; closest.distance = FLT_MAX; closest.hit = false; for (int i = 0; i < game.map.count; i++) { Model model = game.map.models[i]; for (int m = 0; m < model.meshCount; m++) { RayCollision col = GetRayCollisionMesh(ray, model.meshes[m], model.transform); if (col.hit && col.distance < closest.distance && col.distance <= maxDist) { closest = col; } } } if (closest.hit) { if (outCollision) *outCollision = closest; return true; } return false; }