Files
lity3d/Engine/Terrain/terrain.c
T

290 lines
9.6 KiB
C

#include "terrain.h"
#include <stdlib.h>
#include <math.h>
#include <string.h>
// ============================================================================
// 简单的 Value Noise + 叠加
// ============================================================================
float hash_noise(int x, int z, int seed) {
unsigned int n = (unsigned int)x * 374761393u
+ (unsigned int)z * 668265263u
+ (unsigned int)seed * 1013904223u;
n = (n ^ (n >> 13)) * 1274126177u;
n = n ^ (n >> 16);
return (float)(n & 0x7fffffffu) / (float)0x7fffffffu;
}
static float smooth_noise(float x, float z, int seed) {
int ix = (int)floorf(x);
int iz = (int)floorf(z);
float fx = x - ix;
float fz = z - iz;
float v00 = hash_noise(ix, iz, seed);
float v10 = hash_noise(ix + 1, iz, seed);
float v01 = hash_noise(ix, iz + 1, seed);
float v11 = hash_noise(ix + 1, iz + 1, seed);
float u = fx * fx * (3.0f - 2.0f * fx);
float v = fz * fz * (3.0f - 2.0f * fz);
float a = v00 + (v10 - v00) * u;
float b = v01 + (v11 - v01) * u;
return a + (b - a) * v;
}
float fbm_noise(float x, float z, int seed, int octaves, float persistence) {
float total = 0.0f;
float amplitude = 1.0f;
float frequency = 1.0f;
float max_value = 0.0f;
for (int i = 0; i < octaves; i++) {
total += smooth_noise(x * frequency, z * frequency, seed + i * 57) * amplitude;
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0f;
}
return total / max_value;
}
// ============================================================================
// 高度图
// ============================================================================
TerrainConfig terrain_default_config(void) {
TerrainConfig cfg = {0};
cfg.chunks_x = 4;
cfg.chunks_z = 4;
cfg.vertices_per_chunk = 32;
cfg.chunk_size = 64.0f;
cfg.height_scale = 20.0f;
cfg.uv_scale = 0.1f;
return cfg;
}
TerrainHeightmap terrain_heightmap_create(int size) {
TerrainHeightmap hm = {0};
if (size < 2) size = 2;
if (size > TERRAIN_MAX_HEIGHTMAP_SIZE) size = TERRAIN_MAX_HEIGHTMAP_SIZE;
hm.size = size;
hm.data = (float*)calloc(size * size, sizeof(float));
return hm;
}
void terrain_heightmap_destroy(TerrainHeightmap* hm) {
if (hm && hm->data) {
free(hm->data);
hm->data = NULL;
hm->size = 0;
}
}
void terrain_heightmap_fill_noise(TerrainHeightmap* hm, int seed, float scale, float amplitude) {
if (!hm || !hm->data) return;
int s = hm->size;
for (int z = 0; z < s; z++) {
for (int x = 0; x < s; x++) {
float nx = (float)x / (float)(s - 1) * scale;
float nz = (float)z / (float)(s - 1) * scale;
float h = fbm_noise(nx, nz, seed, 6, 0.5f);
hm->data[z * s + x] = h * amplitude;
}
}
}
float terrain_heightmap_sample(const TerrainHeightmap* hm, float x, float z) {
if (!hm || !hm->data || hm->size < 2) return 0.0f;
int s = hm->size;
float fx = x * (float)(s - 1);
float fz = z * (float)(s - 1);
int ix = (int)floorf(fx);
int iz = (int)floorf(fz);
if (ix < 0) ix = 0;
if (iz < 0) iz = 0;
if (ix >= s - 1) ix = s - 2;
if (iz >= s - 1) iz = s - 2;
float tx = fx - ix;
float tz = fz - iz;
float h00 = hm->data[iz * s + ix];
float h10 = hm->data[iz * s + ix + 1];
float h01 = hm->data[(iz + 1) * s + ix];
float h11 = hm->data[(iz + 1) * s + ix + 1];
float a = h00 + (h10 - h00) * tx;
float b = h01 + (h11 - h01) * tx;
return a + (b - a) * tz;
}
// ============================================================================
// 地形网格
// ============================================================================
static Mesh* create_mesh_from_terrain(ID3D11Device* dev, const TerrainHeightmap* hm,
float size, float height_scale, float uv_scale) {
if (!hm || !hm->data || hm->size < 2) return NULL;
int s = hm->size;
int vc = s * s;
int ic = (s - 1) * (s - 1) * 6;
Vertex* verts = (Vertex*)calloc(vc, sizeof(Vertex));
uint32_t* idx = (uint32_t*)malloc(ic * sizeof(uint32_t));
float half = size * 0.5f;
float step = size / (float)(s - 1);
for (int z = 0; z < s; z++) {
for (int x = 0; x < s; x++) {
int i = z * s + x;
float px = (float)x * step - half;
float pz = (float)z * step - half;
float py = hm->data[i] * height_scale;
verts[i].pos[0] = px;
verts[i].pos[1] = py;
verts[i].pos[2] = pz;
verts[i].uv[0] = (float)x * uv_scale;
verts[i].uv[1] = (float)z * uv_scale;
verts[i].color[0] = 1.0f;
verts[i].color[1] = 1.0f;
verts[i].color[2] = 1.0f;
verts[i].color[3] = 1.0f;
}
}
// 计算法线
memset(verts, 0, vc * sizeof(Vertex)); // 重新初始化法线
for (int z = 0; z < s; z++) {
for (int x = 0; x < s; x++) {
int i = z * s + x;
float px = (float)x * step - half;
float pz = (float)z * step - half;
float py = hm->data[i] * height_scale;
verts[i].pos[0] = px;
verts[i].pos[1] = py;
verts[i].pos[2] = pz;
verts[i].uv[0] = (float)x * uv_scale;
verts[i].uv[1] = (float)z * uv_scale;
// 估算法线
float hl = (x > 0) ? hm->data[z * s + x - 1] * height_scale : py;
float hr = (x < s - 1) ? hm->data[z * s + x + 1] * height_scale : py;
float hd = (z > 0) ? hm->data[(z - 1) * s + x] * height_scale : py;
float hu = (z < s - 1) ? hm->data[(z + 1) * s + x] * height_scale : py;
verts[i].norm[0] = (hl - hr);
verts[i].norm[1] = 2.0f * step;
verts[i].norm[2] = (hd - hu);
float nx = verts[i].norm[0];
float ny = verts[i].norm[1];
float nz = verts[i].norm[2];
float len = sqrtf(nx * nx + ny * ny + nz * nz);
if (len > 0.0001f) {
verts[i].norm[0] = nx / len;
verts[i].norm[1] = ny / len;
verts[i].norm[2] = nz / len;
} else {
verts[i].norm[0] = 0;
verts[i].norm[1] = 1;
verts[i].norm[2] = 0;
}
// 根据高度和坡度着色
float h = hm->data[i];
float slope = (ny > 0.0001f) ? sqrtf(nx * nx + nz * nz) / ny : 0.0f;
if (h > 0.72f) {
verts[i].color[0] = 0.92f;
verts[i].color[1] = 0.92f;
verts[i].color[2] = 0.95f;
} else if (h > 0.55f || slope > 0.42f) {
verts[i].color[0] = 0.62f;
verts[i].color[1] = 0.60f;
verts[i].color[2] = 0.56f;
} else if (h < 0.18f) {
verts[i].color[0] = 0.82f;
verts[i].color[1] = 0.76f;
verts[i].color[2] = 0.50f;
} else {
verts[i].color[0] = 0.40f;
verts[i].color[1] = 0.68f;
verts[i].color[2] = 0.36f;
}
verts[i].color[3] = 1.0f;
}
}
int ii = 0;
for (int z = 0; z < s - 1; z++) {
for (int x = 0; x < s - 1; x++) {
uint32_t a = z * s + x;
uint32_t b = z * s + x + 1;
uint32_t c = (z + 1) * s + x;
uint32_t d = (z + 1) * s + x + 1;
idx[ii++] = a;
idx[ii++] = c;
idx[ii++] = b;
idx[ii++] = b;
idx[ii++] = c;
idx[ii++] = d;
}
}
Mesh* m = (Mesh*)calloc(1, sizeof(Mesh));
m->vcount = vc;
m->icount = ic;
m->stride = sizeof(Vertex);
D3D11_BUFFER_DESC bd = {0};
D3D11_SUBRESOURCE_DATA sd = {0};
bd.ByteWidth = sizeof(Vertex) * vc;
bd.Usage = D3D11_USAGE_DEFAULT;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
sd.pSysMem = verts;
dev->lpVtbl->CreateBuffer(dev, &bd, &sd, &m->vb);
bd.ByteWidth = sizeof(uint32_t) * ic;
bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
sd.pSysMem = idx;
dev->lpVtbl->CreateBuffer(dev, &bd, &sd, &m->ib);
free(verts);
free(idx);
return m;
}
Mesh* terrain_mesh_create(ID3D11Device* dev, const TerrainHeightmap* hm, float size, float height_scale, float uv_scale) {
return create_mesh_from_terrain(dev, hm, size, height_scale, uv_scale);
}
// ============================================================================
// 地形对象
// ============================================================================
Terrain* terrain_create(ID3D11Device* dev, const TerrainConfig* config) {
if (!dev || !config) return NULL;
Terrain* t = (Terrain*)calloc(1, sizeof(Terrain));
t->config = *config;
t->device = dev;
t->heightmap = terrain_heightmap_create(config->vertices_per_chunk);
terrain_heightmap_fill_noise(&t->heightmap, 12345, 4.0f, 1.0f);
t->mesh = terrain_mesh_create(dev, &t->heightmap, config->chunk_size, config->height_scale, config->uv_scale);
return t;
}
void terrain_destroy(Terrain* terrain) {
if (!terrain) return;
terrain_heightmap_destroy(&terrain->heightmap);
if (terrain->mesh) mesh_destroy(terrain->mesh);
free(terrain);
}
float terrain_get_height(const Terrain* terrain, float world_x, float world_z) {
if (!terrain) return 0.0f;
float half = terrain->config.chunk_size * 0.5f;
float local_x = (world_x + half) / terrain->config.chunk_size;
float local_z = (world_z + half) / terrain->config.chunk_size;
float h = terrain_heightmap_sample(&terrain->heightmap, local_x, local_z);
return h * terrain->config.height_scale;
}