#ifndef DC_GENERATOR_H
#define DC_GENERATOR_H
#include <cstdint>
#include <cmath>
// Abstract base class for density/SDF evaluation.
//
// Convention: positive = solid, negative = air.
// (Matches DCDensity's convention used throughout the DC pipeline.)
//
// Implementations provide at minimum evaluate(). The default gradient()
// uses Quilez's tetrahedron finite-difference method. Override for analytic
// gradients or GPU-backed evaluation.
class DCGenerator {
public:
virtual ~DCGenerator() = default;
// Required: evaluate density at world position.
// lod_voxel_size: current LOD voxel size (for Nyquist octave clamping).
// Returns: positive = solid, negative = air.
virtual float evaluate(float x, float y, float z, float lod_voxel_size) = 0;
// Optional: analytic gradient. Default: Quilez tetrahedron finite difference.
// Writes unit normal into normal[3].
virtual void gradient(float x, float y, float z, float lod_voxel_size, float normal[3]) {
float h = lod_voxel_size * 0.25f;
if (h < 0.001f) h = 0.001f;
float d0 = evaluate(x + h, y - h, z - h, lod_voxel_size);
float d1 = evaluate(x - h, y - h, z + h, lod_voxel_size);
float d2 = evaluate(x - h, y + h, z - h, lod_voxel_size);
float d3 = evaluate(x + h, y + h, z + h, lod_voxel_size);
normal[0] = -(d0 - d1 - d2 + d3);
normal[1] = -(-d0 - d1 + d2 + d3);
normal[2] = -(-d0 + d1 - d2 + d3);
float len = std::sqrt(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]);
if (len > 1e-8f) {
normal[0] /= len;
normal[1] /= len;
normal[2] /= len;
}
}
// MANY POINTS AT ONCE, and the default is exactly the loop it replaces.
//
// A generator whose per-point cost is dominated by resolving state that is
// the same for every point in a grid can hoist that out here. The proc-nodes
// bridge is one: it answered "what is Erosion Noise's Gain" by scanning a
// vector of Godot Strings, per voxel, 35937 times per chunk.
//
// Overriding this is OPTIONAL and the default below is behaviour-identical,
// so no existing generator changes. r_out receives p_count values;
// p_xyz holds p_count * 3 floats, x,y,z interleaved.
virtual void evaluate_batch(const float* p_xyz, int p_count,
float lod_voxel_size, float* r_out) {
for (int i = 0; i < p_count; i++) {
r_out[i] = evaluate(p_xyz[i * 3], p_xyz[i * 3 + 1], p_xyz[i * 3 + 2],
lod_voxel_size);
}
}
// Optional: GPU compute shader source (GLSL). Return nullptr for CPU-only.
virtual bool has_gpu_shader() const { return false; }
virtual const char* gpu_shader_source() const { return nullptr; }
// OPAQUE DATA for the custom shader. dc-terrain does not know or care
// what these floats mean -- a heightfield, a mask, a lookup table. It
// transports them to a storage buffer the density body may read, and
// the generator that supplied them owns their meaning.
//
// The pointer must stay valid and STABLE: the GPU thread re-uploads only
// when it changes, exactly as it recompiles only when the shader source
// changes.
virtual const float* gpu_custom_data() const { return nullptr; }
// ELEMENTS behind gpu_custom_data(), not a side length. DCCompute uploads
// exactly this many floats; the two are a PAIR and must move together —
// moving one alone gives either a truncated buffer or an attempt to
// allocate count² floats, which is 7.9 TB for a 1026 tile and a segfault.
virtual int gpu_custom_data_count() const { return 0; }
// A CONTENT VERSION, because the STABLE-pointer contract above cannot
// express mutation.
//
// The GPU thread re-uploads when the POINTER changes. That is correct for a
// buffer that is only ever replaced wholesale, and WRONG the moment a
// generator can grow its data in place: a std::vector assigned to within
// its existing capacity keeps the same data() address, so the new contents
// never reach the GPU and every log line still reads healthy. A tile
// fetched from a peer mid-session is exactly that case.
//
// Bump this whenever the bytes change, whatever the pointer does.
virtual uint32_t gpu_custom_data_version() const { return 0; }
// NOISE VOLUMES for a density body that SAMPLES noise rather than computing
// it (GPU Gems 3 ch.1 fetches eight 16^3 RGBA volumes; that is why nine
// octaves were affordable in 2007). Transported to sampler3D bindings the
// host shader declares by name, under the same stable-pointer contract as
// gpu_custom_data above.
//
// Returns an array of EIGHT pointers -- plain 0-3, then packed 4-7 -- each
// to dim^3 RGBA HALF floats (the .vol files' native format, and what the
// texture is created as). Deliberately not float32: promoting them changes
// the precision of the hardware trilinear filtering, and a 25-unit domain
// warp amplifies that into a visible density difference from anything else
// sampling the same volumes. nullptr means the body samples no volumes, and
// then neither shader declares the bindings.
virtual const void* const* gpu_noise_volumes() const { return nullptr; }
// Side length, unlike gpu_custom_data_count which is an element count.
virtual int gpu_noise_dim() const { return 0; }
virtual uint32_t gpu_noise_version() const { return 0; }
// Optional: bounding box hint for grid allocation.
// Writes axis-aligned min/max into min[3]/max[3]. Returns false if unbounded.
virtual bool get_bounds(float min[3], float max[3]) const { return false; }
};
#endif // DC_GENERATOR_H