Skip to content

Commit d87e0d0

Browse files
committed
feat(hydrology): add flow breaching for drainage basin (tri and cell-based). Add flow breaching for TerrainTriMesh based on Disjktra shortest path
1 parent ca09e15 commit d87e0d0

12 files changed

Lines changed: 493 additions & 1 deletion

File tree

HighMap/include/highmap/geometry/cloud.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,16 @@ Array cloud_sdf_to_array(const Cloud &cloud,
505505
const Array *p_noise_x = nullptr,
506506
const Array *p_noise_y = nullptr);
507507

508+
/**
509+
* @brief Checks whether the point cloud contains duplicate points.
510+
*
511+
* @param cloud Input point cloud.
512+
* @param eps Distance tolerance for considering two points identical.
513+
* @param xy_only If true, compares only the X and Y coordinates.
514+
* @return True if duplicate points are found, false otherwise.
515+
*/
516+
bool has_duplicates(const Cloud &cloud, float eps = 1e-9f, bool xy_only = true);
517+
508518
/**
509519
* @brief Interpolate values from an array at the points' `(x, y)` locations.
510520
*

HighMap/include/highmap/hydrology/drainage_basin.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ class DrainageBasin
6464
const std::vector<float> &erodibility,
6565
float m_exp) const;
6666

67+
void flow_breach();
68+
69+
std::vector<std::vector<glm::vec3>> flow_breach_paths();
70+
6771
float update_elevations(const std::vector<float> &response_times,
6872
float uplift_rate,
6973
const std::vector<float> &max_slope);

HighMap/include/highmap/hydrology/drainage_basin_cell_based.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class DrainageBasinCellBased
3333
void compute_receivers(unsigned int seed = 0, float noise_strength = 0.f);
3434
void compute_receivers_priority_flood();
3535
void update_stream_tree(unsigned int seed, float noise_strength);
36+
void update_stream_tree();
3637
void update_traversals();
3738

3839
std::vector<glm::ivec2> get_outlets() const;
@@ -59,6 +60,8 @@ class DrainageBasinCellBased
5960

6061
void accumulate_area_by_outlet(Array &acc) const;
6162

63+
void flow_breach();
64+
6265
// --- Members ---
6366

6467
Array z;

HighMap/include/highmap/terrain_tri_mesh.hpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ class TerrainTriMesh
7474
}
7575
};
7676

77+
struct ShortestPathResult
78+
{
79+
std::vector<float> distance;
80+
std::vector<size_t> parent;
81+
};
82+
7783
public:
7884
TerrainTriMesh() = default;
7985
TerrainTriMesh(const std::vector<glm::vec3> &ref_points);
@@ -112,6 +118,8 @@ class TerrainTriMesh
112118

113119
void subdivise();
114120

121+
void flow_breach(float epsilon);
122+
115123
// --- Triangle walk and interp ---
116124

117125
bool barycentric(const glm::vec2 &p,
@@ -151,6 +159,24 @@ class TerrainTriMesh
151159

152160
size_t size() const;
153161

162+
// --- Shortest path ---
163+
164+
ShortestPathResult compute_shortest_paths_to_hull(
165+
bool use_delta_z = false,
166+
float elevation_weight = 1.f) const;
167+
168+
ShortestPathResult compute_shortest_paths(size_t start,
169+
bool use_delta_z = false,
170+
float elevation_weight = 1.f) const;
171+
172+
std::vector<size_t> shortest_path(size_t start,
173+
size_t end,
174+
bool use_delta_z = false,
175+
float elevation_weight = 1.f) const;
176+
177+
std::vector<size_t> path_to_hull(size_t start,
178+
const ShortestPathResult &result) const;
179+
154180
// --- Accessors ---
155181

156182
const std::vector<glm::vec3> &get_points() const;
@@ -190,4 +216,9 @@ TerrainTriMesh generate_terrain_tri_mesh_from_heightmap(const Array &z,
190216
int max_triangles = 0,
191217
int max_points = 0);
192218

219+
TerrainTriMesh generate_terrain_tri_mesh_from_heightmap_random(
220+
const Array &z,
221+
int control_points_count,
222+
std::uint32_t seed);
223+
193224
} // namespace hmap

HighMap/src/geometry/cloud_functions.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,34 @@ Array cloud_sdf_to_array(const Cloud &cloud,
6464
return array;
6565
}
6666

67+
bool has_duplicates(const Cloud &cloud, float eps, bool xy_only)
68+
{
69+
std::vector<glm::vec3> pts = cloud.to_vec3();
70+
71+
std::sort(pts.begin(),
72+
pts.end(),
73+
[](const auto &a, const auto &b)
74+
{ return a.x < b.x || (a.x == b.x && a.y < b.y); });
75+
76+
if (xy_only)
77+
{
78+
for (size_t i = 1; i < pts.size(); ++i)
79+
{
80+
glm::vec2 p0 = {pts[i].x, pts[i].y};
81+
glm::vec2 p1 = {pts[i - 1].x, pts[i - 1].y};
82+
83+
if (glm::distance(p0, p1) < eps) return true;
84+
}
85+
}
86+
else
87+
{
88+
for (size_t i = 1; i < pts.size(); ++i)
89+
if (glm::distance(pts[i], pts[i - 1]) < eps) return true;
90+
}
91+
92+
return false;
93+
}
94+
6795
std::vector<float> interpolate_values_from_array(const Cloud &cloud,
6896
const Array &array,
6997
glm::vec4 bbox)

HighMap/src/hydrology/drainage_basin.cpp

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,82 @@ std::pair<std::vector<size_t>, bool> DrainageBasin::find_subroots()
320320
return {subroot, has_lake};
321321
}
322322

323+
void DrainageBasin::flow_breach()
324+
{
325+
auto &pts = this->mesh.get_points();
326+
327+
for (const auto &[outlet, traversal] : this->traversals)
328+
{
329+
if (traversal.empty()) continue;
330+
331+
// traversal is upstream -> downstream
332+
size_t p = traversal.front();
333+
float zc = pts[p].z; // current
334+
335+
// follow receivers downstream to outlet
336+
while (true)
337+
{
338+
const size_t &r = this->receivers[p];
339+
340+
if (r == p) break; // reached outlet
341+
342+
if (pts[p].z < zc)
343+
zc = pts[p].z;
344+
else
345+
pts[p].z = zc;
346+
347+
p = r;
348+
}
349+
}
350+
}
351+
352+
std::vector<std::vector<glm::vec3>> DrainageBasin::flow_breach_paths()
353+
{
354+
auto &pts = this->mesh.get_points();
355+
356+
std::vector<std::vector<glm::vec3>> paths;
357+
paths.reserve(this->traversals.size());
358+
359+
for (const auto &[outlet, traversal] : this->traversals)
360+
{
361+
if (traversal.empty())
362+
{
363+
paths.emplace_back();
364+
continue;
365+
}
366+
367+
std::vector<glm::vec3> path;
368+
path.reserve(traversal.size());
369+
370+
// traversal is upstream -> downstream
371+
size_t p = traversal.front();
372+
float zc = pts[p].z; // current
373+
374+
path.push_back(pts[p]);
375+
376+
// follow receivers downstream to outlet
377+
while (true)
378+
{
379+
const size_t &r = this->receivers[p];
380+
381+
if (r == p) break; // reached outlet
382+
383+
if (pts[p].z < zc)
384+
zc = pts[p].z;
385+
else
386+
pts[p].z = zc;
387+
388+
path.push_back(pts[p]);
389+
390+
p = r;
391+
}
392+
393+
paths.push_back(std::move(path));
394+
}
395+
396+
return paths;
397+
}
398+
323399
const std::vector<size_t> &DrainageBasin::for_each_upstream(size_t outlet) const
324400
{
325401
return traversals.at(outlet);
@@ -341,7 +417,7 @@ std::vector<std::vector<size_t>> DrainageBasin::get_main_channels() const
341417
std::vector<size_t> channel;
342418
channel.reserve(traversal.size());
343419

344-
// traversal is upstream -> downstream
420+
// traversal is downstream -> upstream
345421
size_t p = traversal.front();
346422
channel.push_back(p);
347423

HighMap/src/hydrology/drainage_basin_cell_based.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,33 @@ std::pair<Mat<glm::ivec2>, bool> DrainageBasinCellBased::find_subroots()
293293
return {subroot, has_lake};
294294
}
295295

296+
void DrainageBasinCellBased::flow_breach()
297+
{
298+
for (const auto &[outlet, traversal] : traversals)
299+
{
300+
if (traversal.empty()) continue;
301+
302+
// traversal is upstream -> downstream
303+
glm::ivec2 p = traversal.front();
304+
float zc = z(p);
305+
306+
// follow receivers downstream to outlet
307+
while (true)
308+
{
309+
const glm::ivec2 &r = receivers(p.x, p.y);
310+
311+
if (r == p) break; // reached outlet
312+
313+
if (z(p) < zc)
314+
zc = z(p);
315+
else
316+
z(p) = zc;
317+
318+
p = r;
319+
}
320+
}
321+
}
322+
296323
std::vector<std::vector<glm::ivec2>> DrainageBasinCellBased::get_main_channels()
297324
const
298325
{
@@ -517,6 +544,11 @@ void DrainageBasinCellBased::update_stream_tree(unsigned int seed,
517544
this->update_traversals();
518545
}
519546

547+
void DrainageBasinCellBased::update_stream_tree()
548+
{
549+
this->update_stream_tree(0, 0.f);
550+
}
551+
520552
void DrainageBasinCellBased::update_traversals()
521553
{
522554
this->traversals.clear();
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* Copyright (c) 2026 Otto Link. Distributed under the terms of the GNU General
2+
* Public License. The full license is in the file LICENSE, distributed with
3+
* this software. */
4+
#include <limits>
5+
#include <queue>
6+
7+
#include "highmap/terrain_tri_mesh.hpp"
8+
9+
namespace hmap
10+
{
11+
12+
void TerrainTriMesh::flow_breach(float epsilon)
13+
{
14+
auto paths = compute_shortest_paths_to_hull(true);
15+
16+
std::vector<bool> visited(points.size(), false);
17+
std::vector<size_t> path;
18+
19+
for (size_t start = 0; start < points.size(); ++start)
20+
{
21+
std::vector<size_t> path = this->path_to_hull(start, paths);
22+
23+
for (size_t i = 0; i < path.size() - 1; ++i)
24+
{
25+
size_t child = path[i + 1];
26+
size_t parent = path[i];
27+
float required = points[parent].z - epsilon;
28+
if (points[child].z > required) points[child].z = required;
29+
}
30+
}
31+
}
32+
33+
} // namespace hmap

0 commit comments

Comments
 (0)