#include "terrain_manager.h" #include "../Rendering/renderer.h" #include "../Rendering/frustum.h" #include "../Core/logger.h" #include #include // ============================================================================ // 噪声:分块需要世界一致的高度,这里复用 terrain.c 的 hash_noise/fbm_noise // ============================================================================ static float sample_world_height(int seed, float world_x, float world_z, float scale) { return fbm_noise(world_x * scale, world_z * scale, seed, 6, 0.5f); } static void fill_heightmap_for_chunk(TerrainHeightmap* hm, int seed, int cx, int cz, float chunk_size, float scale) { if (!hm || !hm->data) return; int s = hm->size; float half = chunk_size * 0.5f; float step = chunk_size / (float)(s - 1); float origin_x = (float)cx * chunk_size - half; float origin_z = (float)cz * chunk_size - half; for (int z = 0; z < s; z++) { for (int x = 0; x < s; x++) { float wx = origin_x + (float)x * step; float wz = origin_z + (float)z * step; hm->data[z * s + x] = sample_world_height(seed, wx, wz, scale); } } } static void make_chunk_world_matrix(TerrainChunk* chunk, Mat4* out) { mat4_identity(out); out->m[3][0] = (float)chunk->cx * chunk->terrain->config.chunk_size; out->m[3][1] = 0.0f; out->m[3][2] = (float)chunk->cz * chunk->terrain->config.chunk_size; } TerrainManager* terrain_manager_create(ID3D11Device* dev, const TerrainConfig* config, Material* base_material) { if (!dev || !config) return NULL; TerrainManager* tm = (TerrainManager*)calloc(1, sizeof(TerrainManager)); tm->config = *config; tm->device = dev; tm->base_material = base_material; tm->load_radius = 3; tm->seed = 12345; tm->chunk_count = 0; return tm; } void terrain_manager_destroy(TerrainManager* tm) { if (!tm) return; for (int i = 0; i < tm->chunk_count; i++) { if (tm->chunks[i].terrain) terrain_destroy(tm->chunks[i].terrain); } free(tm); } static int find_chunk_index(TerrainManager* tm, int cx, int cz) { for (int i = 0; i < tm->chunk_count; i++) { if (tm->chunks[i].loaded && tm->chunks[i].cx == cx && tm->chunks[i].cz == cz) return i; } return -1; } static TerrainChunk* alloc_chunk_slot(TerrainManager* tm) { if (tm->chunk_count >= TERRAIN_MAX_CHUNKS) return NULL; return &tm->chunks[tm->chunk_count++]; } static TerrainChunk* create_chunk(TerrainManager* tm, int cx, int cz) { TerrainChunk* chunk = alloc_chunk_slot(tm); if (!chunk) { LOG_ERROR("Terrain: failed to alloc chunk slot"); return NULL; } chunk->cx = cx; chunk->cz = cz; chunk->terrain = (Terrain*)calloc(1, sizeof(Terrain)); chunk->terrain->config = tm->config; chunk->terrain->device = tm->device; chunk->terrain->heightmap = terrain_heightmap_create(tm->config.vertices_per_chunk); fill_heightmap_for_chunk(&chunk->terrain->heightmap, tm->seed, cx, cz, tm->config.chunk_size, 0.015f); chunk->terrain->mesh = terrain_mesh_create(tm->device, &chunk->terrain->heightmap, tm->config.chunk_size, tm->config.height_scale, tm->config.uv_scale); chunk->material = tm->base_material; chunk->loaded = 1; if (!chunk->terrain->mesh) { LOG_ERROR("Terrain: chunk mesh create failed at (%d, %d)", cx, cz); } else { LOG_INFO("Terrain: created chunk (%d, %d) mesh v=%u i=%u", cx, cz, chunk->terrain->mesh->vcount, chunk->terrain->mesh->icount); } return chunk; } void terrain_manager_update(TerrainManager* tm, Vec3 observer_pos) { if (!tm) return; float inv_size = 1.0f / tm->config.chunk_size; int ox = (int)floorf(observer_pos.x * inv_size + 0.5f); int oz = (int)floorf(observer_pos.z * inv_size + 0.5f); static int logged = 0; if (!logged) { LOG_INFO("Terrain update: observer=(%.1f,%.1f,%.1f) center_chunk=(%d,%d)", observer_pos.x, observer_pos.y, observer_pos.z, ox, oz); logged = 1; } // 标记需要保留 int keep[TERRAIN_MAX_CHUNKS] = {0}; // 加载需要的分块 int r = tm->load_radius; for (int z = oz - r; z <= oz + r; z++) { for (int x = ox - r; x <= ox + r; x++) { int idx = find_chunk_index(tm, x, z); if (idx < 0) { if (create_chunk(tm, x, z)) { keep[tm->chunk_count - 1] = 1; } } else { keep[idx] = 1; } } } // 卸载不需要的分块 int write = 0; for (int i = 0; i < tm->chunk_count; i++) { if (keep[i]) { tm->chunks[write] = tm->chunks[i]; write++; } else { terrain_destroy(tm->chunks[i].terrain); } } tm->chunk_count = write; } void terrain_manager_render(TerrainManager* tm, Renderer* renderer) { if (!tm || !renderer) return; static int logged = 0; if (!logged) { LOG_INFO("Terrain render: chunk_count=%d", tm->chunk_count); logged = 1; } Mat4 vp; renderer_get_view_projection(renderer, &vp); Vec4 planes[6]; frustum_from_matrix(&vp, planes); float half = tm->config.chunk_size * 0.5f; float radius = sqrtf(half * half * 2.0f + (tm->config.height_scale * 0.5f) * (tm->config.height_scale * 0.5f)); float cy = tm->config.height_scale * 0.5f; int rendered = 0; for (int i = 0; i < tm->chunk_count; i++) { TerrainChunk* c = &tm->chunks[i]; if (!c->loaded || !c->terrain || !c->terrain->mesh) continue; Vec3 center = { (float)c->cx * tm->config.chunk_size, cy, (float)c->cz * tm->config.chunk_size }; if (!frustum_intersects_sphere(planes, center, radius)) continue; Mat4 world; make_chunk_world_matrix(c, &world); renderer_draw_mesh_with_material(renderer, c->terrain->mesh, c->material, &world); rendered++; } LOG_INFO("Terrain render: visible_chunks=%d/%d", rendered, tm->chunk_count); } float terrain_manager_get_height(TerrainManager* tm, float world_x, float world_z) { if (!tm) return 0.0f; float inv_size = 1.0f / tm->config.chunk_size; int cx = (int)floorf(world_x * inv_size + 0.5f); int cz = (int)floorf(world_z * inv_size + 0.5f); // 先在已加载分块中查找 int idx = find_chunk_index(tm, cx, cz); if (idx >= 0 && tm->chunks[idx].terrain) { Terrain* t = tm->chunks[idx].terrain; float local_x = world_x - (float)cx * tm->config.chunk_size; float local_z = world_z - (float)cz * tm->config.chunk_size; return terrain_get_height(t, local_x, local_z); } // 回退到直接采样噪声 return sample_world_height(tm->seed, world_x, world_z, 0.015f) * tm->config.height_scale; }