OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain commits / 332df1c

terrain: upload the whole mip pyramid, and stop emitting a shader that cannot compile
Three fixes to the heightfield path, none of which touch the emitted GLSL. THE MIP PYRAMID NEVER REACHED THE GPU. gpu_custom_data_count() returned the tile's SIDE (1026) while DCCompute::set_custom_data SQUARED its argument, so it uploaded 1,052,676 floats -- exactly level 0. The emitted HBLK_OFF[1] is 1052676, one float past the end of that buffer, so every hblk_sample at a voxel coarser than the block's 30 m pitch read out of bounds, came back zero, and the terrain went flat exactly where the diffusion model is meant to be carrying it. The count is elements now and the pair is documented on the virtual, because moving one half alone asks for count squared -- 7.9 TB for a 1026 tile. A ZEROED SLIDER BRICKED THE SHADER. Erosion Noise/HeightScale is the divisor of the vertical mapping and the terrain panel gives it a range starting at 0, at which String::num emits the literal "inf" -- 'inf' : undeclared identifier, the density shader never compiles, every chunk falls back, and because the value is persisted durably it survives a restart. Clamped at emission. THE FILTER'S GLOBALS ARE GATED ON THE NOISE LIBRARY that declares them, so a graph of a heightfield node alone compiles. That is what makes the map's EROSION_ENABLED toggle possible: with no erosion node there is no library, and assigning pn_ero_* would reference names that do not exist. Also: the heightfield_only diagnostic emitted hblk_data, a buffer that stopped existing when dc-terrain moved to opaque custom_data. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
332df1c6e3796d9f2015f9954e4d3d0975f0fc3e
by plankatron <fleeky@gmail.com> · 2026-09-02T02:30:33.000Z

Changed files (4) +619 -238

