// =====================================================================
// Noise SDF body — pluggable generator for the DC GPU pipeline.
//
// Provides: float evaluate_density(float x, float y, float z)
//
// Requires (declared by template):
// - Params UBO fields: primary_frequency, primary_strength, etc.
// - perm_tables[] (PermBuf), voronoi_count/voronoi_data[] (VoronoiBuf)
// - CSGPrimitive struct, csg_count, csg_primitives[] (CSGBuf)
//
// Extracted from density_fill.glsl — the 7-layer noise + CSG evaluation
// that was previously duplicated across density_fill, edge_intersect,
// and surface_normals shaders.
// =====================================================================
// Permutation table offsets for each noise generator
#define PERM_PRIMARY 0
#define PERM_TUNNEL_A 512
#define PERM_TUNNEL_B 1024
#define PERM_RIDGE 1536
#define PERM_WARP 2048
#define PERM_DETAIL 2560
int perm(int table_offset, int idx) {
return perm_tables[table_offset + (idx & 255)];
}
int fastfloor(float x) {
int xi = int(x);
return (x < float(xi)) ? xi - 1 : xi;
}
float grad3(int hash, float x, float y, float z) {
int h = hash & 15;
float u = (h < 8) ? x : y;
float v = (h < 4) ? y : ((h == 12 || h == 14) ? x : z);
return (((h & 1) != 0) ? -u : u) + (((h & 2) != 0) ? -v : v);
}
float contrib(int table_offset, int ii, int jj, int kk, float x, float y, float z) {
float t = 0.6 - x * x - y * y - z * z;
if (t < 0.0) return 0.0;
t *= t;
int gi = perm(table_offset, ii + perm(table_offset, jj + perm(table_offset, kk)));
return t * t * grad3(gi, x, y, z);
}
float noise3(int table_offset, float x, float y, float z) {
const float F3 = 1.0 / 3.0;
const float G3 = 1.0 / 6.0;
float s = (x + y + z) * F3;
int i = fastfloor(x + s);
int j = fastfloor(y + s);
int k = fastfloor(z + s);
float t = float(i + j + k) * G3;
float X0 = float(i) - t;
float Y0 = float(j) - t;
float Z0 = float(k) - t;
float x0 = x - X0;
float y0 = y - Y0;
float z0 = z - Z0;
int i1, j1, k1;
int i2, j2, k2;
if (x0 >= y0) {
if (y0 >= z0) {
i1=1; j1=0; k1=0; i2=1; j2=1; k2=0;
} else if (x0 >= z0) {
i1=1; j1=0; k1=0; i2=1; j2=0; k2=1;
} else {
i1=0; j1=0; k1=1; i2=1; j2=0; k2=1;
}
} else {
if (y0 < z0) {
i1=0; j1=0; k1=1; i2=0; j2=1; k2=1;
} else if (x0 < z0) {
i1=0; j1=1; k1=0; i2=0; j2=1; k2=1;
} else {
i1=0; j1=1; k1=0; i2=1; j2=1; k2=0;
}
}
float x1 = x0 - float(i1) + G3;
float y1 = y0 - float(j1) + G3;
float z1 = z0 - float(k1) + G3;
float x2 = x0 - float(i2) + 2.0 * G3;
float y2 = y0 - float(j2) + 2.0 * G3;
float z2 = z0 - float(k2) + 2.0 * G3;
float x3 = x0 - 1.0 + 3.0 * G3;
float y3 = y0 - 1.0 + 3.0 * G3;
float z3 = z0 - 1.0 + 3.0 * G3;
int ii = i & 255;
int jj = j & 255;
int kk = k & 255;
float n0 = contrib(table_offset, ii, jj, kk, x0, y0, z0);
float n1 = contrib(table_offset, ii + i1, jj + j1, kk + k1, x1, y1, z1);
float n2 = contrib(table_offset, ii + i2, jj + j2, kk + k2, x2, y2, z2);
float n3 = contrib(table_offset, ii + 1, jj + 1, kk + 1, x3, y3, z3);
return 32.0 * (n0 + n1 + n2 + n3);
}
float fbm3(int table_offset, float x, float y, float z, int octaves) {
float sum = 0.0;
float amp = 1.0;
float freq = 1.0;
float max_amp = 0.0;
for (int o = 0; o < octaves; o++) {
sum += noise3(table_offset, x * freq, y * freq, z * freq) * amp;
max_amp += amp;
amp *= 0.5; // persistence
freq *= 2.0; // lacunarity
}
return sum / max_amp;
}
// Helper: sample noise with frequency scaling
float sample_noise(int table_offset, float freq, float px, float py, float pz, int oct) {
return fbm3(table_offset, px * freq, py * freq, pz * freq, oct);
}
// =====================================================================
// CSG primitive evaluation (port of dc_density.cpp)
// =====================================================================
// Edit application — IDENTICAL to the density-stage apply_gpu_edits in
// dc_compute.cpp so the mesh stage carves the exact same surface the density
// grid was carved with (no per-chunk divergence). Box + sphere only; mesh-SDF
// edits (etype>=4) need the BVH bindings the mesh stage doesn't have, so they
// are skipped here.
float apply_gpu_edits(float d, float wx, float wy, float wz) {
for (uint i = 0u; i < edit_count; i++) {
GpuEdit e = gpu_edits[i];
uint etype = e.edit_type;
// Bounding sphere early reject
float edx = wx - e.pos_x;
float edy = wy - e.pos_y;
float edz = wz - e.pos_z;
float dist_sq = edx*edx + edy*edy + edz*edz;
if (dist_sq > e.radius * e.radius) continue;
if (etype <= 1u) {
// Sphere: smooth quadratic falloff
float r_sq = e.radius * e.radius;
float t = 1.0 - dist_sq / r_sq;
d += e.strength * t * t;
} else if (etype <= 3u) {
// Box: SDF-based CSG
float dx = abs(wx - e.pos_x) - e.half_x;
float dy = abs(wy - e.pos_y) - e.half_y;
float dz = abs(wz - e.pos_z) - e.half_z;
float outside = sqrt(
max(dx, 0.0) * max(dx, 0.0) +
max(dy, 0.0) * max(dy, 0.0) +
max(dz, 0.0) * max(dz, 0.0));
float inside = min(max(max(dx, dy), dz), 0.0);
float box_sdf = outside + inside;
if (e.strength < 0.0) {
d = min(d, box_sdf);
} else {
d = max(d, -box_sdf);
}
} else if (etype == 6u || etype == 7u) {
// Hard sphere CSG: SDF = dist - radius (neg inside, pos outside —
// same convention as box_sdf). Union/difference by strength sign.
float sphere_sdf = sqrt(dist_sq) - e.radius;
if (e.strength < 0.0) {
d = min(d, sphere_sdf);
} else {
d = max(d, -sphere_sdf);
}
} else if (etype == 8u || etype == 9u) {
// Convex brush = intersection of half-spaces: f = max_i(n_i·p - d_i)
// (neg inside, pos outside). Planes in [_pad1, _pad1+_pad2). IDENTICAL
// to the density-stage convex branch in dc_compute.cpp.
uint poff = e._pad1;
uint pcnt = e._pad2;
float f = -1e30;
for (uint pi = 0u; pi < pcnt; pi++) {
vec4 pl = convex_planes[poff + pi];
f = max(f, pl.x * wx + pl.y * wy + pl.z * wz - pl.w);
}
if (e.strength < 0.0) {
d = min(d, f);
} else {
d = max(d, -f);
}
}
// etype 4/5 (mesh-SDF edits) skipped: BVH bindings unavailable here.
}
return d;
}
// =====================================================================
// Settled-edit SDF delta brick — trilinear sample of brick_delta[] (binding
// 10, declared in edge_intersect.glsl). Chunk-local grid at the density
// stride (grid_dims + 1). MUST match the density-stage apply_brick_delta in
// dc_compute.cpp so the fractional mesh-stage samples agree with the grid
// endpoints (same divergence class as the two evaluate_density copies). A
// zero buffer returns 0 exactly, so this is a no-op until a bake fills it.
// =====================================================================
float apply_brick_delta(float wx, float wy, float wz) {
int stride = grid_dims.x + 1;
vec3 lv = (vec3(wx, wy, wz) - cell_origin) / voxel_size;
lv = clamp(lv, vec3(0.0), vec3(float(stride - 1) - 1e-4));
ivec3 i0 = ivec3(floor(lv));
vec3 f = lv - vec3(i0);
int sx = stride, sxy = stride * stride;
int b = i0.z * sxy + i0.y * sx + i0.x;
float c00 = mix(brick_delta[b], brick_delta[b + 1], f.x);
float c10 = mix(brick_delta[b + sx], brick_delta[b + sx + 1], f.x);
float c01 = mix(brick_delta[b + sxy], brick_delta[b + sxy + 1], f.x);
float c11 = mix(brick_delta[b + sxy + sx], brick_delta[b + sxy + sx + 1], f.x);
return mix(mix(c00, c10, f.y), mix(c01, c11, f.y), f.z);
}
// =====================================================================
// Density evaluation (exact port of DCDensity::evaluate)
// =====================================================================
float clampf(float v, float lo, float hi) {
return clamp(v, lo, hi);
}
float smoothstep_manual(float t) {
return t * t * (3.0 - 2.0 * t);
}
// Hash-based procedural Voronoi (infinite tiling) — IDENTICAL to the CPU
// DCDensity::evaluate_internal and the density-fill stage in dc_compute.cpp.
// The previous buffer-based voronoi_data[] path used a DIFFERENT cell set
// (SeededRNG LCG from configure()) than the carved density grid, so the mesh
// stage placed floor/ceiling features at different heights than the grid —
// adjacent-chunk mismatches. This hash path reconstructs the exact same cells.
uint hash2d(int ix, int iz, int seed_offset) {
uint h = uint(ix * 374761393 + iz * 668265263 + seed_offset);
h = (h ^ (h >> 13u)) * 1274126177u;
h = h ^ (h >> 16u);
return h;
}
float hash_float(int ix, int iz, int seed_offset) {
return float(hash2d(ix, iz, seed_offset) & 0xFFFFFFu) / 16777216.0;
}
float evaluate_density(float x, float y, float z) {
// === Base density: flat ground or spherical planet ===
float d;
if (spherical != 0) {
// Sphere SDF: positive inside (solid), negative outside (air)
float dx = x - planet_center_x;
float dy = y - planet_center_y;
float dz = z - planet_center_z;
float dist = sqrt(dx * dx + dy * dy + dz * dz);
d = planet_radius - dist;
// Early out: deep interior or far above surface
float max_displacement = primary_strength + ridge_strength +
detail_strength + warp_strength * 0.5;
if (abs(d) > max_displacement * 2.5) {
if (edit_count > 0u) d = apply_gpu_edits(d, x, y, z);
return d;
}
} else {
d = -y * ground_bias;
}
// === Noise layers (skipped when use_noise == 0) ===
if (use_noise == 0) {
if (edit_count > 0u) d = apply_gpu_edits(d, x, y, z);
if (spherical == 0) {
float floor_blend = max((-y - solid_floor_depth) * 0.1, 0.0);
d += floor_blend;
float sky_blend = max((y - sky_height) * 0.1, 0.0);
d -= sky_blend;
}
return d;
}
// === Layer 1: Domain warping ===
float wx = x + sample_noise(PERM_WARP, warp_frequency, x, y, z, warp_octaves) * warp_strength;
float wy = y + sample_noise(PERM_WARP, warp_frequency, x + 31.7, y + 47.3, z + 89.1, warp_octaves) * warp_strength;
float wz = z + sample_noise(PERM_WARP, warp_frequency, x + 73.1, y + 11.9, z + 59.7, warp_octaves) * warp_strength;
// === Layer 2: Primary 3D noise ===
d += sample_noise(PERM_PRIMARY, primary_frequency, wx, wy, wz, primary_octaves) * primary_strength;
// === Layer 3: Ridged noise ===
float ridge_raw = sample_noise(PERM_RIDGE, ridge_frequency, wx, wy, wz, ridge_octaves);
float ridge_eps = 0.08;
float ridged = 1.0 - sqrt(ridge_raw * ridge_raw + ridge_eps * ridge_eps);
ridged = max(ridged, 0.0);
ridged = ridged * ridged;
d += ridged * ridge_strength;
// === Layer 4: Detail noise ===
d += sample_noise(PERM_DETAIL, detail_frequency, wx, wy, wz, detail_octaves) * detail_strength;
// === Layer 5: Tunnels ===
if (spherical != 0) {
// Spherical tunnels: depth-based fade (carve crust, not surface)
float max_displacement = primary_strength + ridge_strength +
detail_strength + warp_strength * 0.5;
float crust_depth = max_displacement * 2.0;
if (d > -tunnel_strength && d < crust_depth) {
float ta = sample_noise(PERM_TUNNEL_A, tunnel_frequency, wx, wy * 0.7, wz, tunnel_octaves);
float tb = sample_noise(PERM_TUNNEL_B, tunnel_frequency, wx + 200.0, wy * 0.7, wz + 200.0, tunnel_octaves);
float tube_dist_sq = ta * ta + tb * tb;
float radius_sq = tunnel_radius * tunnel_radius;
float tube_carve = max(1.0 - tube_dist_sq / radius_sq, 0.0);
float depth_below_surface = d;
float surface_fade = clampf(depth_below_surface / (tunnel_strength * 0.5), 0.0, 1.0);
d -= tube_carve * tunnel_strength * surface_fade;
}
} else {
// Flat terrain tunnels
float ta = sample_noise(PERM_TUNNEL_A, tunnel_frequency, wx, wy * 0.7, wz, tunnel_octaves);
float tb = sample_noise(PERM_TUNNEL_B, tunnel_frequency, wx + 200.0, wy * 0.7, wz + 200.0, tunnel_octaves);
float tube_dist_sq = ta * ta + tb * tb;
float radius_sq = tunnel_radius * tunnel_radius;
float tube_carve = max(1.0 - tube_dist_sq / radius_sq, 0.0);
d -= tube_carve * tunnel_strength;
}
// === Flat terrain only: voronoi, height clamping, spawn area ===
if (spherical == 0) {
// Voronoi floors/ceilings — hash-based, matching CPU evaluate_internal
// and the density-fill stage (NOT the legacy voronoi_data[] buffer).
if (floor_strength > 0.0) {
float cell_spacing = terrain_size / float(voronoi_cell_count);
int gx = int(floor(x / cell_spacing));
int gz = int(floor(z / cell_spacing));
int seed_base = seed + 100;
float nearest_dist_sq = 3.402823e+38;
float nearest_floor_h = 0.0;
float second_dist_sq = 3.402823e+38;
for (int dzi = -1; dzi <= 1; dzi++) {
for (int dxi = -1; dxi <= 1; dxi++) {
int cxi = gx + dxi;
int czi = gz + dzi;
float jx = hash_float(cxi, czi, seed_base);
float jz = hash_float(cxi, czi, seed_base + 7);
float cell_x = (float(cxi) + 0.2 + jx * 0.6) * cell_spacing;
float cell_z = (float(czi) + 0.2 + jz * 0.6) * cell_spacing;
float fh = hash_float(cxi, czi, seed_base + 13);
float flr_h = floor_height_min + fh * (floor_height_max - floor_height_min);
float ddx = x - cell_x;
float ddz = z - cell_z;
float vd = ddx * ddx + ddz * ddz;
if (vd < nearest_dist_sq) {
second_dist_sq = nearest_dist_sq;
nearest_dist_sq = vd;
nearest_floor_h = flr_h;
} else if (vd < second_dist_sq) {
second_dist_sq = vd;
}
}
}
float edge_ratio = 1.0 - nearest_dist_sq / (second_dist_sq + 0.001);
float cell_blend = clampf(edge_ratio * 2.0, 0.0, 1.0);
cell_blend = smoothstep_manual(cell_blend);
float floor_dist = nearest_floor_h - y;
float floor_effect = 0.0;
if (floor_dist > 0.0) {
floor_effect = clampf(floor_dist * 0.5, 0.0, 2.0);
} else if (floor_dist > -1.5) {
floor_effect = floor_dist * 1.5;
}
float ceiling_h = nearest_floor_h + room_height;
float ceil_dist = y - ceiling_h;
float ceil_effect = 0.0;
if (ceil_dist > 0.0 && ceil_dist < 3.0) {
ceil_effect = -clampf(ceil_dist * 0.5, 0.0, 2.0);
}
d += (floor_effect + ceil_effect) * cell_blend * floor_strength;
}
// Height clamping
float floor_blend = max((-y - solid_floor_depth) * 0.1, 0.0);
d += floor_blend;
float sky_blend = max((y - sky_height) * 0.1, 0.0);
d -= sky_blend;
// Crater system: spawn crater + random craters across the island.
// Ported verbatim from CPU DCDensity::evaluate_internal. Crater #0
// (radius = flat_center_radius) SUBSUMES the old standalone flat-center
// spawn block — do NOT keep both (double-flatten).
// CraterInfo: cx, cz, radius
float crater_cx[6];
float crater_cz[6];
float crater_r[6];
int num_craters = 0;
// Main spawn crater at origin
crater_cx[num_craters] = 0.0;
crater_cz[num_craters] = 0.0;
crater_r[num_craters] = flat_center_radius;
num_craters++;
// Random craters seeded from terrain seed
for (int ci = 0; ci < 5; ci++) {
float angle = hash_float(ci, seed, 200) * 6.2831853;
float cdistr = 40.0 + hash_float(ci, seed, 201) * (island_radius * 0.5);
float cr = 10.0 + hash_float(ci, seed, 202) * 20.0;
crater_cx[num_craters] = cos(angle) * cdistr;
crater_cz[num_craters] = sin(angle) * cdistr;
crater_r[num_craters] = cr;
num_craters++;
}
for (int ci = 0; ci < num_craters; ci++) {
float cdx = x - crater_cx[ci];
float cdz = z - crater_cz[ci];
float cdist = sqrt(cdx * cdx + cdz * cdz);
float cr = crater_r[ci];
float ctransition = cr * 0.5;
if (cdist < cr + ctransition) {
// Flat center
float blend = 1.0 - clampf(
(cdist - cr * 0.3) / (cr * 0.7 + ctransition),
0.0, 1.0);
blend = smoothstep_manual(blend);
float flat_d = -y * 0.8;
d = d + (flat_d - d) * blend;
// Crater rim
float rim_start = cr * 0.7;
float rim_end = cr + ctransition * 0.5;
if (cdist > rim_start && cdist < rim_end) {
float rim_t = (cdist - rim_start) / (rim_end - rim_start);
rim_t = sin(rim_t * 3.14159);
float rim_height = cr * 0.2;
float rim_noise = fbm3(PERM_DETAIL,
x * detail_frequency * 2.0,
y * detail_frequency,
z * detail_frequency * 2.0, 2) * rim_height * 0.4;
float rim_pull = (rim_height + rim_noise) * rim_t;
d += max(0.0, (rim_pull - y) * 0.3) * rim_t;
}
}
}
} // end flat-terrain-only block
// === Settled brick — added before HOT edits (base + brick, then HOT).
// Gated on flags bit1 so no-edit chunks (zero/stale brick) read nothing. ===
if ((flags & 2) != 0) d += apply_brick_delta(x, y, z);
// === Edits (CSG) — applied LAST, matching the density-fill stage which
// applies edits in main() after the full evaluate_density(). Keeping this
// here (and not removing it) is required: edge_intersect.glsl calls
// evaluate_density() directly and never applies edits separately. ===
// STEP 2: when SAMPLING the settled-edit brick (flags bit1), the edits are
// ALREADY folded into brick_delta above — skip the inline re-apply to avoid
// double-counting. edit_buf stays POPULATED though (dc_gpu_thread no longer
// empties it on brick chunks), so edge_intersect's exact-Hermite override
// still reads the convex planes and sharpens faces/edges THROUGH the brick
// (verified: tests/test_brick.cpp — rotated face 0.075 → 0.000). Non-brick
// chunks (bit1 clear) apply edits inline exactly as before.
if (edit_count > 0u && (flags & 2) == 0) {
d = apply_gpu_edits(d, x, y, z);
}
return d;
}