| … | … | @@ -1787,6 +1787,9 @@ float DCTerrainGeneratorProcNodes::_eval_at(const EvalParams &p, |
| 1787 | 1787 | // scaling the curve by it would move sea level per region. Same reason, |
| 1788 | 1788 | // same line, as the emission site. |
| 1789 | 1789 | vec3 terrain_offset; |
| 1790 | // Hoisted: the subaerial erosion mask below needs the pre-erosion elevation, |
| 1791 | // and hc is otherwise scoped to the branch that computes it. |
| 1792 | float pre_erosion_h = mtn.x; |
| 1790 | 1793 | if (hypso_enabled_ != 0.0f) { |
| 1791 | 1794 | dcerosion::vec2 hc = dcerosion::hypso_curve(mtn.x, |
| 1792 | 1795 | hypso_sea_, hypso_shelf_width_, hypso_slope_width_, hypso_abyss_, hypso_plat_slope_, |
| … | … | @@ -1794,6 +1797,7 @@ float DCTerrainGeneratorProcNodes::_eval_at(const EvalParams &p, |
| 1794 | 1797 | hypso_peak_onset_, hypso_peak_gain_); |
| 1795 | 1798 | terrain_offset = vec3(hc.x, mtn.y * mtn_scale * hc.y, |
| 1796 | 1799 | mtn.z * mtn_scale * hc.y) * (mtn_height * 8.0f); |
| 1800 | pre_erosion_h = hc.x; |
| 1797 | 1801 | } else { |
| 1798 | 1802 | terrain_offset = vec3(mtn.x, mtn.y * mtn_scale, mtn.z * mtn_scale) * (mtn_amp * 8.0f); |
| 1799 | 1803 | } |
| … | … | @@ -1882,8 +1886,21 @@ float DCTerrainGeneratorProcNodes::_eval_at(const EvalParams &p, |
| 1882 | 1886 | terrain_height * erosion_scale_ * en_cell_scale, |
| 1883 | 1887 | lod_voxel_size, 2.0f); |
| 1884 | 1888 | } |
| 1889 | // EROSION IS SUBAERIAL -- mirrors pn_erosion_mask in the emitted GLSL. |
| 1890 | // Written out rather than shared because the CPU path calls the node's |
| 1891 | // wrapper directly and has no globals to set. If these two expressions |
| 1892 | // differ, the surface you collide with and the surface you see part |
| 1893 | // company, which is what test_erosion_parity exists to catch. |
| 1894 | float ero_mask = 1.0f; |
| 1895 | if (erosion_mask_depth_ > 0.0f) { |
| 1896 | float depth_m = (get_waterline_norm() - pre_erosion_h) |
| 1897 | * mountain_height_ * erosion_height_scale_ * inv_y_scale; |
| 1898 | float t = depth_m / erosion_mask_depth_; |
| 1899 | t = t < 0.0f ? 0.0f : (t > 1.0f ? 1.0f : t); |
| 1900 | ero_mask = 1.0f - (t * t * (3.0f - 2.0f * t)); |
| 1901 | } |
| 1885 | 1902 | vec2 en = dcerosion::erosion_node_eval( |
| 1886 | | uv, gn + terrain_offset, en_height_scale, en_erosion_str, |
| 1903 | uv, gn + terrain_offset, en_height_scale, en_erosion_str * ero_mask, |
| 1887 | 1904 | en_cell_scale, en_octaves, en_gain, 4.8f, |
| 1888 | 1905 | erosion_scale_, ero_max_oct); |
| 1889 | 1906 | |
| … | … | @@ -1891,6 +1908,19 @@ float DCTerrainGeneratorProcNodes::_eval_at(const EvalParams &p, |
| 1891 | 1908 | float d = en.x - y_mapped; |
| 1892 | 1909 | d *= inv_y_scale; |
| 1893 | 1910 | |
| 1911 | // ⚠ SUBMARINE TERRAIN IS NOT MIRRORED HERE, AND THAT IS A KNOWN GAP. |
| 1912 | // The GPU density adds a 3D fBm below the coastal buffer; this CPU twin |
| 1913 | // does not, so the meshed seabed has caves and arches that the CPU path |
| 1914 | // does not know about. Everything meshes on the GPU today (GPUPATH reports |
| 1915 | // gpu_mesh 100%), so the visible surface is right -- but any consumer of |
| 1916 | // THIS function below the buffer is wrong by up to submarine_amp metres. |
| 1917 | // |
| 1918 | // The blocker is erosion_cpp_shim.h: it implements the 2D subset the |
| 1919 | // erosion filter needs, and proc_simplex.inc wants vec4, floor(vec3) and |
| 1920 | // scalar-vector operators it does not have. Extending the shim is the fix; |
| 1921 | // faking the noise here would recreate the exact divergence |
| 1922 | // test_erosion_parity exists to catch. |
| 1923 | |
| 1894 | 1924 | // Clip: solid below floor_y, air above ceiling_y |
| 1895 | 1925 | d = MAX(d, (floor_y - y) * 2.0f); |
| 1896 | 1926 | d = MIN(d, (ceiling_y - y) * 2.0f); |