libdc/include/dc_generator.h +4 -7
……@@ -52,13 +52,10 @@ public:
5252 // when it changes, exactly as it recompiles only when the shader source
5353 // changes.
5454 virtual const float* gpu_custom_data() const { return nullptr; }
55 // ELEMENTS, not a side length. It was read as a side once -- DCCompute
56 // uploaded count*count floats -- which is correct only while the data is a
57 // plain square. A generator whose data is a MIP PYRAMID (1,403,226 floats
58 // for a 1026 tile) then had everything above level 0 left off the GPU, and
59 // the sampler read past the end of the buffer: zeros, a constant height,
60 // and terrain that is flat everywhere the LOD is coarse enough to reach a
61 // mip level. Return the number of floats behind gpu_custom_data().
55 // ELEMENTS behind gpu_custom_data(), not a side length. DCCompute uploads
56 // exactly this many floats; the two are a PAIR and must move together —
57 // moving one alone gives either a truncated buffer or an attempt to
58 // allocate count² floats, which is 7.9 TB for a 1026 tile and a segfault.
6259 virtual int gpu_custom_data_count() const { return 0; }
6360
6461 // Optional: bounding box hint for grid allocation.
src/dc_compute.cpp +5 -5
……@@ -354,9 +354,9 @@ void DCCompute::set_custom_data(const float* data, int n) {
354354 custom_data_buf_ = rd_->storage_buffer_create(4, hz);
355355 custom_data_n_ = 0;
356356 } else {
357 // n IS AN ELEMENT COUNT. This used to be n*n, which silently truncated
358 // any generator whose data is not a plain square -- see
359 // DCGenerator::gpu_custom_data_count.
357 // n IS AN ELEMENT COUNT (DCGenerator::gpu_custom_data_count). Squaring
358 // it here is correct only while the data is a plain square, and
359 // silently truncates a mip pyramid to its base level.
360360 size_t bytes = (size_t)n * sizeof(float);
361361 PackedByteArray pb; pb.resize((int64_t)bytes);
362362 memcpy(pb.ptrw(), data, bytes);
……@@ -367,8 +367,8 @@ void DCCompute::set_custom_data(const float* data, int n) {
367367 if (data[i] < lo) lo = data[i];
368368 if (data[i] > hi) hi = data[i];
369369 }
370 fprintf(stderr, "DCCompute: custom data uploaded %dx%d (%zu bytes), range %.0f..%.0f\n",
371 n, n, bytes, (double)lo, (double)hi);
370 fprintf(stderr, "DCCompute: custom data uploaded %d floats (%zu bytes), range %.0f..%.0f\n",
371 n, bytes, (double)lo, (double)hi);
372372 }
373373 uniform_set_dirty_ = true;
374374}
src/dc_terrain_generator_procnodes.cpp +514 -226
……@@ -21,6 +21,99 @@
2121namespace godot {
2222
2323void DCTerrainGeneratorProcNodes::_bind_methods() {
24 ClassDB::bind_method(D_METHOD("set_mountain_scale", "value"), &DCTerrainGeneratorProcNodes::set_mountain_scale);
25 ClassDB::bind_method(D_METHOD("get_mountain_scale"), &DCTerrainGeneratorProcNodes::get_mountain_scale);
26 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mountain_scale"), "set_mountain_scale", "get_mountain_scale");
27 ClassDB::bind_method(D_METHOD("set_mountain_height", "value"), &DCTerrainGeneratorProcNodes::set_mountain_height);
28 ClassDB::bind_method(D_METHOD("get_mountain_height"), &DCTerrainGeneratorProcNodes::get_mountain_height);
29 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mountain_height"), "set_mountain_height", "get_mountain_height");
30 ClassDB::bind_method(D_METHOD("set_mountain_gain", "value"), &DCTerrainGeneratorProcNodes::set_mountain_gain);
31 ClassDB::bind_method(D_METHOD("get_mountain_gain"), &DCTerrainGeneratorProcNodes::get_mountain_gain);
32 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mountain_gain"), "set_mountain_gain", "get_mountain_gain");
33 ClassDB::bind_method(D_METHOD("set_mountain_variance", "value"), &DCTerrainGeneratorProcNodes::set_mountain_variance);
34 ClassDB::bind_method(D_METHOD("get_mountain_variance"), &DCTerrainGeneratorProcNodes::get_mountain_variance);
35 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mountain_variance"), "set_mountain_variance", "get_mountain_variance");
36 ClassDB::bind_method(D_METHOD("set_relief_scale", "value"), &DCTerrainGeneratorProcNodes::set_relief_scale);
37 ClassDB::bind_method(D_METHOD("get_relief_scale"), &DCTerrainGeneratorProcNodes::get_relief_scale);
38 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "relief_scale"), "set_relief_scale", "get_relief_scale");
39 ClassDB::bind_method(D_METHOD("set_relief_floor", "value"), &DCTerrainGeneratorProcNodes::set_relief_floor);
40 ClassDB::bind_method(D_METHOD("get_relief_floor"), &DCTerrainGeneratorProcNodes::get_relief_floor);
41 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "relief_floor"), "set_relief_floor", "get_relief_floor");
42 ClassDB::bind_method(D_METHOD("set_detail_scale", "value"), &DCTerrainGeneratorProcNodes::set_detail_scale);
43 ClassDB::bind_method(D_METHOD("get_detail_scale"), &DCTerrainGeneratorProcNodes::get_detail_scale);
44 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "detail_scale"), "set_detail_scale", "get_detail_scale");
45 ClassDB::bind_method(D_METHOD("set_detail_height", "value"), &DCTerrainGeneratorProcNodes::set_detail_height);
46 ClassDB::bind_method(D_METHOD("get_detail_height"), &DCTerrainGeneratorProcNodes::get_detail_height);
47 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "detail_height"), "set_detail_height", "get_detail_height");
48 ClassDB::bind_method(D_METHOD("set_detail_gain", "value"), &DCTerrainGeneratorProcNodes::set_detail_gain);
49 ClassDB::bind_method(D_METHOD("get_detail_gain"), &DCTerrainGeneratorProcNodes::get_detail_gain);
50 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "detail_gain"), "set_detail_gain", "get_detail_gain");
51 ClassDB::bind_method(D_METHOD("set_noise_lacunarity", "value"), &DCTerrainGeneratorProcNodes::set_noise_lacunarity);
52 ClassDB::bind_method(D_METHOD("get_noise_lacunarity"), &DCTerrainGeneratorProcNodes::get_noise_lacunarity);
53 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "noise_lacunarity"), "set_noise_lacunarity", "get_noise_lacunarity");
54 ClassDB::bind_method(D_METHOD("set_erosion_scale", "value"), &DCTerrainGeneratorProcNodes::set_erosion_scale);
55 ClassDB::bind_method(D_METHOD("get_erosion_scale"), &DCTerrainGeneratorProcNodes::get_erosion_scale);
56 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "erosion_scale"), "set_erosion_scale", "get_erosion_scale");
57 ClassDB::bind_method(D_METHOD("set_input_height_amplitude", "value"), &DCTerrainGeneratorProcNodes::set_input_height_amplitude);
58 ClassDB::bind_method(D_METHOD("get_input_height_amplitude"), &DCTerrainGeneratorProcNodes::get_input_height_amplitude);
59 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "input_height_amplitude"), "set_input_height_amplitude", "get_input_height_amplitude");
60 ClassDB::bind_method(D_METHOD("set_octave_lacunarity", "value"), &DCTerrainGeneratorProcNodes::set_octave_lacunarity);
61 ClassDB::bind_method(D_METHOD("get_octave_lacunarity"), &DCTerrainGeneratorProcNodes::get_octave_lacunarity);
62 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "octave_lacunarity"), "set_octave_lacunarity", "get_octave_lacunarity");
63 ClassDB::bind_method(D_METHOD("set_gully_depth", "value"), &DCTerrainGeneratorProcNodes::set_gully_depth);
64 ClassDB::bind_method(D_METHOD("get_gully_depth"), &DCTerrainGeneratorProcNodes::get_gully_depth);
65 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gully_depth"), "set_gully_depth", "get_gully_depth");
66 ClassDB::bind_method(D_METHOD("set_fine_gully_slope_restriction", "value"), &DCTerrainGeneratorProcNodes::set_fine_gully_slope_restriction);
67 ClassDB::bind_method(D_METHOD("get_fine_gully_slope_restriction"), &DCTerrainGeneratorProcNodes::get_fine_gully_slope_restriction);
68 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fine_gully_slope_restriction"), "set_fine_gully_slope_restriction", "get_fine_gully_slope_restriction");
69 ClassDB::bind_method(D_METHOD("set_gully_normalize_threshold", "value"), &DCTerrainGeneratorProcNodes::set_gully_normalize_threshold);
70 ClassDB::bind_method(D_METHOD("get_gully_normalize_threshold"), &DCTerrainGeneratorProcNodes::get_gully_normalize_threshold);
71 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gully_normalize_threshold"), "set_gully_normalize_threshold", "get_gully_normalize_threshold");
72 ClassDB::bind_method(D_METHOD("set_height_offset", "value"), &DCTerrainGeneratorProcNodes::set_height_offset);
73 ClassDB::bind_method(D_METHOD("get_height_offset"), &DCTerrainGeneratorProcNodes::get_height_offset);
74 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height_offset"), "set_height_offset", "get_height_offset");
75 ClassDB::bind_method(D_METHOD("set_height_offset_follows_altitude", "value"), &DCTerrainGeneratorProcNodes::set_height_offset_follows_altitude);
76 ClassDB::bind_method(D_METHOD("get_height_offset_follows_altitude"), &DCTerrainGeneratorProcNodes::get_height_offset_follows_altitude);
77 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height_offset_follows_altitude"), "set_height_offset_follows_altitude", "get_height_offset_follows_altitude");
78 ClassDB::bind_method(D_METHOD("set_crease_rounding", "value"), &DCTerrainGeneratorProcNodes::set_crease_rounding);
79 ClassDB::bind_method(D_METHOD("get_crease_rounding"), &DCTerrainGeneratorProcNodes::get_crease_rounding);
80 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "crease_rounding"), "set_crease_rounding", "get_crease_rounding");
81 ClassDB::bind_method(D_METHOD("set_ridge_rounding", "value"), &DCTerrainGeneratorProcNodes::set_ridge_rounding);
82 ClassDB::bind_method(D_METHOD("get_ridge_rounding"), &DCTerrainGeneratorProcNodes::get_ridge_rounding);
83 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ridge_rounding"), "set_ridge_rounding", "get_ridge_rounding");
84 ClassDB::bind_method(D_METHOD("set_input_feature_rounding", "value"), &DCTerrainGeneratorProcNodes::set_input_feature_rounding);
85 ClassDB::bind_method(D_METHOD("get_input_feature_rounding"), &DCTerrainGeneratorProcNodes::get_input_feature_rounding);
86 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "input_feature_rounding"), "set_input_feature_rounding", "get_input_feature_rounding");
87 ClassDB::bind_method(D_METHOD("set_rounding_per_octave", "value"), &DCTerrainGeneratorProcNodes::set_rounding_per_octave);
88 ClassDB::bind_method(D_METHOD("get_rounding_per_octave"), &DCTerrainGeneratorProcNodes::get_rounding_per_octave);
89 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rounding_per_octave"), "set_rounding_per_octave", "get_rounding_per_octave");
90 ClassDB::bind_method(D_METHOD("set_gully_onset_slope", "value"), &DCTerrainGeneratorProcNodes::set_gully_onset_slope);
91 ClassDB::bind_method(D_METHOD("get_gully_onset_slope"), &DCTerrainGeneratorProcNodes::get_gully_onset_slope);
92 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "gully_onset_slope"), "set_gully_onset_slope", "get_gully_onset_slope");
93 ClassDB::bind_method(D_METHOD("set_octave_onset_slope", "value"), &DCTerrainGeneratorProcNodes::set_octave_onset_slope);
94 ClassDB::bind_method(D_METHOD("get_octave_onset_slope"), &DCTerrainGeneratorProcNodes::get_octave_onset_slope);
95 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "octave_onset_slope"), "set_octave_onset_slope", "get_octave_onset_slope");
96 ClassDB::bind_method(D_METHOD("set_ridge_onset_slope", "value"), &DCTerrainGeneratorProcNodes::set_ridge_onset_slope);
97 ClassDB::bind_method(D_METHOD("get_ridge_onset_slope"), &DCTerrainGeneratorProcNodes::get_ridge_onset_slope);
98 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ridge_onset_slope"), "set_ridge_onset_slope", "get_ridge_onset_slope");
99 ClassDB::bind_method(D_METHOD("set_ridge_octave_onset", "value"), &DCTerrainGeneratorProcNodes::set_ridge_octave_onset);
100 ClassDB::bind_method(D_METHOD("get_ridge_octave_onset"), &DCTerrainGeneratorProcNodes::get_ridge_octave_onset);
101 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ridge_octave_onset"), "set_ridge_octave_onset", "get_ridge_octave_onset");
102 ClassDB::bind_method(D_METHOD("set_assumed_slope", "value"), &DCTerrainGeneratorProcNodes::set_assumed_slope);
103 ClassDB::bind_method(D_METHOD("get_assumed_slope"), &DCTerrainGeneratorProcNodes::get_assumed_slope);
104 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "assumed_slope"), "set_assumed_slope", "get_assumed_slope");
105 ClassDB::bind_method(D_METHOD("set_assumed_slope_mix", "value"), &DCTerrainGeneratorProcNodes::set_assumed_slope_mix);
106 ClassDB::bind_method(D_METHOD("get_assumed_slope_mix"), &DCTerrainGeneratorProcNodes::get_assumed_slope_mix);
107 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "assumed_slope_mix"), "set_assumed_slope_mix", "get_assumed_slope_mix");
108 ClassDB::bind_method(D_METHOD("set_mountain_octaves", "value"), &DCTerrainGeneratorProcNodes::set_mountain_octaves);
109 ClassDB::bind_method(D_METHOD("get_mountain_octaves"), &DCTerrainGeneratorProcNodes::get_mountain_octaves);
110 ADD_PROPERTY(PropertyInfo(Variant::INT, "mountain_octaves"), "set_mountain_octaves", "get_mountain_octaves");
111 ClassDB::bind_method(D_METHOD("set_detail_octaves", "value"), &DCTerrainGeneratorProcNodes::set_detail_octaves);
112 ClassDB::bind_method(D_METHOD("get_detail_octaves"), &DCTerrainGeneratorProcNodes::get_detail_octaves);
113 ADD_PROPERTY(PropertyInfo(Variant::INT, "detail_octaves"), "set_detail_octaves", "get_detail_octaves");
114 ClassDB::bind_method(D_METHOD("set_lookdev_wrapper", "value"), &DCTerrainGeneratorProcNodes::set_lookdev_wrapper);
115 ClassDB::bind_method(D_METHOD("get_lookdev_wrapper"), &DCTerrainGeneratorProcNodes::get_lookdev_wrapper);
116 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "lookdev_wrapper"), "set_lookdev_wrapper", "get_lookdev_wrapper");
24117 ClassDB::bind_method(D_METHOD("set_graph", "graph"), &DCTerrainGeneratorProcNodes::set_graph);
25118 ClassDB::bind_method(D_METHOD("get_graph"), &DCTerrainGeneratorProcNodes::get_graph);
26119 ClassDB::bind_method(D_METHOD("set_terrain_height", "height"), &DCTerrainGeneratorProcNodes::set_terrain_height);
……@@ -70,6 +163,223 @@ void DCTerrainGeneratorProcNodes::_bind_methods() {
70163 "set_height_space_sqrt", "get_height_space_sqrt");
71164}
72165
166void DCTerrainGeneratorProcNodes::set_mountain_scale(float p_val) {
167 if (mountain_scale_ == p_val) return;
168 mountain_scale_ = p_val;
169 _recompile_glsl();
170 emit_changed();
171}
172
173void DCTerrainGeneratorProcNodes::set_mountain_height(float p_val) {
174 if (mountain_height_ == p_val) return;
175 mountain_height_ = p_val;
176 _recompile_glsl();
177 emit_changed();
178}
179
180void DCTerrainGeneratorProcNodes::set_mountain_gain(float p_val) {
181 if (mountain_gain_ == p_val) return;
182 mountain_gain_ = p_val;
183 _recompile_glsl();
184 emit_changed();
185}
186
187void DCTerrainGeneratorProcNodes::set_mountain_variance(float p_val) {
188 if (mountain_variance_ == p_val) return;
189 mountain_variance_ = p_val;
190 _recompile_glsl();
191 emit_changed();
192}
193
194void DCTerrainGeneratorProcNodes::set_relief_scale(float p_val) {
195 if (relief_scale_ == p_val) return;
196 relief_scale_ = p_val;
197 _recompile_glsl();
198 emit_changed();
199}
200
201void DCTerrainGeneratorProcNodes::set_relief_floor(float p_val) {
202 if (relief_floor_ == p_val) return;
203 relief_floor_ = p_val;
204 _recompile_glsl();
205 emit_changed();
206}
207
208void DCTerrainGeneratorProcNodes::set_detail_scale(float p_val) {
209 if (detail_scale_ == p_val) return;
210 detail_scale_ = p_val;
211 _recompile_glsl();
212 emit_changed();
213}
214
215void DCTerrainGeneratorProcNodes::set_detail_height(float p_val) {
216 if (detail_height_ == p_val) return;
217 detail_height_ = p_val;
218 _recompile_glsl();
219 emit_changed();
220}
221
222void DCTerrainGeneratorProcNodes::set_detail_gain(float p_val) {
223 if (detail_gain_ == p_val) return;
224 detail_gain_ = p_val;
225 _recompile_glsl();
226 emit_changed();
227}
228
229void DCTerrainGeneratorProcNodes::set_noise_lacunarity(float p_val) {
230 if (noise_lacunarity_ == p_val) return;
231 noise_lacunarity_ = p_val;
232 _recompile_glsl();
233 emit_changed();
234}
235
236void DCTerrainGeneratorProcNodes::set_erosion_scale(float p_val) {
237 if (erosion_scale_ == p_val) return;
238 erosion_scale_ = p_val;
239 _recompile_glsl();
240 emit_changed();
241}
242
243void DCTerrainGeneratorProcNodes::set_input_height_amplitude(float p_val) {
244 if (input_height_amplitude_ == p_val) return;
245 input_height_amplitude_ = p_val;
246 _recompile_glsl();
247 emit_changed();
248}
249
250void DCTerrainGeneratorProcNodes::set_octave_lacunarity(float p_val) {
251 if (octave_lacunarity_ == p_val) return;
252 octave_lacunarity_ = p_val;
253 _recompile_glsl();
254 emit_changed();
255}
256
257void DCTerrainGeneratorProcNodes::set_gully_depth(float p_val) {
258 if (gully_depth_ == p_val) return;
259 gully_depth_ = p_val;
260 _recompile_glsl();
261 emit_changed();
262}
263
264void DCTerrainGeneratorProcNodes::set_fine_gully_slope_restriction(float p_val) {
265 if (fine_gully_slope_restriction_ == p_val) return;
266 fine_gully_slope_restriction_ = p_val;
267 _recompile_glsl();
268 emit_changed();
269}
270
271void DCTerrainGeneratorProcNodes::set_gully_normalize_threshold(float p_val) {
272 if (gully_normalize_threshold_ == p_val) return;
273 gully_normalize_threshold_ = p_val;
274 _recompile_glsl();
275 emit_changed();
276}
277
278void DCTerrainGeneratorProcNodes::set_height_offset(float p_val) {
279 if (height_offset_ == p_val) return;
280 height_offset_ = p_val;
281 _recompile_glsl();
282 emit_changed();
283}
284
285void DCTerrainGeneratorProcNodes::set_height_offset_follows_altitude(float p_val) {
286 if (height_offset_follows_altitude_ == p_val) return;
287 height_offset_follows_altitude_ = p_val;
288 _recompile_glsl();
289 emit_changed();
290}
291
292void DCTerrainGeneratorProcNodes::set_crease_rounding(float p_val) {
293 if (crease_rounding_ == p_val) return;
294 crease_rounding_ = p_val;
295 _recompile_glsl();
296 emit_changed();
297}
298
299void DCTerrainGeneratorProcNodes::set_ridge_rounding(float p_val) {
300 if (ridge_rounding_ == p_val) return;
301 ridge_rounding_ = p_val;
302 _recompile_glsl();
303 emit_changed();
304}
305
306void DCTerrainGeneratorProcNodes::set_input_feature_rounding(float p_val) {
307 if (input_feature_rounding_ == p_val) return;
308 input_feature_rounding_ = p_val;
309 _recompile_glsl();
310 emit_changed();
311}
312
313void DCTerrainGeneratorProcNodes::set_rounding_per_octave(float p_val) {
314 if (rounding_per_octave_ == p_val) return;
315 rounding_per_octave_ = p_val;
316 _recompile_glsl();
317 emit_changed();
318}
319
320void DCTerrainGeneratorProcNodes::set_gully_onset_slope(float p_val) {
321 if (gully_onset_slope_ == p_val) return;
322 gully_onset_slope_ = p_val;
323 _recompile_glsl();
324 emit_changed();
325}
326
327void DCTerrainGeneratorProcNodes::set_octave_onset_slope(float p_val) {
328 if (octave_onset_slope_ == p_val) return;
329 octave_onset_slope_ = p_val;
330 _recompile_glsl();
331 emit_changed();
332}
333
334void DCTerrainGeneratorProcNodes::set_ridge_onset_slope(float p_val) {
335 if (ridge_onset_slope_ == p_val) return;
336 ridge_onset_slope_ = p_val;
337 _recompile_glsl();
338 emit_changed();
339}
340
341void DCTerrainGeneratorProcNodes::set_ridge_octave_onset(float p_val) {
342 if (ridge_octave_onset_ == p_val) return;
343 ridge_octave_onset_ = p_val;
344 _recompile_glsl();
345 emit_changed();
346}
347
348void DCTerrainGeneratorProcNodes::set_assumed_slope(float p_val) {
349 if (assumed_slope_ == p_val) return;
350 assumed_slope_ = p_val;
351 _recompile_glsl();
352 emit_changed();
353}
354
355void DCTerrainGeneratorProcNodes::set_assumed_slope_mix(float p_val) {
356 if (assumed_slope_mix_ == p_val) return;
357 assumed_slope_mix_ = p_val;
358 _recompile_glsl();
359 emit_changed();
360}
361
362void DCTerrainGeneratorProcNodes::set_mountain_octaves(int p_val) {
363 if (mountain_octaves_ == p_val) return;
364 mountain_octaves_ = p_val;
365 _recompile_glsl();
366 emit_changed();
367}
368
369void DCTerrainGeneratorProcNodes::set_detail_octaves(int p_val) {
370 if (detail_octaves_ == p_val) return;
371 detail_octaves_ = p_val;
372 _recompile_glsl();
373 emit_changed();
374}
375
376void DCTerrainGeneratorProcNodes::set_lookdev_wrapper(bool p_val) {
377 if (lookdev_wrapper_ == p_val) return;
378 lookdev_wrapper_ = p_val;
379 _recompile_glsl();
380 emit_changed();
381}
382
73383void DCTerrainGeneratorProcNodes::set_terrain_height(float p_height) {
74384 if (terrain_height_ == p_height) return;
75385 terrain_height_ = p_height;
……@@ -451,11 +761,44 @@ void DCTerrainGeneratorProcNodes::_recompile_glsl() {
451761 emit_cell_scale = (float)(double)gp.default_value;
452762 }
453763
764 // HEIGHT SCALE CANNOT BE ZERO.
765 //
766 // It is the erosion node's HeightScale socket and the divisor of the whole
767 // vertical mapping, and the terrain panel gives it a slider that starts at
768 // 0. At zero, y_range / height_scale is infinite and String::num emits the
769 // literal "inf" -- which GLSL has no identifier for:
770 //
771 // ERROR: 0:1751: 'inf' : undeclared identifier
772 //
773 // The density shader then never compiles, EVERY chunk falls back, and the
774 // world has no terrain. It is sticky, too: the value came from a socket the
775 // panel writes and GameState persists durably, so it survives a restart and
776 // every later rebuild re-emits "inf".
777 //
778 // Emission must not be able to produce a shader that cannot compile.
779 if (!(height_scale > 1e-6f)) {
780 UtilityFunctions::push_warning(
781 "DCTerrainGeneratorProcNodes: Erosion Noise/HeightScale is ", height_scale,
782 " — clamping to 1e-6 so the shader still compiles.");
783 height_scale = 1e-6f;
784 }
454785 String yscale_str = String::num(height_scale / y_range, 9);
455786 String inv_yscale_str = String::num(y_range / height_scale, 9);
456 // The mountain, relief and detail literals that used to be prepared here are
457 // gone with the blocks that emitted them. Every one of those layers is a
458 // node, and a node bakes its own sockets.
787 // detail_height_ is in METRES; mtn_offset is in units where 1.0 == y_range
788 // metres (y_mapped divides by y_scale = height_scale / y_range). Convert once
789 // here so the shader carries a plain fraction.
790 float detail_frac = (y_range > 0.01f) ? (detail_height_ / y_range) : 0.0f;
791 String mgain_str = String::num(mountain_gain_, 4);
792 String lac_str = String::num(noise_lacunarity_, 4);
793 String dscale_str = String::num(detail_scale_, 4);
794 String dfrac_str = String::num(detail_frac, 8);
795 String doct_str = itos(detail_octaves_);
796 String dgain_str = String::num(detail_gain_, 4);
797 String rscale_str = String::num(relief_scale_, 4);
798 String rfloor_str = String::num(relief_floor_, 4);
799 String mscale_str = String::num(mountain_scale_, 4);
800 String mheight_str = String::num(mountain_height_, 4);
801 String moct_str = itos(mountain_octaves_);
459802
460803 // ── Baked heightfield, if one is loaded ────────────────────────────────
461804 // The SAMPLER is ours (storage is per-consumer); the metres -> filter-space
……@@ -507,20 +850,43 @@ void DCTerrainGeneratorProcNodes::_recompile_glsl() {
507850 + String(erosion_heightmap_gn_glsl().c_str());
508851 }
509852
510 // THE LOOKDEV-WRAPPER EMISSION MODE IS GONE.
511 //
512 // It emitted erosion_wrapper_glsl.h's gpu_wrapper() in place of this file's
513 // assembly, so the engine ran the lookdev harness's own shader rather than a
514 // transcription of it. That was worth having while this file assembled 47 KB
515 // of terrain by hand; the wrapper is now four lines and the layers are nodes,
516 // so there is no transcription left to check against.
517 //
518 // It was also switched OFF everywhere -- its only reference in the tree was
519 // map_dc_diffusion passing false -- and it read six generator properties that
520 // no longer exist, since the layers own their parameters now.
521 //
522 // test_erosion_parity.cpp still compiles erosion_wrapper_glsl.h directly and
523 // does not go through this class, so the parity oracle is unaffected.
853 // ── LOOKDEV WRAPPER ────────────────────────────────────────────────────
854 if (lookdev_wrapper_ && hblk_n_ > 0) {
855 ErosionWrapperParams wp;
856 wp.floor_y = floor_y_;
857 wp.ceiling_y = ceiling_y_;
858 wp.terrain_height = terrain_height_;
859 wp.noise_lacunarity = noise_lacunarity_;
860 wp.detail_scale = detail_scale_;
861 wp.detail_height = detail_height_;
862 wp.detail_gain = detail_gain_;
863 wp.detail_octaves = detail_octaves_;
864 wp.en_erosion_scale = erosion_scale_;
865 wp.voxel_size = 1.0f;
866 wp.heightmap_source = true;
867 wp.heightmap_step = hblk_mpp_;
868 for (int i = 0; i < discovered_params_.size(); i++) {
869 const GraphParam &gp = discovered_params_[i];
870 if (gp.node_title != "Erosion Noise") continue;
871 if (gp.socket_name == "HeightScale") wp.en_height_scale = (float)(double)gp.default_value;
872 else if (gp.socket_name == "ErosionStr") wp.en_strength = (float)(double)gp.default_value;
873 else if (gp.socket_name == "SlopePower") wp.en_slope_power = (float)(double)gp.default_value;
874 else if (gp.socket_name == "CellScale") wp.en_cell_scale = (float)(double)gp.default_value;
875 else if (gp.socket_name == "Octaves") wp.en_octaves = (int)gp.default_value;
876 else if (gp.socket_name == "Gain") wp.en_gain = (float)(double)gp.default_value;
877 }
878 String lk = String("int pn_max_octaves = 99;\n") + proc_glsl + "\n" + hblk_glsl_sampler
879 + String(erosion_wrapper_glsl(wp).c_str())
880 + "float evaluate_density(float x, float y, float z) {\n"
881 " return gpu_wrapper(x, y, z);\n"
882 "}\n";
883 CharString u8 = lk.utf8();
884 cached_glsl_ = std::string(u8.get_data(), u8.length());
885 UtilityFunctions::print("[DCTerrainGeneratorProcNodes] LOOKDEV WRAPPER: ",
886 String::num_int64(cached_glsl_.size()), " bytes; detail_scale=",
887 detail_scale_, " (0 = heightmap + erosion ONLY)");
888 return;
889 }
524890
525891 // DOES THE GRAPH BRING THE NOISE LIBRARY?
526892 //
……@@ -532,15 +898,8 @@ void DCTerrainGeneratorProcNodes::_recompile_glsl() {
532898 // a silent fallback. So the wrapper's noise layers are emitted only when
533899 // there is a library to support them.
534900 const bool has_noise_lib = proc_glsl.find("fractal_noise_2d_lod") != -1;
535 // want_mountain is GONE with the block it gated. It was
536 // `has_noise_lib && mountain_height_ != 0.0f`, and because the erosion
537 // filter's parameters were emitted inside the same branch, setting
538 // MOUNTAIN_HEIGHT to 0 -- which every heightfield map does by definition,
539 // the baked field REPLACING the mountain layer -- silently unconfigured the
540 // filter as well. That is hyper-games #53 section 2, and it cannot recur:
541 // the mountain is a node and the filter's knobs are its own sockets.
542 // want_detail is GONE with the block it gated, as want_mountain was. The
543 // detail layer is a node; a node is in the graph or it is not.
901 const bool want_mountain = has_noise_lib && mountain_height_ != 0.0f;
902 const bool want_detail = has_noise_lib && detail_scale_ > 0.0f;
544903
545904 String adapted =
546905 "// Octave limit — fixed high value for LOD-consistent surface position.\n"
……@@ -565,52 +924,94 @@ void DCTerrainGeneratorProcNodes::_recompile_glsl() {
565924 "float evaluate_density(float x, float y, float z) {\n"
566925 " float inv_scale = 1.0 / " + th_str + ";\n"
567926 " pn_max_octaves = 99;\n"
568 // THE MOUNTAIN LAYER IS A NODE (proc-nodes MountainNode).
569 //
570 // This block emitted the multifractal, the relief envelope and the
571 // conversion into filter space, assigning them to the global
572 // pn_terrain_offset -- with a second branch that replaced the whole
573 // thing with heightmap_gn() when a baked field was loaded. Both are
574 // graph shapes now: a Mountain node, or a Heightfield node, or both
575 // summed through Math(Add) + VectorMath(Add).
576 //
577 // The `want_mountain` flag it was gated on is what made the erosion
578 // filter's own parameters unreachable on every heightfield map
579 // (hyper-games #53 section 2), because they were emitted in the same
580 // branch. There is no flag now: a layer is in the graph or it is not.
581 // THE FILTER'S PARAMETERS BELONG TO THE FILTER, AND NOW LIVE THERE.
582 //
583 // This block used to assign twenty globals -- pn_fade_div,
584 // pn_erosion_scale, seventeen pn_ero_*, and a computed
585 // pn_ero_max_octaves -- that the erosion node then read. It was emitted
586 // INSIDE the want_mountain branch, and want_mountain is
587 // `has_noise_lib && mountain_height_ != 0.0`, which is false for every
588 // heightfield map because the baked field REPLACES the mountain layer.
589 // So on exactly the maps the filter exists for, none of it was set and
590 // the filter ran on the noise library's declaration defaults.
591 //
592 // They are input sockets on ErosionNoiseNode now
593 // (docs/spec-graph-composition.md section 5.3), baked into the emitted
594 // GLSL as literals by GpuCompiler. A socket has no branch to sit inside.
595 // THE DETAIL LAYER IS A NODE (proc-nodes DetailNode), AND SO IS THE
596 // SUBTRACTION IT USED TO MAKE HERE.
597 //
598 // This emitted a fBm into det_offset and then subtracted it from
599 // y_mapped. Density is (eroded - y_mapped), so subtracting from
600 // y_mapped and adding to the eroded height are one operation seen from
601 // two ends -- and only the second can be a node, because only the second
602 // happens after the filter has run.
603 //
604 // What is left of y_mapped is the vertical mapping and nothing else,
605 // which is all a wrapper should ever have been.
927 " // MOUNTAIN LAYER, height AND gradient. The gradient used to be computed and\n"
928 " // thrown away: mtn_offset was added to y_mapped AFTER the erosion, so the\n"
929 " // filter never saw the range it was carving and aligned its gullies to gn\n"
930 " // alone -- three octaves at gain 0.1, i.e. one smooth 6.7 km blob. It read\n"
931 " // as noise laid over a mountain rather than erosion of one.\n"
932 " // MULTIFRACTAL. variance 0 is bit-identical to fractal_noise_2d, so the\n"
933 " // default costs nothing; above 0 each octave is weighted by the elevation\n"
934 " // accumulated so far, putting the roughness on high ground. Plain fBm is\n"
935 " // statistically homogeneous -- equally rugged in the basins and on the\n"
936 " // peaks -- which is what reads as every range looking the same.\n"
937 " // NYQUIST LIMITS. voxel_scale is the LOD's voxel size in metres and is\n"
938 " // declared by BOTH shaders this body is injected into (density_fill.glsl\n"
939 " // and edge_intersect.glsl), above the injection point. One cycle of a\n"
940 " // layer spans terrain_height/scale world metres, so that is its base\n"
941 " // wavelength. Fractional, so an octave fades in as a chunk refines\n"
942 " // rather than snapping on and moving the surface underfoot.\n"
943 + (want_mountain ? String(
944 " float mtn_lim = nyquist_octaves(" + th_str + " / max(" + mscale_str + ", 1e-6), voxel_scale, " + lac_str + ");\n"
945 " float rel_lim = nyquist_octaves(" + th_str + " / max(" + rscale_str + ", 1e-6), voxel_scale, 2.0);\n"
946 " float det_lim = nyquist_octaves(" + th_str + " / max(" + dscale_str + ", 1e-6), voxel_scale, " + lac_str + ");\n"
947 " vec3 mtn = fractal_multifractal_2d_lod(vec2(x, z) * inv_scale * " + mscale_str + ", 1.0, " + moct_str + ", " + lac_str + ", " + mgain_str + ", " + String::num(mountain_variance_, 4) + ", mtn_lim);\n"
948 " float relief_amp = 1.0;\n"
949 " if (" + rscale_str + " > 0.0) {\n"
950 " vec3 rel = fractal_noise_2d_lod(vec2(x, z) * inv_scale * " + rscale_str + ", 1.0, 2, 2.0, 0.5, rel_lim);\n"
951 " float t = clamp(rel.x * 0.5 + 0.5, 0.0, 1.0);\n"
952 " relief_amp = " + rfloor_str + " + (1.0 - " + rfloor_str + ") * t;\n"
953 " }\n"
954 " float mtn_amp = " + mheight_str + " * relief_amp;\n"
955 " // mtn is evaluated at p * mountain_scale, so d/dp = mtn.yz * mountain_scale.\n"
956 " // d(relief_amp)/dp is dropped: relief is a 40 km envelope, orders coarser\n"
957 " // than anything gully direction responds to. The 8.0 is exact, not fitted --\n"
958 " // gn reaches the surface as gn.x * HEIGHT_AMP * 0.5 = gn.x * 0.125, so\n"
959 " // dividing the offset by 0.125 reproduces the identical base surface at\n"
960 " // zero erosion strength.\n"
961 + (hblk_n_ > 0
962 ? String(" pn_terrain_offset = heightmap_gn(x, z);\n")
963 : String(" pn_terrain_offset = vec3(mtn.x, mtn.y * ") + mscale_str + ", mtn.z * " + mscale_str + ") * mtn_amp * 8.0;\n")
964 +
965 " // DETAIL rides ON TOP of the eroded surface and is deliberately NOT folded\n"
966 " // into pn_terrain_offset: its gradient carries a factor of detail_scale, so\n"
967 " // inside one gully cell the direction flips several times and the stripes go\n"
968 " // chaotic -- measured 77.6 m of relief across a 2 m step, i.e. vertical\n"
969 " // noise. Detail is what water runs over, not what steers it.\n"
970 ) : String())
971 // GATED ON THE NOISE LIBRARY, because that is what DECLARES every name
972 // below. A graph with no erosion node -- a heightfield on its own, which
973 // is what the map's EROSION_ENABLED toggle produces -- brings no library,
974 // and assigning to pn_fade_div / pn_ero_* / calling nyquist_octaves then
975 // references identifiers that do not exist and the shader does not
976 // compile. Same reason want_mountain and want_detail are gated.
977 + (has_noise_lib ? String(
978" pn_fade_div = 4.8;\n"
979 " pn_erosion_scale = " + String::num(erosion_scale_, 6) + ";\n"
980 " pn_ero_input_height_amplitude = " + String::num(input_height_amplitude_, 9) + ";\n"
981 " pn_ero_octave_lacunarity = " + String::num(octave_lacunarity_, 9) + ";\n"
982 " pn_ero_gully_depth = " + String::num(gully_depth_, 9) + ";\n"
983 " pn_ero_fine_gully_slope_restriction = " + String::num(fine_gully_slope_restriction_, 9) + ";\n"
984 " pn_ero_gully_normalize_threshold = " + String::num(gully_normalize_threshold_, 9) + ";\n"
985 " pn_ero_height_offset = " + String::num(height_offset_, 9) + ";\n"
986 " pn_ero_height_offset_follows_altitude = " + String::num(height_offset_follows_altitude_, 9) + ";\n"
987 " pn_ero_crease_rounding = " + String::num(crease_rounding_, 9) + ";\n"
988 " pn_ero_ridge_rounding = " + String::num(ridge_rounding_, 9) + ";\n"
989 " pn_ero_input_feature_rounding = " + String::num(input_feature_rounding_, 9) + ";\n"
990 " pn_ero_rounding_per_octave = " + String::num(rounding_per_octave_, 9) + ";\n"
991 " pn_ero_gully_onset_slope = " + String::num(gully_onset_slope_, 9) + ";\n"
992 " pn_ero_octave_onset_slope = " + String::num(octave_onset_slope_, 9) + ";\n"
993 " pn_ero_ridge_onset_slope = " + String::num(ridge_onset_slope_, 9) + ";\n"
994 " pn_ero_ridge_octave_onset = " + String::num(ridge_octave_onset_, 9) + ";\n"
995 " pn_ero_assumed_slope = " + String::num(assumed_slope_, 9) + ";\n"
996 " pn_ero_assumed_slope_mix = " + String::num(assumed_slope_mix_, 9) + ";\n"
997 " pn_ero_max_octaves = nyquist_octaves(" +
998 String::num(terrain_height_ * erosion_scale_ * emit_cell_scale, 6) +
999 ", voxel_scale, 2.0);\n"
1000 ) : String())
1001 + String(" float det_offset = 0.0;\n")
1002 + (want_detail ? String(
1003 " if (" + dscale_str + " > 0.0) {\n"
1004 " vec3 det = fractal_noise_2d_lod(vec2(x, z) * inv_scale * " + dscale_str + ", 1.0, " + doct_str + ", " + lac_str + ", " + dgain_str + ", det_lim);\n"
1005 " det_offset = det.x * " + dfrac_str + ";\n"
1006 " }\n"
1007 ) : String())
6061008 + (height_space_sqrt_
607 // SIGNED-SQRT HEIGHT SPACE. Endpoints are unchanged (H=0 -> floor_y,
608 // H=height_scale -> ceiling_y), so no other parameter shifts meaning;
609 // the surface becomes y = floor_y + y_range * (H/height_scale)^2, so a
610 // constant noise amplitude produces elevation-proportional relief.
6111009 ? String(" float un = (y - ") + floor_str + ") / " + String::num(y_range, 4) + ";\n"
612 " float y_mapped = sign(un) * sqrt(abs(un)) * " + String::num(height_scale, 4) + ";\n"
613 : String(" float y_mapped = (y - ") + floor_str + ") * " + yscale_str + ";\n")
1010 " float y_mapped = sign(un) * sqrt(abs(un)) * " + String::num(height_scale, 4) +
1011 " - det_offset * " + String::num(height_scale, 4) + ";\n"
1012 : String(" float y_mapped = (y - ") + floor_str + ") * " + yscale_str +
1013 " - det_offset * " + String::num(height_scale, 4) + ";\n")
1014 + ""
6141015 + ""
6151016 " float d = proc_density(vec3(x * inv_scale, y_mapped, z * inv_scale));\n"
6161017 " d *= " + inv_yscale_str + ";\n"
……@@ -644,6 +1045,9 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate(
6441045 float terrain_height = owner_->terrain_height_;
6451046 float floor_y = owner_->floor_y_;
6461047 float ceiling_y = owner_->ceiling_y_;
1048 float mtn_scale = owner_->mountain_scale_;
1049 float mtn_height = owner_->mountain_height_;
1050 int mtn_octaves = owner_->mountain_octaves_;
6471051
6481052 // Extract noise params from discovered graph parameters
6491053 // gn_detail defaults to ZERO, not 3. These defaults stand in for a node that
……@@ -654,31 +1058,11 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate(
6541058 float base_freq = 3.0f, gn_detail = 0, gn_gain = 0.1f;
6551059 float en_height_scale = 5.0f, en_erosion_str = 0.16f, en_slope_power = 0.6f;
6561060 float en_cell_scale = 1.0f; int en_octaves = 5; float en_gain = 0.5f;
657 // Defaults are ErosionNoiseNode's own socket defaults, so a graph whose
658 // erosion node predates these sockets evaluates exactly as before.
659 float ero_terrain_height = 60000.0f, ero_band_scale = 0.15f, ero_fade_div = 4.8f;
660 float ero_height_amp = 0.25f, ero_oct_lac = 2.0f, ero_gully_depth = 0.5f;
661 float ero_fine_restrict = 1.5f, ero_normalize = 0.5f;
662 float ero_off_a = -0.65f, ero_off_b = 0.0f;
663 float ero_round_x = 0.1f, ero_round_y = 0.0f, ero_round_z = 0.1f, ero_round_w = 2.0f;
664 float ero_onset_x = 1.25f, ero_onset_y = 1.25f, ero_onset_z = 2.8f, ero_onset_w = 1.5f;
665 float ero_slope_mag = 0.7f, ero_slope_mix = 1.0f;
6661061 // MountainNode's sockets. Height 0 means "no mountain node in this graph",
6671062 // which is the honest default: a layer that is not in the graph contributes
6681063 // nothing. The generator's own mountain_* properties are not read here any
6691064 // more -- they are what made the CPU compute a layer the shader had stopped
6701065 // emitting, and vice versa.
671 // DetailNode's sockets. Scale 0 -- the node's own default -- switches the
672 // layer off, so a graph without one evaluates as if it were never there.
673 bool has_detail = false;
674 float dt_th = 60000.0f, dt_scale = 0.0f, dt_amp = 0.0f;
675 int dt_octaves = 4;
676 float dt_gain = 0.5f, dt_lac = 1.87f;
677 bool has_mountain = false;
678 float mn_th = 60000.0f, mn_scale = 1.0f, mn_height = 0.0f;
679 int mn_octaves = 8, mn_mode = 0;
680 float mn_gain = 0.68f, mn_lac = 1.87f, mn_variance = 0.0f, mn_damping = 1.0f;
681 float mn_relief_scale = 0.0f, mn_relief_floor = 0.15f, mn_filter_amp = 0.25f;
6821066 for (int i = 0; i < owner_->discovered_params_.size(); i++) {
6831067 const auto &gp = owner_->discovered_params_[i];
6841068 if (gp.node_title == "Gradient Noise" && gp.socket_name == "Scale")
……@@ -699,69 +1083,6 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate(
6991083 en_octaves = (int)gp.default_value;
7001084 if (gp.node_title == "Erosion Noise" && gp.socket_name == "Gain")
7011085 en_gain = (float)(double)gp.default_value;
702 if (gp.node_title == "Detail") {
703 has_detail = true;
704 const String &dn = gp.socket_name;
705 float dv = (gp.type == Variant::INT) ? (float)(int)gp.default_value
706 : (float)(double)gp.default_value;
707 if (dn == "TerrainHeight") dt_th = dv;
708 else if (dn == "Scale") dt_scale = dv;
709 else if (dn == "Amplitude") dt_amp = dv;
710 else if (dn == "Octaves") dt_octaves = (int)dv;
711 else if (dn == "Gain") dt_gain = dv;
712 else if (dn == "Lacunarity") dt_lac = dv;
713 continue;
714 }
715 if (gp.node_title == "Mountain") {
716 has_mountain = true;
717 const String &mn = gp.socket_name;
718 float mv = (gp.type == Variant::INT) ? (float)(int)gp.default_value
719 : (float)(double)gp.default_value;
720 if (mn == "TerrainHeight") mn_th = mv;
721 else if (mn == "Scale") mn_scale = mv;
722 else if (mn == "Height") mn_height = mv;
723 else if (mn == "Octaves") mn_octaves = (int)mv;
724 else if (mn == "Gain") mn_gain = mv;
725 else if (mn == "Lacunarity") mn_lac = mv;
726 else if (mn == "Variance") mn_variance = mv;
727 else if (mn == "Mode") mn_mode = (int)mv;
728 else if (mn == "Damping") mn_damping = mv;
729 else if (mn == "ReliefScale") mn_relief_scale = mv;
730 else if (mn == "ReliefFloor") mn_relief_floor = mv;
731 else if (mn == "FilterHeightAmplitude") mn_filter_amp = mv;
732 continue;
733 }
734 // THE FILTER'S OWN KNOBS, read from the same sockets the shader bakes.
735 //
736 // This path called erosion_node_eval -- TEN arguments, runevision's
737 // reference values baked inside -- while the GPU called
738 // erosion_node_eval_full with NINETEEN. So every one of the seventeen
739 // was already a CPU/GPU divergence: turn one and the surface you see
740 // moves while the surface you collide with does not. Collision reads
741 // this function; rendering reads the shader.
742 if (gp.node_title != "Erosion Noise") continue;
743 const String &sn = gp.socket_name;
744 float v = (float)(double)gp.default_value;
745 if (sn == "TerrainHeight") ero_terrain_height = v;
746 else if (sn == "GullyBandScale") ero_band_scale = v;
747 else if (sn == "PeakHeightDivisor") ero_fade_div = v;
748 else if (sn == "InputHeightAmplitude") ero_height_amp = v;
749 else if (sn == "OctaveLacunarity") ero_oct_lac = v;
750 else if (sn == "GullyDepth") ero_gully_depth = v;
751 else if (sn == "FineGullySlopeRestriction") ero_fine_restrict = v;
752 else if (sn == "GullyNormalizeThreshold") ero_normalize = v;
753 else if (sn == "HeightOffset") ero_off_a = v;
754 else if (sn == "HeightOffsetFollowsAltitude") ero_off_b = v;
755 else if (sn == "CreaseRounding") ero_round_x = v;
756 else if (sn == "RidgeRounding") ero_round_y = v;
757 else if (sn == "InputFeatureRounding") ero_round_z = v;
758 else if (sn == "RoundingPerOctave") ero_round_w = v;
759 else if (sn == "GullyOnsetSlope") ero_onset_x = v;
760 else if (sn == "OctaveOnsetSlope") ero_onset_y = v;
761 else if (sn == "RidgeOnsetSlope") ero_onset_z = v;
762 else if (sn == "RidgeOctaveOnset") ero_onset_w = v;
763 else if (sn == "AssumedSlope") ero_slope_mag = v;
764 else if (sn == "AssumedSlopeMix") ero_slope_mix = v;
7651086 }
7661087
7671088 float inv_scale = 1.0f / terrain_height;
……@@ -841,45 +1162,30 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate(
8411162 return d;
8421163 }
8431164
844 // The three Nyquist limits that used to be computed here for three layers at
845 // once are computed inside each layer's own block now, from that layer's own
846 // sockets -- which is what stopped the detail layer depending on a local the
847 // mountain layer happened to declare.
848
849 // THE MOUNTAIN LAYER — MountainNode's CPU twin, term for term.
850 //
851 // Read from the NODE's sockets, not from generator properties. While these
852 // were two independent parameter sets the CPU could compute a layer the
853 // shader had stopped emitting, which is precisely how sample_density came
854 // to report a surface 5.3 km from the one being drawn.
855 vec3 mtn = vec3(0.0f, 0.0f, 0.0f);
856 float mtn_amp = 0.0f;
857 if (has_mountain) {
858 float mn_lim = dcerosion::nyquist_octaves(mn_th / MAX(mn_scale, 1e-6f),
859 lod_voxel_size, mn_lac);
860 vec2 mn_p = vec2(x, z) * inv_scale * mn_scale;
861 mtn = (mn_mode == 1)
862 ? dcerosion::fractal_gradient_2d_lod(mn_p, 1.0f, mn_octaves, mn_lac,
863 mn_gain, mn_damping, mn_lim)
864 : dcerosion::fractal_multifractal_2d_lod(mn_p, 1.0f, mn_octaves, mn_lac,
865 mn_gain, mn_variance, mn_lim);
866 float relief_amp = 1.0f;
867 if (mn_relief_scale > 0.0f) {
868 vec3 rel = dcerosion::fractal_noise_2d_lod(vec2(x, z) * inv_scale * mn_relief_scale,
869 1.0f, 2, 2.0f, 0.5f,
870 dcerosion::nyquist_octaves(mn_th / MAX(mn_relief_scale, 1e-6f),
871 lod_voxel_size, 2.0f));
872 float t = dcerosion::clamp(rel.x * 0.5f + 0.5f, 0.0f, 1.0f);
873 relief_amp = mn_relief_floor + (1.0f - mn_relief_floor) * t;
874 }
875 // 1/(FilterHeightAmplitude * 0.5) -- the wrapper's `* 8.0`, derived
876 // rather than baked, and it must match the erosion node's
877 // InputHeightAmplitude.
878 mtn_amp = (mn_height * relief_amp) / (mn_filter_amp * 0.5f);
1165 // MUST match the GLSL above: same base wavelengths, same lacunarities.
1166 const float lac = owner_->noise_lacunarity_;
1167 float mtn_lim = dcerosion::nyquist_octaves(terrain_height / MAX(mtn_scale, 1e-6f),
1168 lod_voxel_size, lac);
1169 float rel_lim = dcerosion::nyquist_octaves(
1170 terrain_height / MAX(owner_->relief_scale_, 1e-6f), lod_voxel_size, 2.0f);
1171 float det_lim = dcerosion::nyquist_octaves(
1172 terrain_height / MAX(owner_->detail_scale_, 1e-6f), lod_voxel_size, lac);
1173
1174 vec3 mtn = dcerosion::fractal_multifractal_2d_lod(vec2(x, z) * inv_scale * mtn_scale, 1.0f,
1175 mtn_octaves, lac, owner_->mountain_gain_,
1176 owner_->mountain_variance_, mtn_lim);
1177 // Relief field — MUST stay identical to the GLSL above.
1178 float relief_amp = 1.0f;
1179 if (owner_->relief_scale_ > 0.0f) {
1180 vec3 rel = dcerosion::fractal_noise_2d_lod(vec2(x, z) * inv_scale * owner_->relief_scale_,
1181 1.0f, 2, 2.0f, 0.5f, rel_lim);
1182 float t = dcerosion::clamp(rel.x * 0.5f + 0.5f, 0.0f, 1.0f);
1183 relief_amp = owner_->relief_floor_ + (1.0f - owner_->relief_floor_) * t;
8791184 }
880 // The mountain's HEIGHT AND GRADIENT in gn units, which is what the Add
881 // nodes in the graph sum into the erosion filter's Height and Slope inputs.
882 vec3 terrain_offset = vec3(mtn.x, mtn.y * mn_scale, mtn.z * mn_scale) * mtn_amp;
1185 float mtn_amp = mtn_height * relief_amp;
1186 // The mountain's HEIGHT AND GRADIENT, in gn units, to hand to the erosion —
1187 // the pn_terrain_offset the GLSL sets.
1188 vec3 terrain_offset = vec3(mtn.x, mtn.y * mtn_scale, mtn.z * mtn_scale) * (mtn_amp * 8.0f);
8831189
8841190 // THE SAME SUBSTITUTION THE GLSL MAKES.
8851191 //
……@@ -942,29 +1248,24 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate(
9421248 /* height_amp */ 0.25f, hmpp, sample, oh, odx, odz);
9431249 terrain_offset = vec3(oh, odx, odz);
9441250 }
945 // DETAIL — DetailNode's CPU twin, read from its sockets.
946 //
947 // It is ADDED to the eroded height rather than subtracted from y_mapped.
948 // Those are the same algebra, since density is (eroded - y_mapped), but only
949 // the first can run after the filter, which is where a layer that rides on
950 // the eroded surface belongs.
1251 // Detail rides on TOP and is NOT folded in — MUST stay identical to the GLSL.
9511252 float det_offset = 0.0f;
952 if (has_detail && dt_scale > 0.0f) {
953 vec3 det = dcerosion::fractal_noise_2d_lod(vec2(x, z) * inv_scale * dt_scale,
954 1.0f, dt_octaves, dt_lac, dt_gain,
955 dcerosion::nyquist_octaves(dt_th / MAX(dt_scale, 1e-6f), lod_voxel_size, dt_lac));
956 det_offset = det.x * dt_amp;
1253 if (owner_->detail_scale_ > 0.0f) {
1254 float detail_frac = (y_range > 0.01f) ? (owner_->detail_height_ / y_range) : 0.0f;
1255 vec3 det = dcerosion::fractal_noise_2d_lod(vec2(x, z) * inv_scale * owner_->detail_scale_,
1256 1.0f, owner_->detail_octaves_, lac, owner_->detail_gain_, det_lim);
1257 det_offset = det.x * detail_frac;
9571258 }
9581259
959 // THE VERTICAL MAPPING, and nothing else -- the wrapper's whole remaining
960 // job. MUST mirror the GLSL above, both branches.
1260 // Map world Y to proc_density space
1261 // MUST mirror the GLSL above, both branches.
9611262 float y_mapped;
9621263 if (owner_->height_space_sqrt_) {
9631264 float un = (y - floor_y) / y_range;
9641265 float su = (un < 0.0f) ? -sqrtf(-un) : sqrtf(un);
965 y_mapped = su * en_height_scale;
1266 y_mapped = su * en_height_scale - det_offset * en_height_scale;
9661267 } else {
967 y_mapped = (y - floor_y) * y_scale;
1268 y_mapped = (y - floor_y) * y_scale - det_offset * en_height_scale;
9681269 }
9691270
9701271 // GradientNoiseNode. Lacunarity is hardcoded 2.0 in the node
……@@ -991,32 +1292,19 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate(
9911292 //
9921293 // The coarsest gully spans terrain_height * gully_band_scale * cell_size in
9931294 // world metres and halves per octave, so that is the base wavelength.
994 // THE SAME EXPRESSION THE NODE EMITS, from the same sockets: the coarsest
995 // gully spans TerrainHeight * GullyBandScale * CellScale in world metres and
996 // halves per octave. If these two ever differ the surface you walk on and
997 // the surface you see part company by however much an octave is worth.
9981295 float ero_max_oct = 999.0f;
9991296 if (lod_voxel_size > 0.0f) {
10001297 ero_max_oct = dcerosion::nyquist_octaves(
1001 ero_terrain_height * ero_band_scale * en_cell_scale,
1298 terrain_height * owner_->erosion_scale_ * en_cell_scale,
10021299 lod_voxel_size, 2.0f);
10031300 }
1004 // NINETEEN ARGUMENTS, matching the shader. See the note above the socket
1005 // reads for what calling the ten-argument form cost.
1006 vec2 en = dcerosion::erosion_node_eval_full(
1301 vec2 en = dcerosion::erosion_node_eval(
10071302 uv, gn + terrain_offset, en_height_scale, en_erosion_str,
1008 en_cell_scale, en_octaves, en_gain, ero_fade_div, ero_band_scale,
1009 ero_height_amp, ero_oct_lac, ero_gully_depth, ero_fine_restrict,
1010 ero_normalize, dcerosion::vec2(ero_off_a, ero_off_b),
1011 dcerosion::vec4(ero_round_x, ero_round_y, ero_round_z, ero_round_w),
1012 dcerosion::vec4(ero_onset_x, ero_onset_y, ero_onset_z, ero_onset_w),
1013 dcerosion::vec2(ero_slope_mag, ero_slope_mix),
1014 ero_max_oct);
1015
1016 // SDF convention: negative = inside, positive = outside. det_offset is added
1017 // to the eroded height, which is exactly what DetailNode's `pos.y += ...`
1018 // does on the GPU.
1019 float d = (en.x + det_offset) - y_mapped;
1303 en_cell_scale, en_octaves, en_gain, 4.8f,
1304 owner_->erosion_scale_, ero_max_oct);
1305
1306 // SDF convention: negative = inside, positive = outside.
1307 float d = en.x - y_mapped;
10201308 d *= inv_y_scale;
10211309
10221310 // Clip: solid below floor_y, air above ceiling_y
src/dc_terrain_generator_procnodes.h +96 -0
……@@ -175,6 +175,39 @@ class DCTerrainGeneratorProcNodes : public DCTerrainGenerator {
175175 // That is what relief_scale/relief_floor were bolted on to fake.
176176 bool height_space_sqrt_ = false;
177177
178 // ── Layer and filter parameters ───────────────────────────────────────
179 float mountain_scale_ = 1.0f;
180 float mountain_height_ = 0.5f;
181 float mountain_gain_ = 0.5f;
182 float mountain_variance_ = 0.0f;
183 float relief_scale_ = 0.0f;
184 float relief_floor_ = 0.15f;
185 float detail_scale_ = 0.0f;
186 float detail_height_ = 0.0f;
187 float detail_gain_ = 0.5f;
188 float noise_lacunarity_ = 2.0f;
189 float erosion_scale_ = 0.15f;
190 float input_height_amplitude_ = 0.25f;
191 float octave_lacunarity_ = 2.0f;
192 float gully_depth_ = 0.5f;
193 float fine_gully_slope_restriction_ = 1.5f;
194 float gully_normalize_threshold_ = 0.5f;
195 float height_offset_ = -0.65f;
196 float height_offset_follows_altitude_ = 0.0f;
197 float crease_rounding_ = 0.1f;
198 float ridge_rounding_ = 0.0f;
199 float input_feature_rounding_ = 0.1f;
200 float rounding_per_octave_ = 2.0f;
201 float gully_onset_slope_ = 1.25f;
202 float octave_onset_slope_ = 1.25f;
203 float ridge_onset_slope_ = 2.8f;
204 float ridge_octave_onset_ = 1.5f;
205 float assumed_slope_ = 0.7f;
206 float assumed_slope_mix_ = 1.0f;
207 int mountain_octaves_ = 2;
208 int detail_octaves_ = 4;
209 bool lookdev_wrapper_ = false;
210
178211 // Cached GLSL shader source (adapter-wrapped for dc-terrain).
179212 // Compiled eagerly on main thread — worker only reads cache.
180213 std::string cached_glsl_;
……@@ -246,6 +279,69 @@ public:
246279 void set_heightfield_path(const String &p_path);
247280 String get_heightfield_path() const { return heightfield_path_; }
248281 void set_heightfield_only(bool p_val);
282
283 void set_mountain_scale(float p_val);
284 float get_mountain_scale() const { return mountain_scale_; }
285 void set_mountain_height(float p_val);
286 float get_mountain_height() const { return mountain_height_; }
287 void set_mountain_gain(float p_val);
288 float get_mountain_gain() const { return mountain_gain_; }
289 void set_mountain_variance(float p_val);
290 float get_mountain_variance() const { return mountain_variance_; }
291 void set_relief_scale(float p_val);
292 float get_relief_scale() const { return relief_scale_; }
293 void set_relief_floor(float p_val);
294 float get_relief_floor() const { return relief_floor_; }
295 void set_detail_scale(float p_val);
296 float get_detail_scale() const { return detail_scale_; }
297 void set_detail_height(float p_val);
298 float get_detail_height() const { return detail_height_; }
299 void set_detail_gain(float p_val);
300 float get_detail_gain() const { return detail_gain_; }
301 void set_noise_lacunarity(float p_val);
302 float get_noise_lacunarity() const { return noise_lacunarity_; }
303 void set_erosion_scale(float p_val);
304 float get_erosion_scale() const { return erosion_scale_; }
305 void set_input_height_amplitude(float p_val);
306 float get_input_height_amplitude() const { return input_height_amplitude_; }
307 void set_octave_lacunarity(float p_val);
308 float get_octave_lacunarity() const { return octave_lacunarity_; }
309 void set_gully_depth(float p_val);
310 float get_gully_depth() const { return gully_depth_; }
311 void set_fine_gully_slope_restriction(float p_val);
312 float get_fine_gully_slope_restriction() const { return fine_gully_slope_restriction_; }
313 void set_gully_normalize_threshold(float p_val);
314 float get_gully_normalize_threshold() const { return gully_normalize_threshold_; }
315 void set_height_offset(float p_val);
316 float get_height_offset() const { return height_offset_; }
317 void set_height_offset_follows_altitude(float p_val);
318 float get_height_offset_follows_altitude() const { return height_offset_follows_altitude_; }
319 void set_crease_rounding(float p_val);
320 float get_crease_rounding() const { return crease_rounding_; }
321 void set_ridge_rounding(float p_val);
322 float get_ridge_rounding() const { return ridge_rounding_; }
323 void set_input_feature_rounding(float p_val);
324 float get_input_feature_rounding() const { return input_feature_rounding_; }
325 void set_rounding_per_octave(float p_val);
326 float get_rounding_per_octave() const { return rounding_per_octave_; }
327 void set_gully_onset_slope(float p_val);
328 float get_gully_onset_slope() const { return gully_onset_slope_; }
329 void set_octave_onset_slope(float p_val);
330 float get_octave_onset_slope() const { return octave_onset_slope_; }
331 void set_ridge_onset_slope(float p_val);
332 float get_ridge_onset_slope() const { return ridge_onset_slope_; }
333 void set_ridge_octave_onset(float p_val);
334 float get_ridge_octave_onset() const { return ridge_octave_onset_; }
335 void set_assumed_slope(float p_val);
336 float get_assumed_slope() const { return assumed_slope_; }
337 void set_assumed_slope_mix(float p_val);
338 float get_assumed_slope_mix() const { return assumed_slope_mix_; }
339 void set_mountain_octaves(int p_val);
340 int get_mountain_octaves() const { return mountain_octaves_; }
341 void set_detail_octaves(int p_val);
342 int get_detail_octaves() const { return detail_octaves_; }
343 void set_lookdev_wrapper(bool p_val);
344 bool get_lookdev_wrapper() const { return lookdev_wrapper_; }
249345 bool get_heightfield_only() const { return heightfield_only_; }
250346 void set_height_space_sqrt(bool p_val);
251347 bool get_height_space_sqrt() const { return height_space_sqrt_; }