#include "dc_terrain_generator_procnodes_bounded.h"
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
using namespace godot;
void DCTerrainGeneratorProcNodesBounded::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_graph", "graph"), &DCTerrainGeneratorProcNodesBounded::set_graph);
ClassDB::bind_method(D_METHOD("get_graph"), &DCTerrainGeneratorProcNodesBounded::get_graph);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "graph", PROPERTY_HINT_RESOURCE_TYPE, "Resource"),
"set_graph", "get_graph");
ClassDB::bind_method(D_METHOD("get_last_compile_error"),
&DCTerrainGeneratorProcNodesBounded::get_last_compile_error);
ClassDB::bind_method(D_METHOD("has_compiled_shader"),
&DCTerrainGeneratorProcNodesBounded::has_compiled_shader);
ClassDB::bind_method(D_METHOD("get_compiled_glsl"),
&DCTerrainGeneratorProcNodesBounded::get_compiled_glsl);
ClassDB::bind_method(D_METHOD("has_bounds"), &DCTerrainGeneratorProcNodesBounded::has_bounds);
ClassDB::bind_method(D_METHOD("get_bounds_min"), &DCTerrainGeneratorProcNodesBounded::get_bounds_min);
ClassDB::bind_method(D_METHOD("get_bounds_max"), &DCTerrainGeneratorProcNodesBounded::get_bounds_max);
ClassDB::bind_method(D_METHOD("get_authored_voxel_size"),
&DCTerrainGeneratorProcNodesBounded::get_authored_voxel_size);
}
void DCTerrainGeneratorProcNodesBounded::set_graph(const Ref<Resource> &p_graph) {
graph_ = p_graph;
_recompile();
emit_changed();
}
void DCTerrainGeneratorProcNodesBounded::_recompile() {
cached_glsl_.clear();
last_compile_error_ = String();
has_bounds_ = false;
authored_voxel_size_ = 0.0f;
if (graph_.is_null()) {
// Not an error: an unconfigured generator is a legitimate state and a
// scene that has not been wired up yet should not scream on load. It is
// recorded so a gate can tell this apart from a real failure.
last_compile_error_ = "no graph assigned";
return;
}
// NO SKIP LIST AND NO OCTAVE CLAMP. The world generator clamps octaves so a
// coarse LOD cannot run more of them than its voxel size can carry; that is
// a property of a streaming clipmap, and this generator meshes one bounded
// asset at the resolution its author chose. Adding the clamp here would
// silently change the field with distance.
Variant glsl_var = graph_->call("compile_to_glsl_function", "proc_density", false,
PackedStringArray());
if (glsl_var.get_type() != Variant::STRING) {
last_compile_error_ = "compile_to_glsl_function returned non-string "
"(is the proc-nodes extension loaded?)";
UtilityFunctions::push_error("DCTerrainGeneratorProcNodesBounded: ", last_compile_error_);
return;
}
String proc_glsl = glsl_var;
if (proc_glsl.is_empty()) {
// proc-nodes refuses rather than dropping a node it cannot emit, and it
// has already said which node. What it cannot know is that the caller
// was a terrain generator, so say what the refusal COSTS.
last_compile_error_ = "compile_to_glsl_function REFUSED (a node has no GLSL -- "
"see the proc-nodes error above for which)";
UtilityFunctions::push_error(
"DCTerrainGeneratorProcNodesBounded: ", last_compile_error_,
". No GPU shader, and this generator has no CPU path, so the terrain "
"will be empty rather than wrong.");
return;
}
// THE WHOLE WRAPPER. World coordinates in, density out, nothing between.
//
// This one line is the point of the class. The world generator spends ~400
// lines here mapping metres into node space and back, because its graph is
// a heightfield over a planet. A bounded asset declares its own extent, so
// node units ARE world units and there is nothing to convert. Any rescale
// added here would put the asset somewhere other than where Blender says.
String wrapped = proc_glsl +
"\nfloat evaluate_density(float x, float y, float z) {\n"
" return proc_density(vec3(x, y, z));\n"
"}\n";
CharString u8 = wrapped.utf8();
cached_glsl_ = std::string(u8.get_data(), u8.length());
// The graph's own extent, already converted out of Blender's frame and
// re-sorted componentwise by ProcNodeGraph::get_density_bounds.
Variant b = graph_->call("get_density_bounds");
if (b.get_type() == Variant::DICTIONARY) {
Dictionary d = b;
if (d.has("min") && d.has("max")) {
const Vector3 lo = d["min"];
const Vector3 hi = d["max"];
// A degenerate box would mesh nothing and look exactly like a
// missing generator, so refuse it by name instead.
if (hi.x > lo.x && hi.y > lo.y && hi.z > lo.z) {
bmin_[0] = lo.x; bmin_[1] = lo.y; bmin_[2] = lo.z;
bmax_[0] = hi.x; bmax_[1] = hi.y; bmax_[2] = hi.z;
has_bounds_ = true;
if (d.has("voxel_size")) {
authored_voxel_size_ = (float)(double)d["voxel_size"];
}
} else {
UtilityFunctions::push_warning(
"DCTerrainGeneratorProcNodesBounded: the graph's declared bounds "
"are empty or inverted (min ", lo, ", max ", hi,
"). Treating the field as unbounded.");
}
}
}
}