#include "all.h" #include #include #include GameState game; static array(CachedTexture) texture_cache; Texture2D GetTexture(const char *name) { for (int i = 0; i < texture_cache.length; i++) { if (strcmp(texture_cache.data[i].name, name) == 0) return texture_cache.data[i].tex; } char lname[256]; strncpy(lname, name, sizeof(lname)); for (int i = 0; lname[i]; i++) lname[i] = tolower(lname[i]); char path[256]; void *data = NULL; size_t size = 0; const char *ext = ".jpg"; snprintf(path, sizeof(path), "textures/%s.png", lname); data = vfs_read(path, &size); if (data) { ext = ".png"; } else { snprintf(path, sizeof(path), "textures/%s.jpg", lname); data = vfs_read(path, &size); ext = ".jpg"; } Texture2D tex = { 0 }; if (data) { Image img = LoadImageFromMemory(ext, data, (int)size); if (img.data) { tex = LoadTextureFromImage(img); UnloadImage(img); } vfs_free(data); } if (tex.id == 0) { TraceLog(LOG_WARNING, "Failed to load texture: '%s' (tried path: '%s')", name, path); Image img = GenImageChecked(64, 64, 8, 8, (Color){128, 128, 128, 255}, (Color){200, 200, 200, 255}); tex = LoadTextureFromImage(img); UnloadImage(img); } if (tex.id != 0) { GenTextureMipmaps(&tex); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); SetTextureWrap(tex, TEXTURE_WRAP_REPEAT); CachedTexture cached; strncpy(cached.name, name, sizeof(cached.name)); cached.tex = tex; array_push(texture_cache, cached); } return tex; } void UnloadMap(void) { if (game.world_models) { for (int i = 0; i < game.world_model_count; i++) { UnloadModel(game.world_models[i]); } free(game.world_models); game.world_models = NULL; game.world_model_count = 0; } for (int i = 0; i < texture_cache.length; i++) { UnloadTexture(texture_cache.data[i].tex); } array_clear(texture_cache); } 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 camera if no start found game.camera.position = (Vector3){ 0, 10, 0 }; game.camera.target = (Vector3){ 1, 10, 0 }; game.camera.up = (Vector3){ 0, 1, 0 }; game.camera.fovy = 75.0f; game.camera.projection = CAMERA_PERSPECTIVE; 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; } 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.pos = (Vector3){ x, z, -y }; game.camera.position = game.pos; 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.yaw = (angle + 90.0f) * DEG2RAD; game.pitch = 0; game.camera.target = Vector3Add(game.pos, (Vector3){sinf(game.yaw), 0, cosf(game.yaw)}); TraceLog(LOG_INFO, "Player spawn set to: %f, %f, %f (yaw: %f)", game.pos.x, game.pos.y, game.pos.z, game.yaw); } } } 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.world_models = final_models.data; game.world_model_count = (int)final_models.length; array_free(groups); FreeMap(map); TraceLog(LOG_INFO, "Processed %d brushes into %d models", total_brushes, game.world_model_count); return true; } void InitGame(void) { array_init(texture_cache); game.world_models = NULL; game.world_model_count = 0; // Load UI Font size_t font_size = 0; void *font_data = vfs_read("fonts/LiberationSans-Bold.ttf", &font_size); if (font_data) { game.font_ui = LoadFontFromMemory(".ttf", font_data, (int)font_size, 20, NULL, 0); vfs_free(font_data); } else { game.font_ui = GetFontDefault(); } LoadMap("maps/demo1.map"); game.cursor_captured = false; EnableCursor(); game.move_mode = MOVE_NORMAL; game.velocity = (Vector3){ 0, 0, 0 }; game.is_grounded = false; } void UpdateGame(void) { if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { game.cursor_captured = !game.cursor_captured; if (game.cursor_captured) DisableCursor(); else EnableCursor(); } if (IsKeyPressed(KEY_ONE)) LoadMap("maps/demo1.map"); if (IsKeyPressed(KEY_TWO)) LoadMap("maps/demo2.map"); if (IsKeyPressed(KEY_V)) { game.vsync = !game.vsync; if (game.vsync) { SetWindowState(FLAG_VSYNC_HINT); SetTargetFPS(GetMonitorRefreshRate(GetCurrentMonitor())); } else { ClearWindowState(FLAG_VSYNC_HINT); SetTargetFPS(0); } } UpdatePlayer(); } void DrawGame(void) { BeginDrawing(); ClearBackground(DARKGRAY); BeginMode3D(game.camera); // Enable backface culling to hide interior faces of brushes rlEnableBackfaceCulling(); for (int i = 0; i < game.world_model_count; i++) { DrawModel(game.world_models[i], (Vector3){ 0, 0, 0 }, 1.0f, WHITE); } EndMode3D(); int screenWidth = GetScreenWidth(); int screenHeight = GetScreenHeight(); DrawLine(screenWidth / 2 - 10, screenHeight / 2, screenWidth / 2 + 10, screenHeight / 2, GREEN); DrawLine(screenWidth / 2, screenHeight / 2 - 10, screenWidth / 2, screenHeight / 2 + 10, GREEN); DrawTextEx(game.font_ui, TextFormat("%i FPS", GetFPS()), (Vector2){ 10, 10 }, 20, 2, GREEN); DrawTextEx(game.font_ui, TextFormat("VSync: %s", game.vsync ? "ON" : "OFF"), (Vector2){ 10, 35 }, 20, 2, GREEN); DrawTextEx(game.font_ui, TextFormat("Speed: %.0f", game.horizontal_speed), (Vector2){ 10, 60 }, 20, 2, GREEN); EndDrawing(); }