| … | … | @@ -1209,8 +1209,30 @@ void DCTerrainGeneratorProcNodes::_recompile_glsl() { |
| 1209 | 1209 | |
| 1210 | 1210 | // ── ProcNodesBridge ──────────────────────────────────────────────────────────── |
| 1211 | 1211 | |
| 1212 | | float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate( |
| 1213 | | float x, float y, float z, float lod_voxel_size) { |
| 1212 | // ============================================================================ |
| 1213 | // THE DENSITY WRAPPER, SPLIT IN TWO. |
| 1214 | // |
| 1215 | // It used to be one function, and every line of it ran PER VOXEL -- including |
| 1216 | // two linear scans over discovered_params_ comparing Godot Strings, to answer |
| 1217 | // questions ("what is Erosion Noise's Gain", "is there an Erosion Noise node |
| 1218 | // at all") whose answers cannot change between two points of the same grid. |
| 1219 | // DensityGrid::fill asks 33^3 = 35937 of those per 32^3 chunk. |
| 1220 | // |
| 1221 | // So: _resolve_eval_params does the part that depends on the GRAPH and the LOD, |
| 1222 | // once; _eval_at does the part that depends on the POINT. evaluate() is the two |
| 1223 | // in sequence and is unchanged in behaviour; evaluate_batch() resolves once and |
| 1224 | // loops, which is what dc_worker_cpu's fill now calls. |
| 1225 | // |
| 1226 | // See hyper-games #64 NEXT 2. ⚠ test_erosion_parity.cpp, which that item names |
| 1227 | // as the gate, CANNOT SEE THIS FUNCTION -- it restates both wrappers itself and |
| 1228 | // its own header says so. demo/procnodes_eval_probe.gd is the instrument that |
| 1229 | // can: it drives sample_density through the real bridge, and this split was |
| 1230 | // verified bit-identical across 2601 samples with it. |
| 1231 | // ============================================================================ |
| 1232 | DCTerrainGeneratorProcNodes::EvalParams |
| 1233 | DCTerrainGeneratorProcNodes::_resolve_eval_params(float lod_voxel_size) const { |
| 1234 | EvalParams p; |
| 1235 | p.lod_voxel_size = lod_voxel_size; |
| 1214 | 1236 | // lod_voxel_size USED to be discarded here, which is why this generator had |
| 1215 | 1237 | // no Nyquist protection on any layer: the wrapper passed literal octave |
| 1216 | 1238 | // counts and the graph nodes' min(N, pn_max_octaves) is disarmed by |
| … | … | @@ -1219,12 +1241,12 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate( |
| 1219 | 1241 | // collision keeps evaluating the full field. |
| 1220 | 1242 | |
| 1221 | 1243 | // Read graph parameters (same values as the GLSL adapter uses) |
| 1222 | | float terrain_height = owner_->terrain_height_; |
| 1223 | | float floor_y = owner_->floor_y_; |
| 1224 | | float ceiling_y = owner_->ceiling_y_; |
| 1225 | | float mtn_scale = owner_->mountain_scale_; |
| 1226 | | float mtn_height = owner_->mountain_height_; |
| 1227 | | int mtn_octaves = owner_->mountain_octaves_; |
| 1244 | float terrain_height = terrain_height_; |
| 1245 | float floor_y = floor_y_; |
| 1246 | float ceiling_y = ceiling_y_; |
| 1247 | float mtn_scale = mountain_scale_; |
| 1248 | float mtn_height = mountain_height_; |
| 1249 | int mtn_octaves = mountain_octaves_; |
| 1228 | 1250 | |
| 1229 | 1251 | // Extract noise params from discovered graph parameters |
| 1230 | 1252 | // gn_detail defaults to ZERO, not 3. These defaults stand in for a node that |
| … | … | @@ -1240,8 +1262,8 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate( |
| 1240 | 1262 | // nothing. The generator's own mountain_* properties are not read here any |
| 1241 | 1263 | // more -- they are what made the CPU compute a layer the shader had stopped |
| 1242 | 1264 | // emitting, and vice versa. |
| 1243 | | for (int i = 0; i < owner_->discovered_params_.size(); i++) { |
| 1244 | | const auto &gp = owner_->discovered_params_[i]; |
| 1265 | for (int i = 0; i < discovered_params_.size(); i++) { |
| 1266 | const auto &gp = discovered_params_[i]; |
| 1245 | 1267 | if (gp.node_title == "Gradient Noise" && gp.socket_name == "Scale") |
| 1246 | 1268 | base_freq = (float)(double)gp.default_value; |
| 1247 | 1269 | if (gp.node_title == "Gradient Noise" && gp.socket_name == "Detail") |
| … | … | @@ -1268,16 +1290,6 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate( |
| 1268 | 1290 | float y_scale = en_height_scale / y_range; |
| 1269 | 1291 | float inv_y_scale = y_range / en_height_scale; |
| 1270 | 1292 | |
| 1271 | | // Mountain layer. dcerosion::fractal_noise_2d is THE one from |
| 1272 | | // erosion_filter.inc — the same text the shader compiles — so this layer |
| 1273 | | // and its GLSL twin above can no longer disagree about the noise itself. |
| 1274 | | // What they can still disagree about is the ALGEBRA around it, which is |
| 1275 | | // why the "MUST stay identical" notes below are still here and still mean |
| 1276 | | // it. Hoisting this wrapper the way erosion_node_eval was hoisted is the |
| 1277 | | // remaining piece. |
| 1278 | | using dcerosion::vec2; |
| 1279 | | using dcerosion::vec3; |
| 1280 | | |
| 1281 | 1293 | // DOES THE GRAPH ACTUALLY HAVE AN EROSION NODE? |
| 1282 | 1294 | // |
| 1283 | 1295 | // This function is a hardcoded pipeline -- gn, then terrain_offset, then |
| … | … | @@ -1291,17 +1303,80 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate( |
| 1291 | 1303 | // The honest fix is a CPU evaluator that walks the graph the way the GPU |
| 1292 | 1304 | // compiler does. Until then, match the two shapes that exist. |
| 1293 | 1305 | bool graph_has_erosion = false; |
| 1294 | | for (int i = 0; i < owner_->discovered_params_.size(); i++) { |
| 1295 | | if (owner_->discovered_params_[i].node_title == "Erosion Noise") { |
| 1306 | for (int i = 0; i < discovered_params_.size(); i++) { |
| 1307 | if (discovered_params_[i].node_title == "Erosion Noise") { |
| 1296 | 1308 | graph_has_erosion = true; |
| 1297 | 1309 | break; |
| 1298 | 1310 | } |
| 1299 | 1311 | } |
| 1300 | | if (!graph_has_erosion && owner_->hblk_n_ > 0) { |
| 1312 | p.terrain_height = terrain_height; |
| 1313 | p.floor_y = floor_y; |
| 1314 | p.ceiling_y = ceiling_y; |
| 1315 | p.mtn_scale = mtn_scale; |
| 1316 | p.mtn_height = mtn_height; |
| 1317 | p.mtn_octaves = mtn_octaves; |
| 1318 | p.base_freq = base_freq; |
| 1319 | p.gn_detail = gn_detail; |
| 1320 | p.gn_gain = gn_gain; |
| 1321 | p.en_height_scale = en_height_scale; |
| 1322 | p.en_erosion_str = en_erosion_str; |
| 1323 | p.en_slope_power = en_slope_power; |
| 1324 | p.en_cell_scale = en_cell_scale; |
| 1325 | p.en_octaves = en_octaves; |
| 1326 | p.en_gain = en_gain; |
| 1327 | p.inv_scale = inv_scale; |
| 1328 | p.y_range = y_range; |
| 1329 | p.y_scale = y_scale; |
| 1330 | p.inv_y_scale = inv_y_scale; |
| 1331 | p.graph_has_erosion = graph_has_erosion; |
| 1332 | return p; |
| 1333 | } |
| 1334 | |
| 1335 | float DCTerrainGeneratorProcNodes::_eval_at(const EvalParams &p, |
| 1336 | float x, float y, float z) const { |
| 1337 | // The resolved constants, under the names they had when this was one |
| 1338 | // function, so every line of arithmetic below is UNCHANGED TEXT rather than |
| 1339 | // a rename. That was the whole point of the split: the wrapper this lives |
| 1340 | // in once passed erosion_filter a `scale` 60x out and put the surface you |
| 1341 | // walk on 4 km from the one you see, and a refactor of it has to be |
| 1342 | // provably the same function, not carefully reviewed. |
| 1343 | const float terrain_height = p.terrain_height; |
| 1344 | const float floor_y = p.floor_y; |
| 1345 | const float ceiling_y = p.ceiling_y; |
| 1346 | const float mtn_scale = p.mtn_scale; |
| 1347 | const float mtn_height = p.mtn_height; |
| 1348 | const int mtn_octaves = p.mtn_octaves; |
| 1349 | const float base_freq = p.base_freq; |
| 1350 | const float gn_detail = p.gn_detail; |
| 1351 | const float gn_gain = p.gn_gain; |
| 1352 | const float en_height_scale = p.en_height_scale; |
| 1353 | const float en_erosion_str = p.en_erosion_str; |
| 1354 | const float en_slope_power = p.en_slope_power; |
| 1355 | const float en_cell_scale = p.en_cell_scale; |
| 1356 | const int en_octaves = p.en_octaves; |
| 1357 | const float en_gain = p.en_gain; |
| 1358 | const float inv_scale = p.inv_scale; |
| 1359 | const float y_range = p.y_range; |
| 1360 | const float y_scale = p.y_scale; |
| 1361 | const float inv_y_scale = p.inv_y_scale; |
| 1362 | const bool graph_has_erosion = p.graph_has_erosion; |
| 1363 | const float lod_voxel_size = p.lod_voxel_size; |
| 1364 | |
| 1365 | // Mountain layer. dcerosion::fractal_noise_2d is THE one from |
| 1366 | // erosion_filter.inc — the same text the shader compiles — so this layer |
| 1367 | // and its GLSL twin above can no longer disagree about the noise itself. |
| 1368 | // What they can still disagree about is the ALGEBRA around it, which is |
| 1369 | // why the "MUST stay identical" notes below are still here and still mean |
| 1370 | // it. Hoisting this wrapper the way erosion_node_eval was hoisted is the |
| 1371 | // remaining piece. |
| 1372 | using dcerosion::vec2; |
| 1373 | using dcerosion::vec3; |
| 1374 | |
| 1375 | if (!graph_has_erosion && hblk_n_ > 0) { |
| 1301 | 1376 | // Heightfield alone: density IS height minus query, exactly what the |
| 1302 | 1377 | // node emits. No filter space, no offsets, nothing to get wrong. |
| 1303 | | const float hmpp = owner_->hblk_mpp_; |
| 1304 | | float d = owner_->_hblk_sample_cpu(x, z) - y; |
| 1378 | const float hmpp = hblk_mpp_; |
| 1379 | float d = _hblk_sample_cpu(x, z) - y; |
| 1305 | 1380 | d = MAX(d, (floor_y - y) * 2.0f); |
| 1306 | 1381 | d = MIN(d, (ceiling_y - y) * 2.0f); |
| 1307 | 1382 | return d; |
| … | … | @@ -1311,33 +1386,33 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate( |
| 1311 | 1386 | // algebra to get wrong here, so if the world still looks like noise with |
| 1312 | 1387 | // this on, the field is not reaching the GPU and this is what you are |
| 1313 | 1388 | // standing on instead. |
| 1314 | | if (owner_->heightfield_only_ && owner_->hblk_n_ > 0) { |
| 1315 | | const float hmpp = owner_->hblk_mpp_; |
| 1316 | | float d = owner_->_hblk_sample_cpu(x, z) - y; |
| 1389 | if (heightfield_only_ && hblk_n_ > 0) { |
| 1390 | const float hmpp = hblk_mpp_; |
| 1391 | float d = _hblk_sample_cpu(x, z) - y; |
| 1317 | 1392 | d = MAX(d, (floor_y - y) * 2.0f); |
| 1318 | 1393 | d = MIN(d, (ceiling_y - y) * 2.0f); |
| 1319 | 1394 | return d; |
| 1320 | 1395 | } |
| 1321 | 1396 | |
| 1322 | 1397 | // MUST match the GLSL above: same base wavelengths, same lacunarities. |
| 1323 | | const float lac = owner_->noise_lacunarity_; |
| 1398 | const float lac = noise_lacunarity_; |
| 1324 | 1399 | float mtn_lim = dcerosion::nyquist_octaves(terrain_height / MAX(mtn_scale, 1e-6f), |
| 1325 | 1400 | lod_voxel_size, lac); |
| 1326 | 1401 | float rel_lim = dcerosion::nyquist_octaves( |
| 1327 | | terrain_height / MAX(owner_->relief_scale_, 1e-6f), lod_voxel_size, 2.0f); |
| 1402 | terrain_height / MAX(relief_scale_, 1e-6f), lod_voxel_size, 2.0f); |
| 1328 | 1403 | float det_lim = dcerosion::nyquist_octaves( |
| 1329 | | terrain_height / MAX(owner_->detail_scale_, 1e-6f), lod_voxel_size, lac); |
| 1404 | terrain_height / MAX(detail_scale_, 1e-6f), lod_voxel_size, lac); |
| 1330 | 1405 | |
| 1331 | 1406 | vec3 mtn = dcerosion::fractal_multifractal_2d_lod(vec2(x, z) * inv_scale * mtn_scale, 1.0f, |
| 1332 | | mtn_octaves, lac, owner_->mountain_gain_, |
| 1333 | | owner_->mountain_variance_, mtn_lim); |
| 1407 | mtn_octaves, lac, mountain_gain_, |
| 1408 | mountain_variance_, mtn_lim); |
| 1334 | 1409 | // Relief field — MUST stay identical to the GLSL above. |
| 1335 | 1410 | float relief_amp = 1.0f; |
| 1336 | | if (owner_->relief_scale_ > 0.0f) { |
| 1337 | | vec3 rel = dcerosion::fractal_noise_2d_lod(vec2(x, z) * inv_scale * owner_->relief_scale_, |
| 1411 | if (relief_scale_ > 0.0f) { |
| 1412 | vec3 rel = dcerosion::fractal_noise_2d_lod(vec2(x, z) * inv_scale * relief_scale_, |
| 1338 | 1413 | 1.0f, 2, 2.0f, 0.5f, rel_lim); |
| 1339 | 1414 | float t = dcerosion::clamp(rel.x * 0.5f + 0.5f, 0.0f, 1.0f); |
| 1340 | | relief_amp = owner_->relief_floor_ + (1.0f - owner_->relief_floor_) * t; |
| 1415 | relief_amp = relief_floor_ + (1.0f - relief_floor_) * t; |
| 1341 | 1416 | } |
| 1342 | 1417 | float mtn_amp = mtn_height * relief_amp; |
| 1343 | 1418 | // The mountain's HEIGHT AND GRADIENT, in gn units, to hand to the erosion — |
| … | … | @@ -1357,45 +1432,30 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate( |
| 1357 | 1432 | // |
| 1358 | 1433 | // The algebra is erosion_wrapper_glsl.h's erosion_heightmap_gn_cpu, sitting |
| 1359 | 1434 | // beside the GLSL it mirrors, so the two cannot drift apart quietly. |
| 1360 | | if (owner_->hblk_n_ > 0) { |
| 1361 | | const float hmpp = owner_->hblk_mpp_; |
| 1435 | if (hblk_n_ > 0) { |
| 1436 | const float hmpp = hblk_mpp_; |
| 1362 | 1437 | auto sample = [&](float sx, float sz) -> float { |
| 1363 | | return owner_->_hblk_sample_cpu(sx, sz); |
| 1438 | return _hblk_sample_cpu(sx, sz); |
| 1364 | 1439 | }; |
| 1365 | 1440 | float oh, odx, odz; |
| 1366 | 1441 | erosion_heightmap_gn_cpu(x, z, floor_y, ceiling_y, terrain_height, |
| 1367 | | /* height_amp */ 0.25f, owner_->erosion_scale_, |
| 1368 | | owner_->field_half_range_, hmpp, sample, oh, odx, odz); |
| 1369 | | terrain_offset = vec3(oh, odx, odz); |
| 1370 | | } |
| 1371 | | // THE SAME SUBSTITUTION THE GLSL MAKES, and it must stay the same one: |
| 1372 | | // collision reads this field while rendering reads the shader's, so a |
| 1373 | | // divergence here is a player falling through ground that is visibly there. |
| 1374 | | // The algebra comes from erosion_wrapper_glsl.h, beside the GLSL twin. |
| 1375 | | if (owner_->hblk_n_ > 0) { |
| 1376 | | const float hmpp = owner_->hblk_mpp_; |
| 1377 | | auto sample = [&](float sx, float sz) -> float { |
| 1378 | | return owner_->_hblk_sample_cpu(sx, sz); |
| 1379 | | }; |
| 1380 | | float oh, odx, odz; |
| 1381 | | erosion_heightmap_gn_cpu(x, z, floor_y, ceiling_y, terrain_height, |
| 1382 | | /* height_amp */ 0.25f, owner_->erosion_scale_, |
| 1383 | | owner_->field_half_range_, hmpp, sample, oh, odx, odz); |
| 1442 | /* height_amp */ 0.25f, erosion_scale_, |
| 1443 | field_half_range_, hmpp, sample, oh, odx, odz); |
| 1384 | 1444 | terrain_offset = vec3(oh, odx, odz); |
| 1385 | 1445 | } |
| 1386 | 1446 | // Detail rides on TOP and is NOT folded in — MUST stay identical to the GLSL. |
| 1387 | 1447 | float det_offset = 0.0f; |
| 1388 | | if (owner_->detail_scale_ > 0.0f) { |
| 1389 | | float detail_frac = (y_range > 0.01f) ? (owner_->detail_height_ / y_range) : 0.0f; |
| 1390 | | vec3 det = dcerosion::fractal_noise_2d_lod(vec2(x, z) * inv_scale * owner_->detail_scale_, |
| 1391 | | 1.0f, owner_->detail_octaves_, lac, owner_->detail_gain_, det_lim); |
| 1448 | if (detail_scale_ > 0.0f) { |
| 1449 | float detail_frac = (y_range > 0.01f) ? (detail_height_ / y_range) : 0.0f; |
| 1450 | vec3 det = dcerosion::fractal_noise_2d_lod(vec2(x, z) * inv_scale * detail_scale_, |
| 1451 | 1.0f, detail_octaves_, lac, detail_gain_, det_lim); |
| 1392 | 1452 | det_offset = det.x * detail_frac; |
| 1393 | 1453 | } |
| 1394 | 1454 | |
| 1395 | 1455 | // Map world Y to proc_density space |
| 1396 | 1456 | // MUST mirror the GLSL above, both branches. |
| 1397 | 1457 | float y_mapped; |
| 1398 | | if (owner_->height_space_sqrt_) { |
| 1458 | if (height_space_sqrt_) { |
| 1399 | 1459 | float un = (y - floor_y) / y_range; |
| 1400 | 1460 | float su = (un < 0.0f) ? -sqrtf(-un) : sqrtf(un); |
| 1401 | 1461 | y_mapped = su * en_height_scale - det_offset * en_height_scale; |
| … | … | @@ -1430,13 +1490,13 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate( |
| 1430 | 1490 | float ero_max_oct = 999.0f; |
| 1431 | 1491 | if (lod_voxel_size > 0.0f) { |
| 1432 | 1492 | ero_max_oct = dcerosion::nyquist_octaves( |
| 1433 | | terrain_height * owner_->erosion_scale_ * en_cell_scale, |
| 1493 | terrain_height * erosion_scale_ * en_cell_scale, |
| 1434 | 1494 | lod_voxel_size, 2.0f); |
| 1435 | 1495 | } |
| 1436 | 1496 | vec2 en = dcerosion::erosion_node_eval( |
| 1437 | 1497 | uv, gn + terrain_offset, en_height_scale, en_erosion_str, |
| 1438 | 1498 | en_cell_scale, en_octaves, en_gain, 4.8f, |
| 1439 | | owner_->erosion_scale_, ero_max_oct); |
| 1499 | erosion_scale_, ero_max_oct); |
| 1440 | 1500 | |
| 1441 | 1501 | // SDF convention: negative = inside, positive = outside. |
| 1442 | 1502 | float d = en.x - y_mapped; |
| … | … | @@ -1449,6 +1509,20 @@ float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate( |
| 1449 | 1509 | return d; |
| 1450 | 1510 | } |
| 1451 | 1511 | |
| 1512 | float DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate( |
| 1513 | float x, float y, float z, float lod_voxel_size) { |
| 1514 | return owner_->_eval_at(owner_->_resolve_eval_params(lod_voxel_size), x, y, z); |
| 1515 | } |
| 1516 | |
| 1517 | // THE POINT OF THE SPLIT. One resolve for the whole grid instead of 35937. |
| 1518 | void DCTerrainGeneratorProcNodes::ProcNodesBridge::evaluate_batch( |
| 1519 | const float *p_xyz, int p_count, float p_lod_voxel_size, float *r_out) { |
| 1520 | const EvalParams ep = owner_->_resolve_eval_params(p_lod_voxel_size); |
| 1521 | for (int i = 0; i < p_count; i++) { |
| 1522 | r_out[i] = owner_->_eval_at(ep, p_xyz[i * 3], p_xyz[i * 3 + 1], p_xyz[i * 3 + 2]); |
| 1523 | } |
| 1524 | } |
| 1525 | |
| 1452 | 1526 | bool DCTerrainGeneratorProcNodes::ProcNodesBridge::has_gpu_shader() const { |
| 1453 | 1527 | // Read-only cache check — safe to call from worker thread. |
| 1454 | 1528 | // GLSL is compiled eagerly on main thread in set_graph() / _set(). |