OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / src / dc_terrain_generator_procnodes_bounded.cpp

115 lines · cpp ref:
#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.");
			}
		}
	}
}