1use super::{Grid2D, Rng};
7
8#[derive(Debug, Clone)]
10pub struct RiverSegment {
11 pub x: usize,
12 pub y: usize,
13 pub flow: f32,
15 pub width: f32,
17 pub downstream: (i32, i32),
19}
20
21#[derive(Debug, Clone)]
23pub struct RiverNetwork {
24 pub segments: Vec<RiverSegment>,
25 pub flow_grid: Grid2D,
27 pub threshold: f32,
29}
30
31impl RiverNetwork {
32 pub fn is_river(&self, x: usize, y: usize) -> bool {
34 self.flow_grid.get(x, y) > self.threshold
35 }
36
37 pub fn width_at(&self, x: usize, y: usize) -> f32 {
39 let flow = self.flow_grid.get(x, y);
40 if flow > self.threshold {
41 (flow / self.threshold).sqrt().min(5.0)
42 } else {
43 0.0
44 }
45 }
46
47 pub fn mouths(&self, heightmap: &Grid2D, sea_level: f32) -> Vec<(usize, usize)> {
49 let mut mouths = Vec::new();
50 for seg in &self.segments {
51 let nx = (seg.x as i32 + seg.downstream.0).clamp(0, heightmap.width as i32 - 1) as usize;
52 let ny = (seg.y as i32 + seg.downstream.1).clamp(0, heightmap.height as i32 - 1) as usize;
53 if heightmap.get(nx, ny) < sea_level {
54 mouths.push((seg.x, seg.y));
55 }
56 }
57 mouths
58 }
59
60 pub fn longest_river(&self) -> Vec<(usize, usize)> {
62 if self.segments.is_empty() { return Vec::new(); }
63 let start = self.segments.iter().max_by(|a, b| a.flow.partial_cmp(&b.flow).unwrap()).unwrap();
65 vec![(start.x, start.y)]
67 }
68}
69
70pub fn generate(heightmap: &Grid2D, precipitation: &Grid2D, sea_level: f32) -> RiverNetwork {
72 let w = heightmap.width;
73 let h = heightmap.height;
74
75 let mut flow_dir = vec![(0i32, 0i32); w * h];
77 for y in 0..h {
78 for x in 0..w {
79 let center = heightmap.get(x, y);
80 let mut best_dir = (0i32, 0i32);
81 let mut best_drop = 0.0_f32;
82
83 for &(dx, dy) in &[(-1i32, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (1, -1), (-1, 1), (1, 1)] {
84 let nx = x as i32 + dx;
85 let ny = y as i32 + dy;
86 if nx < 0 || ny < 0 || nx >= w as i32 || ny >= h as i32 { continue; }
87 let nh = heightmap.get(nx as usize, ny as usize);
88 let drop = center - nh;
89 let dist = if dx.abs() + dy.abs() == 2 { 1.414 } else { 1.0 };
90 let slope = drop / dist;
91 if slope > best_drop {
92 best_drop = slope;
93 best_dir = (dx, dy);
94 }
95 }
96 flow_dir[y * w + x] = best_dir;
97 }
98 }
99
100 let mut flow_grid = Grid2D::new(w, h);
102
103 for y in 0..h {
105 for x in 0..w {
106 if heightmap.get(x, y) > sea_level {
107 flow_grid.set(x, y, precipitation.get(x, y));
108 }
109 }
110 }
111
112 let mut sorted: Vec<(usize, usize, f32)> = Vec::with_capacity(w * h);
114 for y in 0..h {
115 for x in 0..w {
116 sorted.push((x, y, heightmap.get(x, y)));
117 }
118 }
119 sorted.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap());
120
121 for &(x, y, elev) in &sorted {
123 if elev < sea_level { continue; }
124 let (dx, dy) = flow_dir[y * w + x];
125 if dx == 0 && dy == 0 { continue; }
126
127 let nx = (x as i32 + dx).clamp(0, w as i32 - 1) as usize;
128 let ny = (y as i32 + dy).clamp(0, h as i32 - 1) as usize;
129 let upstream_flow = flow_grid.get(x, y);
130 flow_grid.add(nx, ny, upstream_flow);
131 }
132
133 let threshold = compute_threshold(&flow_grid, sea_level, heightmap);
135 let mut segments = Vec::new();
136
137 for y in 0..h {
138 for x in 0..w {
139 let flow = flow_grid.get(x, y);
140 if flow > threshold && heightmap.get(x, y) > sea_level {
141 let (dx, dy) = flow_dir[y * w + x];
142 segments.push(RiverSegment {
143 x,
144 y,
145 flow,
146 width: (flow / threshold).sqrt().min(5.0),
147 downstream: (dx, dy),
148 });
149 }
150 }
151 }
152
153 RiverNetwork { segments, flow_grid, threshold }
154}
155
156fn compute_threshold(flow_grid: &Grid2D, sea_level: f32, heightmap: &Grid2D) -> f32 {
158 let mut land_flows: Vec<f32> = Vec::new();
159 for y in 0..flow_grid.height {
160 for x in 0..flow_grid.width {
161 if heightmap.get(x, y) > sea_level {
162 land_flows.push(flow_grid.get(x, y));
163 }
164 }
165 }
166 land_flows.sort_by(|a, b| a.partial_cmp(b).unwrap());
167 if land_flows.is_empty() { return 1.0; }
168 let idx = (land_flows.len() as f32 * 0.98) as usize;
169 land_flows[idx.min(land_flows.len() - 1)]
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_river_generation() {
178 let mut hm = Grid2D::new(32, 32);
180 for y in 0..32 {
181 for x in 0..32 {
182 hm.set(x, y, 1.0 - x as f32 / 32.0);
183 }
184 }
185 let precip = Grid2D::filled(32, 32, 0.5);
186 let rn = generate(&hm, &precip, 0.1);
187 assert!(!rn.segments.is_empty(), "should generate rivers on a slope");
188 }
189
190 #[test]
191 fn test_flat_no_rivers() {
192 let hm = Grid2D::filled(16, 16, 0.5);
193 let precip = Grid2D::filled(16, 16, 0.5);
194 let rn = generate(&hm, &precip, 0.4);
195 assert!(rn.threshold > 0.0);
198 }
199}