From b4d0ad9e95226d225d5361b1182866884aaa6366 Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Thu, 30 Apr 2026 19:19:27 +0200 Subject: Structural refactor --- map.c | 597 +++++++++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 394 insertions(+), 203 deletions(-) (limited to 'map.c') diff --git a/map.c b/map.c index 12d40f9..4e2b83c 100644 --- a/map.c +++ b/map.c @@ -1,246 +1,437 @@ #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]; + 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++]; + 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; - } - } + 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; + 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; + 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_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; + 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); + 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) }; + 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); + 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; - - // Using a smaller epsilon and more robust logic - 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; + 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; + 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 }; + 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; } -- cgit v1.2.3