OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain commits / 66f48f4

erosion-minimal: a wrapper with nothing in it but the erosion
map_dc_diffusion's terrain is the baked diffusion tile and the filter that enhances it. The general wrapper carries a mountain layer, a relief envelope and a detail fBm because it was written when the mountain WAS the terrain; on this map mountain_height and detail_scale are both 0, so those three emit nothing but their comments and a det_offset of zero that y_mapped still subtracts. Dead weight is not free: each is a branch that can be mis-gated, and mis-gating is exactly how the filter's own parameters ended up inside want_mountain and went unset on every heightfield map (hyper-games #53 section 2). This path has no branches to mis-gate — heightfield in, filter, y-map, clip. erosion_minimal generator property, off by default; map_dc_diffusion sets it evaluate_density knobs, y_mapped, proc_density, rescale, clip. Nothing else. Emitted, erosion ON: 59,155 bytes -> 79,928 of SPIR-V, control rejects a broken body. Erosion OFF: 4,167 bytes. Spawn surface 165.0 m either way. Of the 59 KB, evaluate_density is 1,150 bytes and the graph is 1,406 -- the remaining 56.6 KB is the shared noise library, of which 19.9 KB (simplex, voronoi, musgrave, perlin) is not called by this graph at all. Splitting that is the next reduction and is a proc-nodes change, not a wrapper one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
66f48f4467fcda76a8ff312305d325564be0513b
by plankatron <fleeky@gmail.com> · 2026-09-02T02:37:42.000Z

Changed files (2) +96 -0

src/dc_terrain_generator_procnodes.cpp +79 -0
……@@ -21,6 +21,9 @@
2121namespace godot {
2222
2323void DCTerrainGeneratorProcNodes::_bind_methods() {
24 ClassDB::bind_method(D_METHOD("set_erosion_minimal", "value"), &DCTerrainGeneratorProcNodes::set_erosion_minimal);
25 ClassDB::bind_method(D_METHOD("get_erosion_minimal"), &DCTerrainGeneratorProcNodes::get_erosion_minimal);
26 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "erosion_minimal"), "set_erosion_minimal", "get_erosion_minimal");
2427 ClassDB::bind_method(D_METHOD("set_erosion_enabled", "value"), &DCTerrainGeneratorProcNodes::set_erosion_enabled);
2528 ClassDB::bind_method(D_METHOD("get_erosion_enabled"), &DCTerrainGeneratorProcNodes::get_erosion_enabled);
2629 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "erosion_enabled"), "set_erosion_enabled", "get_erosion_enabled");
……@@ -383,6 +386,13 @@ void DCTerrainGeneratorProcNodes::set_lookdev_wrapper(bool p_val) {
383386 emit_changed();
384387}
385388
389void DCTerrainGeneratorProcNodes::set_erosion_minimal(bool p_val) {
390 if (erosion_minimal_ == p_val) return;
391 erosion_minimal_ = p_val;
392 _recompile_glsl();
393 emit_changed();
394}
395
386396void DCTerrainGeneratorProcNodes::set_erosion_enabled(bool p_val) {
387397 if (erosion_enabled_ == p_val) return;
388398 erosion_enabled_ = p_val;
……@@ -914,6 +924,75 @@ void DCTerrainGeneratorProcNodes::_recompile_glsl() {
914924 // undeclared and the shader does not compile, which the map experiences as
915925 // a silent fallback. So the wrapper's noise layers are emitted only when
916926 // there is a library to support them.
927 // ── EROSION-MINIMAL WRAPPER ────────────────────────────────────────────
928 //
929 // The heightmap is the terrain and the filter is the enhancement, so this
930 // emits exactly those two and the mapping between them:
931 //
932 // proc_density = Heightfield -> Erosion (the graph, unchanged)
933 // evaluate_density = y-map, call, rescale, clip
934 //
935 // No mountain, no relief, no detail, no det_offset. Compare with the
936 // general path below, which carries all three because it was written when
937 // the mountain was the terrain -- on this map they contribute nothing but
938 // branches, and a branch that can be mis-gated is how the filter's own
939 // parameters went unset for weeks (hyper-games #53 section 2).
940 //
941 // The erosion knobs are still emitted, and still gated on the noise library
942 // that DECLARES them: with erosion_enabled false there is no library, and
943 // assigning pn_ero_* would name identifiers that do not exist.
944 if (erosion_minimal_) {
945 const bool lib = proc_glsl.find("fractal_noise_2d_lod") != -1;
946 String mm =
947 "// EROSION-MINIMAL: heightfield + erosion filter, and nothing else.\n"
948 "int pn_max_octaves;\n\n"
949 + proc_glsl + "\n"
950 + hblk_glsl +
951 "float evaluate_density(float x, float y, float z) {\n"
952 " float inv_scale = 1.0 / " + th_str + ";\n"
953 " pn_max_octaves = 99;\n"
954 + (lib ? String(
955 " pn_fade_div = 4.8;\n"
956 " pn_erosion_scale = " + String::num(erosion_scale_, 6) + ";\n"
957 " pn_ero_input_height_amplitude = " + String::num(input_height_amplitude_, 9) + ";\n"
958 " pn_ero_octave_lacunarity = " + String::num(octave_lacunarity_, 9) + ";\n"
959 " pn_ero_gully_depth = " + String::num(gully_depth_, 9) + ";\n"
960 " pn_ero_fine_gully_slope_restriction = " + String::num(fine_gully_slope_restriction_, 9) + ";\n"
961 " pn_ero_gully_normalize_threshold = " + String::num(gully_normalize_threshold_, 9) + ";\n"
962 " pn_ero_height_offset = " + String::num(height_offset_, 9) + ";\n"
963 " pn_ero_height_offset_follows_altitude = " + String::num(height_offset_follows_altitude_, 9) + ";\n"
964 " pn_ero_crease_rounding = " + String::num(crease_rounding_, 9) + ";\n"
965 " pn_ero_ridge_rounding = " + String::num(ridge_rounding_, 9) + ";\n"
966 " pn_ero_input_feature_rounding = " + String::num(input_feature_rounding_, 9) + ";\n"
967 " pn_ero_rounding_per_octave = " + String::num(rounding_per_octave_, 9) + ";\n"
968 " pn_ero_gully_onset_slope = " + String::num(gully_onset_slope_, 9) + ";\n"
969 " pn_ero_octave_onset_slope = " + String::num(octave_onset_slope_, 9) + ";\n"
970 " pn_ero_ridge_onset_slope = " + String::num(ridge_onset_slope_, 9) + ";\n"
971 " pn_ero_ridge_octave_onset = " + String::num(ridge_octave_onset_, 9) + ";\n"
972 " pn_ero_assumed_slope = " + String::num(assumed_slope_, 9) + ";\n"
973 " pn_ero_assumed_slope_mix = " + String::num(assumed_slope_mix_, 9) + ";\n"
974 // The coarsest gully spans terrain_height * gully_band_scale *
975 // cell_size in world metres and halves per octave; nyquist_octaves
976 // of that against this chunk's voxel is how many octaves it can
977 // actually resolve.
978 " pn_ero_max_octaves = nyquist_octaves(" +
979 String::num(terrain_height_ * erosion_scale_ * emit_cell_scale, 6) +
980 ", voxel_scale, 2.0);\n") : String())
981 + " float y_mapped = (y - " + floor_str + ") * " + yscale_str + ";\n"
982 " float d = proc_density(vec3(x * inv_scale, y_mapped, z * inv_scale));\n"
983 " d *= " + inv_yscale_str + ";\n"
984 " d = max(d, (" + floor_str + " - y) * 2.0);\n"
985 " d = min(d, (" + ceil_str + " - y) * 2.0);\n"
986 " return d;\n"
987 "}\n";
988 CharString u8 = mm.utf8();
989 cached_glsl_ = std::string(u8.get_data(), u8.length());
990 UtilityFunctions::print("[DCTerrainGeneratorProcNodes] EROSION-MINIMAL: ",
991 String::num_int64(cached_glsl_.size()), " bytes (erosion ",
992 erosion_enabled_ ? "ON" : "OFF", ")");
993 return;
994 }
995
917996 const bool has_noise_lib = proc_glsl.find("fractal_noise_2d_lod") != -1;
918997 const bool want_mountain = has_noise_lib && mountain_height_ != 0.0f;
919998 const bool want_detail = has_noise_lib && detail_scale_ > 0.0f;
src/dc_terrain_generator_procnodes.h +17 -0
……@@ -189,6 +189,21 @@ class DCTerrainGeneratorProcNodes : public DCTerrainGenerator {
189189 // octave loop and leave the library in place.
190190 bool erosion_enabled_ = true;
191191
192 // EROSION-MINIMAL — the wrapper with nothing in it but the erosion.
193 //
194 // The general wrapper carries a mountain layer, a relief envelope and a
195 // detail fBm, because it was written when the mountain WAS the terrain. On
196 // a heightfield map the baked tile is the terrain and those three layers are
197 // dead weight: mountain_height is 0 and detail_scale is 0, so they emit
198 // nothing but their comments and a det_offset of zero that y_mapped still
199 // subtracts.
200 //
201 // Dead weight is not free. Every one of them is a branch that can be
202 // mis-gated, and mis-gating is exactly what hid the erosion filter's own
203 // parameters inside want_mountain (hyper-games #53 section 2). This path has
204 // no branches to mis-gate: heightfield in, filter, y-map, clip.
205 bool erosion_minimal_ = false;
206
192207 // ── Layer and filter parameters ───────────────────────────────────────
193208 float mountain_scale_ = 1.0f;
194209 float mountain_height_ = 0.5f;
……@@ -295,6 +310,8 @@ public:
295310 void set_heightfield_only(bool p_val);
296311 void set_erosion_enabled(bool p_val);
297312 bool get_erosion_enabled() const { return erosion_enabled_; }
313 void set_erosion_minimal(bool p_val);
314 bool get_erosion_minimal() const { return erosion_minimal_; }
298315
299316 void set_mountain_scale(float p_val);
300317 float get_mountain_scale() const { return mountain_scale_; }