// =====================================================================
// Grid SDF body — trilinear interpolation from the density grid.
//
// Provides: float evaluate_density(float x, float y, float z)
//
// Used by the hybrid GPU path: CPU fills the density grid, this SDF
// reads from it for edge_intersect (binary search) and surface_normals
// (central differences). Less accurate than full SDF evaluation but
// works with any DCGenerator that has no GPU shader.
//
// Requires (declared by template):
// - density[] (DensityBuf at binding 0)
// - Params UBO fields: grid_stride, voxel_scale, origin_x/y/z
// =====================================================================
float clampf(float v, float lo, float hi) {
return clamp(v, lo, hi);
}
float evaluate_density(float x, float y, float z) {
// Convert world position to grid coordinates
float gx = (x - origin_x) / voxel_scale;
float gy = (y - origin_y) / voxel_scale;
float gz = (z - origin_z) / voxel_scale;
int s = grid_stride;
// Integer grid coordinates (clamped to valid range)
int ix = clamp(int(floor(gx)), 0, s - 2);
int iy = clamp(int(floor(gy)), 0, s - 2);
int iz = clamp(int(floor(gz)), 0, s - 2);
// Fractional parts
float fx = clampf(gx - float(ix), 0.0, 1.0);
float fy = clampf(gy - float(iy), 0.0, 1.0);
float fz = clampf(gz - float(iz), 0.0, 1.0);
// Base index
int idx = iz * s * s + iy * s + ix;
// 8-corner trilinear interpolation
float d000 = density[idx];
float d100 = density[idx + 1];
float d010 = density[idx + s];
float d110 = density[idx + s + 1];
float d001 = density[idx + s * s];
float d101 = density[idx + s * s + 1];
float d011 = density[idx + s * s + s];
float d111 = density[idx + s * s + s + 1];
float d00 = mix(d000, d100, fx);
float d10 = mix(d010, d110, fx);
float d01 = mix(d001, d101, fx);
float d11 = mix(d011, d111, fx);
float d0 = mix(d00, d10, fy);
float d1 = mix(d01, d11, fy);
return mix(d0, d1, fz);
}