#include "dc_terrain_generator.h"
#include <functional>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <godot_cpp/classes/project_settings.hpp>
#include <godot_cpp/classes/resource_loader.hpp>
#include <godot_cpp/classes/mesh.hpp>
#include <godot_cpp/classes/array_mesh.hpp>
#include <godot_cpp/classes/packed_scene.hpp>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/mesh_instance3d.hpp>
namespace godot {
// ===========================================================================
// DCTerrainGenerator (base)
// ===========================================================================
void DCTerrainGenerator::_bind_methods() {
// No properties — abstract base.
}
// ===========================================================================
// DCTerrainGeneratorNoise
// ===========================================================================
DCTerrainGeneratorNoise::DCTerrainGeneratorNoise() {
_apply_config();
}
void DCTerrainGeneratorNoise::_apply_config() {
if (!dirty_) return;
impl_.density.configure(config_);
dirty_ = false;
}
// Macro to reduce boilerplate for simple scalar properties.
// Each setter marks dirty and emits changed().
#define NOISE_PROP_FLOAT(field) \
void DCTerrainGeneratorNoise::set_##field(float p_val) { \
if (config_.field == p_val) return; \
config_.field = p_val; \
dirty_ = true; \
emit_changed(); \
} \
float DCTerrainGeneratorNoise::get_##field() const { return config_.field; }
#define NOISE_PROP_INT(field) \
void DCTerrainGeneratorNoise::set_##field(int p_val) { \
if (config_.field == p_val) return; \
config_.field = p_val; \
dirty_ = true; \
emit_changed(); \
} \
int DCTerrainGeneratorNoise::get_##field() const { return config_.field; }
#define NOISE_PROP_BOOL(field) \
void DCTerrainGeneratorNoise::set_##field(bool p_val) { \
if (config_.field == p_val) return; \
config_.field = p_val; \
dirty_ = true; \
emit_changed(); \
} \
bool DCTerrainGeneratorNoise::get_##field() const { return config_.field; }
NOISE_PROP_INT(seed)
NOISE_PROP_FLOAT(primary_frequency)
NOISE_PROP_FLOAT(primary_strength)
NOISE_PROP_FLOAT(tunnel_frequency)
NOISE_PROP_FLOAT(tunnel_strength)
NOISE_PROP_FLOAT(tunnel_radius)
NOISE_PROP_FLOAT(ridge_frequency)
NOISE_PROP_FLOAT(ridge_strength)
NOISE_PROP_FLOAT(warp_frequency)
NOISE_PROP_FLOAT(warp_strength)
NOISE_PROP_FLOAT(detail_frequency)
NOISE_PROP_FLOAT(detail_strength)
NOISE_PROP_FLOAT(ground_bias)
NOISE_PROP_FLOAT(solid_floor_depth)
NOISE_PROP_FLOAT(sky_height)
NOISE_PROP_FLOAT(flat_center_radius)
NOISE_PROP_FLOAT(terrain_size)
NOISE_PROP_FLOAT(planet_radius)
NOISE_PROP_FLOAT(floor_strength)
NOISE_PROP_FLOAT(floor_height_min)
NOISE_PROP_FLOAT(floor_height_max)
NOISE_PROP_FLOAT(room_height)
NOISE_PROP_BOOL(use_noise)
NOISE_PROP_BOOL(spherical)
NOISE_PROP_INT(max_noise_octaves)
NOISE_PROP_INT(max_ridge_octaves)
NOISE_PROP_INT(voronoi_cell_count)
#undef NOISE_PROP_FLOAT
#undef NOISE_PROP_INT
#undef NOISE_PROP_BOOL
void DCTerrainGeneratorNoise::_bind_methods() {
// -- Macro for binding float properties --
#define BIND_FLOAT(prop, hint_range) \
ClassDB::bind_method(D_METHOD("set_" #prop, "value"), &DCTerrainGeneratorNoise::set_##prop); \
ClassDB::bind_method(D_METHOD("get_" #prop), &DCTerrainGeneratorNoise::get_##prop); \
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, #prop, PROPERTY_HINT_RANGE, hint_range), \
"set_" #prop, "get_" #prop);
#define BIND_INT(prop, hint_range) \
ClassDB::bind_method(D_METHOD("set_" #prop, "value"), &DCTerrainGeneratorNoise::set_##prop); \
ClassDB::bind_method(D_METHOD("get_" #prop), &DCTerrainGeneratorNoise::get_##prop); \
ADD_PROPERTY(PropertyInfo(Variant::INT, #prop, PROPERTY_HINT_RANGE, hint_range), \
"set_" #prop, "get_" #prop);
#define BIND_BOOL(prop) \
ClassDB::bind_method(D_METHOD("set_" #prop, "value"), &DCTerrainGeneratorNoise::set_##prop); \
ClassDB::bind_method(D_METHOD("get_" #prop), &DCTerrainGeneratorNoise::get_##prop); \
ADD_PROPERTY(PropertyInfo(Variant::BOOL, #prop), \
"set_" #prop, "get_" #prop);
// -- Noise group --
ADD_GROUP("Noise", "");
BIND_BOOL(use_noise)
BIND_INT(seed, "")
BIND_FLOAT(primary_frequency, "0.001,1.0,0.001")
BIND_FLOAT(primary_strength, "0.0,5.0,0.01")
BIND_FLOAT(tunnel_frequency, "0.001,1.0,0.001")
BIND_FLOAT(tunnel_strength, "0.0,5.0,0.01")
BIND_FLOAT(tunnel_radius, "0.0,2.0,0.01")
BIND_FLOAT(ridge_frequency, "0.001,1.0,0.001")
BIND_FLOAT(ridge_strength, "0.0,5.0,0.01")
BIND_FLOAT(warp_frequency, "0.001,1.0,0.001")
BIND_FLOAT(warp_strength, "0.0,30.0,0.1")
BIND_FLOAT(detail_frequency, "0.001,1.0,0.001")
BIND_FLOAT(detail_strength, "0.0,2.0,0.01")
// -- Terrain Shape group --
ADD_GROUP("Terrain Shape", "");
BIND_FLOAT(ground_bias, "-1.0,1.0,0.001")
BIND_FLOAT(solid_floor_depth, "0.0,100.0,0.5")
BIND_FLOAT(sky_height, "0.0,100.0,0.5")
BIND_FLOAT(flat_center_radius, "0.0,50.0,0.5")
BIND_FLOAT(terrain_size, "10.0,500.0,1.0")
// -- Spherical group --
ADD_GROUP("Spherical", "");
BIND_BOOL(spherical)
BIND_FLOAT(planet_radius, "10.0,1000000.0,100.0")
BIND_INT(max_noise_octaves, "0,20,1")
BIND_INT(max_ridge_octaves, "0,20,1")
// -- Voronoi group --
ADD_GROUP("Voronoi Floors", "");
BIND_INT(voronoi_cell_count, "0,20,1")
BIND_FLOAT(floor_strength, "0.0,3.0,0.01")
BIND_FLOAT(floor_height_min, "-50.0,50.0,0.5")
BIND_FLOAT(floor_height_max, "-50.0,50.0,0.5")
BIND_FLOAT(room_height, "1.0,30.0,0.5")
#undef BIND_FLOAT
#undef BIND_INT
#undef BIND_BOOL
}
// ===========================================================================
// DCTerrainGeneratorMeshSDF
// ===========================================================================
void DCTerrainGeneratorMeshSDF::set_mesh_path(const String& p_path) {
if (mesh_path_ == p_path) return;
mesh_path_ = p_path;
reload();
emit_changed();
}
String DCTerrainGeneratorMeshSDF::get_mesh_path() const { return mesh_path_; }
void DCTerrainGeneratorMeshSDF::reload() {
if (mesh_path_.is_empty()) return;
// For OBJ files, use direct C++ loader
if (mesh_path_.ends_with(".obj")) {
String resolved = mesh_path_;
if (resolved.begins_with("res://") || resolved.begins_with("user://")) {
auto* ps = ProjectSettings::get_singleton();
if (ps) {
resolved = ps->globalize_path(resolved);
} else {
UtilityFunctions::printerr("DCTerrainGeneratorMeshSDF: ProjectSettings not available");
return;
}
}
std::string path = resolved.utf8().get_data();
if (!impl_.load_obj(path)) {
UtilityFunctions::printerr("DCTerrainGeneratorMeshSDF: failed to load OBJ ", mesh_path_);
} else {
UtilityFunctions::print("DCTerrainGeneratorMeshSDF: loaded OBJ (", impl_.triangle_count(), " tris)");
}
return;
}
// For GLB/GLTF and other Godot-supported formats, load via ResourceLoader
auto* loader = ResourceLoader::get_singleton();
if (!loader) {
UtilityFunctions::printerr("DCTerrainGeneratorMeshSDF: ResourceLoader not available");
return;
}
Ref<PackedScene> scene = loader->load(mesh_path_);
if (!scene.is_valid()) {
UtilityFunctions::printerr("DCTerrainGeneratorMeshSDF: failed to load ", mesh_path_);
return;
}
// Instantiate scene to find the first MeshInstance3D
Node* root = scene->instantiate();
if (!root) {
UtilityFunctions::printerr("DCTerrainGeneratorMeshSDF: failed to instantiate ", mesh_path_);
return;
}
// Find first MeshInstance3D (depth-first)
Ref<Mesh> mesh;
std::function<void(Node*)> find_mesh = [&](Node* node) {
if (mesh.is_valid()) return;
auto* mi = Object::cast_to<MeshInstance3D>(node);
if (mi && mi->get_mesh().is_valid()) {
mesh = mi->get_mesh();
return;
}
for (int i = 0; i < node->get_child_count(); i++) {
find_mesh(node->get_child(i));
}
};
find_mesh(root);
memdelete(root);
if (!mesh.is_valid() || mesh->get_surface_count() == 0) {
UtilityFunctions::printerr("DCTerrainGeneratorMeshSDF: no mesh found in ", mesh_path_);
return;
}
// Extract vertex and index arrays from first surface
Array arrays = mesh->surface_get_arrays(0);
PackedVector3Array verts = arrays[Mesh::ARRAY_VERTEX];
PackedInt32Array indices = arrays[Mesh::ARRAY_INDEX];
if (verts.size() < 3 || indices.size() < 3) {
UtilityFunctions::printerr("DCTerrainGeneratorMeshSDF: mesh has no geometry in ", mesh_path_);
return;
}
// Convert to raw float/int arrays for libdc
std::vector<float> positions(verts.size() * 3);
for (int i = 0; i < verts.size(); i++) {
positions[i * 3 + 0] = verts[i].x;
positions[i * 3 + 1] = verts[i].y;
positions[i * 3 + 2] = verts[i].z;
}
int num_tris = indices.size() / 3;
std::vector<int> tri_indices(indices.size());
for (int i = 0; i < indices.size(); i++) {
tri_indices[i] = indices[i];
}
if (!impl_.load_arrays(positions.data(), verts.size(), tri_indices.data(), num_tris)) {
UtilityFunctions::printerr("DCTerrainGeneratorMeshSDF: failed to build SDF from ", mesh_path_);
} else {
UtilityFunctions::print("DCTerrainGeneratorMeshSDF: loaded ", mesh_path_,
" (", impl_.triangle_count(), " tris)");
}
}
int DCTerrainGeneratorMeshSDF::get_triangle_count() const {
return impl_.triangle_count();
}
void DCTerrainGeneratorMeshSDF::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mesh_path", "path"), &DCTerrainGeneratorMeshSDF::set_mesh_path);
ClassDB::bind_method(D_METHOD("get_mesh_path"), &DCTerrainGeneratorMeshSDF::get_mesh_path);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "mesh_path", PROPERTY_HINT_GLOBAL_FILE, "*.obj"),
"set_mesh_path", "get_mesh_path");
ClassDB::bind_method(D_METHOD("reload"), &DCTerrainGeneratorMeshSDF::reload);
ClassDB::bind_method(D_METHOD("get_triangle_count"), &DCTerrainGeneratorMeshSDF::get_triangle_count);
}
// ===========================================================================
// DCTerrainGeneratorScript
// ===========================================================================
float DCTerrainGeneratorScript::ScriptBridge::evaluate(
float x, float y, float z, float lod_voxel_size) {
float ret = 0.0f;
if (GDVIRTUAL_IS_OVERRIDDEN_PTR(owner_, _evaluate)) {
GDVIRTUAL_CALL_PTR(owner_, _evaluate, x, y, z, lod_voxel_size, ret);
}
return ret;
}
void DCTerrainGeneratorScript::_bind_methods() {
GDVIRTUAL_BIND(_evaluate, "x", "y", "z", "lod_voxel_size")
}
// ---------------------------------------------------------------------------
// DCTerrainGeneratorGeiss
// ---------------------------------------------------------------------------
void DCTerrainGeneratorGeiss::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_volume_dir", "path"), &DCTerrainGeneratorGeiss::set_volume_dir);
ClassDB::bind_method(D_METHOD("get_volume_dir"), &DCTerrainGeneratorGeiss::get_volume_dir);
ClassDB::bind_method(D_METHOD("reload"), &DCTerrainGeneratorGeiss::reload);
ClassDB::bind_method(D_METHOD("is_loaded"), &DCTerrainGeneratorGeiss::is_loaded);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "volume_dir", PROPERTY_HINT_DIR),
"set_volume_dir", "get_volume_dir");
}
void DCTerrainGeneratorGeiss::set_volume_dir(const String& p_path) {
if (volume_dir_ == p_path) return;
volume_dir_ = p_path;
reload();
}
bool DCTerrainGeneratorGeiss::reload() {
loaded_ = false;
if (volume_dir_.is_empty()) return false;
// globalize so a res:// path works from the editor as well as an export.
const String abs = ProjectSettings::get_singleton()->globalize_path(volume_dir_);
loaded_ = impl_.load_volumes(std::string(abs.utf8().get_data()));
if (!loaded_) {
UtilityFunctions::push_warning(
"DCTerrainGeneratorGeiss: could not load the eight .vol files from ", volume_dir_,
" -- the generator stays GPU-disabled and will produce only air.");
}
return loaded_;
}
} // namespace godot