200 lines
7.0 KiB
C
200 lines
7.0 KiB
C
#include "terrain_manager.h"
|
|
#include "../Rendering/renderer.h"
|
|
#include "../Rendering/frustum.h"
|
|
#include "../Core/logger.h"
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
// ============================================================================
|
|
// 噪声:分块需要世界一致的高度,这里复用 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;
|
|
}
|