#ifndef DC_TERRAIN_GENERATOR_PROCNODES_BOUNDED_H
#define DC_TERRAIN_GENERATOR_PROCNODES_BOUNDED_H
#include "dc_terrain_generator.h"
#include <godot_cpp/variant/string.hpp>
#include <string>
namespace godot {
// ---------------------------------------------------------------------------
// DCTerrainGeneratorProcNodesBounded — a proc-nodes graph, at its own size
// ---------------------------------------------------------------------------
//
// WHY THIS IS NOT DCTerrainGeneratorProcNodes.
//
// That one is the WORLD generator: it maps world metres into a UV-like node
// space through terrain_height, adds its own mountain / relief / detail noise
// layers, clips the density to [floor_y, ceiling_y], and takes its vertical
// scale from a discovered parameter whose node must be titled "Erosion Noise".
// All of that is right for a planet and wrong for an asset. A graph bridged
// from Blender has no Erosion Noise node, so its vertical scale is pinned at
// the 5.0 default whatever the map does, and a mountain 25.8 node units tall
// over a 40-unit footprint cannot fit a band of 5 -- a ratio no choice of
// terrain_height changes, because both axes scale together.
//
// So this generator does not scale anything. DCGenerator::evaluate takes world
// coordinates, the graph declares its bounds in world units, and the field is
// meshed at the size Blender says it is. The emitted wrapper is one line.
//
// BOUNDED, NOT TILING. get_bounds() is answered from the graph's own Group
// Output interface -- "Bounds Min" / "Bounds Max", read by name and converted
// out of Blender's frame by ProcNodeGraph::get_density_bounds. A graph that
// declares no bounds falls back to the base's "unbounded", which is the honest
// answer rather than a guessed box.
//
// ⚠ GPU ONLY, deliberately, and it says so rather than inventing a CPU field.
// evaluate() returns air. The CPU path would have to call back into the graph
// from dc-terrain's worker threads, and neither the reflection call nor
// ProcNodeGraph's cached execution plan has been shown to be safe there. A
// generator that quietly returns a DIFFERENT field on the CPU than it does on
// the GPU is the exact failure the emitted-density gate exists to catch, so
// this returns nothing and is visible about it -- the same stance
// DCGeneratorGeiss takes while its volumes are unloaded.
class DCTerrainGeneratorProcNodesBounded : public DCTerrainGenerator {
GDCLASS(DCTerrainGeneratorProcNodesBounded, DCTerrainGenerator)
// Held as a generic Resource: dc-terrain has NO compile-time dependency on
// proc-nodes and must not gain one. Everything goes through call().
Ref<Resource> graph_;
std::string cached_glsl_;
String last_compile_error_ = "never compiled (no graph has been assigned)";
bool has_bounds_ = false;
float bmin_[3] = { 0, 0, 0 };
float bmax_[3] = { 0, 0, 0 };
float authored_voxel_size_ = 0.0f;
class Bridge : public DCGenerator {
DCTerrainGeneratorProcNodesBounded *owner_;
public:
Bridge(DCTerrainGeneratorProcNodesBounded *o) : owner_(o) {}
float evaluate(float, float, float, float) override {
// Air. See the GPU-ONLY note above: an invented CPU field that
// disagrees with the shader is worse than no field.
return -1.0f;
}
bool has_gpu_shader() const override { return !owner_->cached_glsl_.empty(); }
const char *gpu_shader_source() const override {
return owner_->cached_glsl_.empty() ? nullptr : owner_->cached_glsl_.c_str();
}
bool get_bounds(float p_min[3], float p_max[3]) const override {
if (!owner_->has_bounds_) {
return false;
}
for (int i = 0; i < 3; i++) {
p_min[i] = owner_->bmin_[i];
p_max[i] = owner_->bmax_[i];
}
return true;
}
};
Bridge bridge_{ this };
void _recompile();
protected:
static void _bind_methods();
public:
DCGenerator *get_native_generator() override { return &bridge_; }
void set_graph(const Ref<Resource> &p_graph);
Ref<Resource> get_graph() const { return graph_; }
// Bound so a headless gate can assert on them: "visible" has to mean
// answerable, not printed.
String get_last_compile_error() const { return last_compile_error_; }
bool has_compiled_shader() const { return !cached_glsl_.empty(); }
String get_compiled_glsl() const { return String(cached_glsl_.c_str()); }
bool has_bounds() const { return has_bounds_; }
Vector3 get_bounds_min() const { return Vector3(bmin_[0], bmin_[1], bmin_[2]); }
Vector3 get_bounds_max() const { return Vector3(bmax_[0], bmax_[1], bmax_[2]); }
float get_authored_voxel_size() const { return authored_voxel_size_; }
};
} // namespace godot
#endif // DC_TERRAIN_GENERATOR_PROCNODES_BOUNDED_H