OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / distribute / hypertps-win64 / shaders / generators / noise_sdf.glsl

479 lines · glsl ref:
// =====================================================================
// 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;
}