Skip to main content

banished_terrain/
river.rs

1//! The river strip builder: a generated path record becomes the triangle strip the map draws with
2//! the River material. See docs/TERRAIN-WATER-GEOMETRY.md.
3//!
4//! 1. The path's segments, each end moved by `(+0.5, −0.5)`, go in a spatial index (4-unit cells).
5//!    Each of them but the last puts bank points every quarter unit at the river's half width (the
6//!    first point's `params[3]` + 2) on both sides: left into bank A, right into bank B, each kept
7//!    only when no segment is nearer than the half width. The joint to the next segment adds an arc
8//!    on its outer side, 20 steps per right angle.
9//! 2. Each path point gets the average backward tangent of its two segments.
10//! 3. For each path point and four directions between its tangent and the next one, the nearest
11//!    bank point of either bank to the path point (within twice the half width) is taken. When both
12//!    banks moved on and the line between them is square to the direction, the bank stretches
13//!    walked since the last row are resampled to the same count and added as rows, with a `v`
14//!    running along the river at 1/32 of the midline's length.
15//! 4. Each pair of rows becomes four quads across, `u` 0 to 1 from bank A to bank B; equal
16//!    vertices are shared.
17//!
18//! The arithmetic follows the original's float order so the mesh matches it bit for bit.
19//!
20//! Original: `FUN_1400d4fd0`, `FUN_1400d0f00`, `FUN_1400d12c0`, `FUN_1400d0c20`, `FUN_1400d0830`.
21
22use std::collections::{BTreeMap, HashMap};
23
24use super::PathPoint;
25
26/// Cells of the spatial indices: `0x40800000` (4) and its inverse.
27const CELL_INV: f32 = 0.25;
28
29/// `(int)` truncation as `cvttss2si`.
30fn cell(v: f32) -> i32 {
31    (v * CELL_INV) as i32
32}
33
34fn length(v: [f32; 2]) -> f32 {
35    (v[0] * v[0] + v[1] * v[1]).sqrt()
36}
37
38/// `Vector2::Dot`: `other.y × this.y + other.x × this.x`.
39fn dot(a: [f32; 2], b: [f32; 2]) -> f32 {
40    b[1] * a[1] + b[0] * a[0]
41}
42
43/// `Vector2::Normalize`: the vector times `1 / length`, unchanged when zero.
44fn normalize(v: [f32; 2]) -> [f32; 2] {
45    let l = length(v);
46    if l == 0.0 {
47        return v;
48    }
49    let inv = 1.0 / l;
50    [v[0] * inv, v[1] * inv]
51}
52
53fn sub(a: [f32; 2], b: [f32; 2]) -> [f32; 2] {
54    [a[0] - b[0], a[1] - b[1]]
55}
56
57/// A segment record (`0x38` bytes; `+0x14` a, `+0x1c` b, `+0x24` direction, `+0x2c` length).
58struct Segment {
59    a: [f32; 2],
60    b: [f32; 2],
61    d: [f32; 2],
62    len: f32,
63    perp: [f32; 2],
64    /// Registered cells, inclusive.
65    lo: (i32, i32),
66    hi: (i32, i32),
67}
68
69/// `FUN_1400d0f00`: no segment registered in the cells around `p` (± 1.1 × `w`) is nearer than
70/// `w`. The projection test adds the parameter to both coordinates of `a + d`, as the
71/// original does.
72fn clear_of(segments: &[Segment], p: [f32; 2], w: f32) -> bool {
73    let r = w * 1.1;
74    let lo = (cell(p[0] - r), cell(p[1] - r));
75    let hi = (cell(p[0] + r), cell(p[1] + r));
76    for s in segments {
77        if s.hi.0 < lo.0 || hi.0 < s.lo.0 || s.hi.1 < lo.1 || hi.1 < s.lo.1 {
78            continue;
79        }
80        let da = sub(p, s.a);
81        if w > dot(da, da).sqrt() {
82            return false;
83        }
84        let db = sub(p, s.b);
85        if w > dot(db, db).sqrt() {
86            return false;
87        }
88        let t = dot(da, s.d);
89        if 0.0 < t && t < s.len {
90            let q = [t + (s.a[0] + s.d[0]), t + (s.a[1] + s.d[1])];
91            let e = sub(q, p);
92            if dot(e, e).sqrt() < w {
93                return false;
94            }
95        }
96    }
97    true
98}
99
100/// A bank: its points in order and their cells (each point in the cell of its position).
101#[derive(Default)]
102struct Bank {
103    points: Vec<[f32; 2]>,
104    cells: HashMap<(i32, i32), Vec<usize>>,
105}
106
107impl Bank {
108    fn add(&mut self, p: [f32; 2]) {
109        let i = self.points.len();
110        self.points.push(p);
111        self.cells
112            .entry((cell(p[0]), cell(p[1])))
113            .or_default()
114            .push(i);
115    }
116
117    /// `FUN_1400d12c0`: the nearest point to `p` among the cells within `2w`, its index
118    /// written to `index` (kept when there is none) and the point returned (`p` when none).
119    /// Cells go by rows; a cell's points newest first; the first nearest wins.
120    fn nearest(&self, p: [f32; 2], w: f32, index: &mut usize) -> [f32; 2] {
121        let r = w * 2.0;
122        let lo = (cell(p[0] - r), cell(p[1] - r));
123        let hi = (cell(p[0] + r), cell(p[1] + r));
124        let mut best = 1_000_000.0f32;
125        let mut out = p;
126        for y in lo.1..=hi.1 {
127            for x in lo.0..=hi.0 {
128                let Some(list) = self.cells.get(&(x, y)) else {
129                    continue;
130                };
131                for &i in list.iter().rev() {
132                    let d = length(sub(p, self.points[i]));
133                    if d < best {
134                        best = d;
135                        *index = i;
136                        out = self.points[i];
137                    }
138                }
139            }
140        }
141        out
142    }
143
144    /// `FUN_1400d0c20`: `count` points evenly along the bank from point `start`, `total` long.
145    fn resample(&self, start: usize, count: usize, total: f32) -> Vec<[f32; 2]> {
146        let pts = &self.points;
147        let step = total / count as f32;
148        let mut seg = start;
149        let mut seg_len = length(sub(pts[seg], pts[seg + 1]));
150        let mut end = seg_len;
151        let mut pos = 0.0f32;
152        let mut out = Vec::with_capacity(count);
153        while out.len() < count {
154            if pos < end {
155                let f = 1.0 - (end - pos) / seg_len;
156                let g = 1.0 - f;
157                out.push([
158                    g * pts[seg][0] + f * pts[seg + 1][0],
159                    g * pts[seg][1] + f * pts[seg + 1][1],
160                ]);
161                pos += step;
162            } else {
163                seg += 1;
164                if pts.len() <= seg + 1 {
165                    break;
166                }
167                seg_len = length(sub(pts[seg], pts[seg + 1]));
168                end += seg_len;
169            }
170        }
171        out
172    }
173
174    /// `FUN_1400d0830`: as [`Bank::resample`], each point with its `v`:
175    /// `(walked / total) × v_step + v_start`.
176    fn rows(
177        &self,
178        start: usize,
179        count: usize,
180        total: f32,
181        v_step: f32,
182        v_start: f32,
183        out: &mut Vec<([f32; 2], f32)>,
184    ) {
185        let pts = &self.points;
186        let step = total / count as f32;
187        let mut seg = start;
188        let mut seg_len = length(sub(pts[seg], pts[seg + 1]));
189        let mut end = seg_len;
190        let mut pos = 0.0f32;
191        let mut n = 0;
192        while n < count {
193            if pos < end {
194                let f = 1.0 - (end - pos) / seg_len;
195                let g = 1.0 - f;
196                let p = [
197                    g * pts[seg][0] + f * pts[seg + 1][0],
198                    g * pts[seg][1] + f * pts[seg + 1][1],
199                ];
200                let v = ((end - g * seg_len) / total) * v_step + v_start;
201                out.push((p, v));
202                pos += step;
203                n += 1;
204            } else {
205                seg += 1;
206                if pts.len() <= seg + 1 {
207                    break;
208                }
209                let d = sub(pts[seg], pts[seg + 1]);
210                seg_len = dot(d, d).sqrt();
211                end += seg_len;
212            }
213        }
214    }
215
216    /// The bank's length from point `from` to point `to` (exclusive of `to`'s next).
217    fn walked(&self, from: usize, to: usize) -> f32 {
218        let mut sum = 0.0f32;
219        for m in from..to.saturating_sub(1) {
220            let d = sub(self.points[m], self.points[m + 1]);
221            sum += dot(d, d).sqrt();
222        }
223        sum
224    }
225
226    fn walked_length(&self, from: usize, to: usize) -> f32 {
227        let mut sum = 0.0f32;
228        for m in from..to.saturating_sub(1) {
229            sum += length(sub(self.points[m], self.points[m + 1]));
230        }
231        sum
232    }
233}
234
235/// The strip of one path: vertices `(x, y, u, v)` and triangle indices.
236///
237/// Original: `FUN_1400d4fd0`.
238pub fn river_strip(path: &[PathPoint]) -> (Vec<[f32; 4]>, Vec<u16>) {
239    let n = path.len();
240    if n < 2 {
241        return (Vec::new(), Vec::new());
242    }
243    // 1. Segments.
244    let segments: Vec<Segment> = path
245        .windows(2)
246        .map(|w| {
247            let a = [w[0].pos[0] + 0.5, w[0].pos[1] - 0.5];
248            let b = [w[1].pos[0] + 0.5, w[1].pos[1] - 0.5];
249            let diff = sub(b, a);
250            let len = length(diff);
251            let d = normalize(diff);
252            let (min, max) = (
253                [a[0].min(b[0]), a[1].min(b[1])],
254                [a[0].max(b[0]), a[1].max(b[1])],
255            );
256            Segment {
257                a,
258                b,
259                d,
260                len,
261                perp: [-d[1], d[0]],
262                lo: (cell(min[0]), cell(min[1])),
263                hi: (cell(max[0]), cell(max[1])),
264            }
265        })
266        .collect();
267    let w = path[0].params[3] + 2.0;
268    let (mut bank_a, mut bank_b) = (Bank::default(), Bank::default());
269    for i in 0..n.saturating_sub(2) {
270        let s = &segments[i];
271        let next = &segments[i + 1];
272        let steps = ((s.len * 4.0) as i32).max(1);
273        let (dxl, dyl) = (s.d[0] * s.len, s.d[1] * s.len);
274        let (px, py) = (s.perp[0] * w, s.perp[1] * w);
275        for k in 0..=steps {
276            let t = k as f32 / steps as f32;
277            let p = [s.a[0] + dxl * t, dyl * t + s.a[1]];
278            let left = [p[0] - px, p[1] - py];
279            if clear_of(&segments, left, w) {
280                bank_a.add(left);
281            }
282            let right = [px + p[0], py + p[1]];
283            if clear_of(&segments, right, w) {
284                bank_b.add(right);
285            }
286        }
287        // The joint: an arc on the outer side.
288        let cross = next.d[1] * s.d[0] - next.d[0] * s.d[1];
289        let left_turn = cross > 0.0;
290        let sign = if left_turn { -1.0f32 } else { 1.0 };
291        let cos = next.d[0] * s.d[0] + next.d[1] * s.d[1];
292        let arc = ((banished_core::crt::acosf(cos) * 57.29578 / 90.0 * 20.0) as i32).max(1);
293        for k in 0..arc {
294            let t = k as f32 / arc as f32;
295            let u = 1.0 - t;
296            let p = normalize([
297                u * s.perp[0] + next.perp[0] * t,
298                u * s.perp[1] + next.perp[1] * t,
299            ]);
300            let q = [s.b[0] + p[0] * w * sign, s.b[1] + sign * (p[1] * w)];
301            if clear_of(&segments, q, w) {
302                if left_turn {
303                    bank_a.add(q);
304                } else {
305                    bank_b.add(q);
306                }
307            }
308        }
309    }
310    // 2. Tangents.
311    let tangents: Vec<[f32; 2]> = (0..n)
312        .map(|i| {
313            let back = if 0 < i {
314                normalize(sub(path[i - 1].pos, path[i].pos))
315            } else {
316                [0.0, 0.0]
317            };
318            let ahead = if i + 1 < n {
319                normalize(sub(path[i].pos, path[i + 1].pos))
320            } else {
321                [0.0, 0.0]
322            };
323            normalize([ahead[0] + back[0], ahead[1] + back[1]])
324        })
325        .collect();
326    // 3. Rows.
327    let mut rows_a: Vec<([f32; 2], f32)> = Vec::new();
328    let mut rows_b: Vec<([f32; 2], f32)> = Vec::new();
329    let (mut ia, mut ib) = (0usize, 0usize);
330    let (mut ja, mut jb) = (0usize, 0usize);
331    let mut v_acc = -16.0f32;
332    let mut prev_mid = [0.0f32; 2];
333    let mut first = true;
334    for i in 0..n - 1 {
335        for k in 0..4 {
336            let t = k as f32 * 0.25;
337            let u = 1.0 - t;
338            let dir = normalize([
339                u * tangents[i][0] + t * tangents[i + 1][0],
340                u * tangents[i][1] + t * tangents[i + 1][1],
341            ]);
342            let pa = bank_a.nearest(path[i].pos, w, &mut ja);
343            let pb = bank_b.nearest(path[i].pos, w, &mut jb);
344            if ja <= ia || jb <= ib || (ja == 0 && jb == 0) {
345                continue;
346            }
347            if dot(normalize(sub(pa, pb)), dir).abs() >= 0.02 {
348                continue;
349            }
350            let len_a = bank_a.walked(ia, ja);
351            let len_b = bank_b.walked_length(ib, jb);
352            let most = if len_b < len_a { len_a } else { len_b };
353            if len_a < 2.0 && len_b < 2.0 {
354                continue;
355            }
356            let count = (most as i32).max(1);
357            let (step_a, step_b) = (len_a / count as f32, len_b / count as f32);
358            if step_a < 0.001 || step_b < 0.001 {
359                continue;
360            }
361            let count = count as usize;
362            let a = bank_a.resample(ia, count, len_a);
363            let b = bank_b.resample(ib, count, len_b);
364            let mut mid_len = 0.0f32;
365            for m in 0..count - 1 {
366                let next = [
367                    0.5 * (a[m + 1][0] + b[m + 1][0]),
368                    0.5 * (a[m + 1][1] + b[m + 1][1]),
369                ];
370                let cur = [0.5 * (a[m][0] + b[m][0]), 0.5 * (a[m][1] + b[m][1])];
371                mid_len += length(sub(cur, next));
372            }
373            if !first {
374                let start = [0.5 * (b[0][0] + a[0][0]), 0.5 * (b[0][1] + a[0][1])];
375                v_acc += length(sub(prev_mid, start)) / 32.0;
376            }
377            let last = count - 1;
378            prev_mid = [
379                0.5 * (b[last][0] + a[last][0]),
380                0.5 * (a[last][1] + b[last][1]),
381            ];
382            first = false;
383            let v_step = mid_len / 32.0;
384            bank_a.rows(ia, count, len_a, v_step, v_acc, &mut rows_a);
385            bank_b.rows(ib, count, len_b, v_step, v_acc, &mut rows_b);
386            v_acc = rows_a.last().map_or(v_acc, |r| r.1);
387            ia = ja;
388            ib = jb;
389        }
390    }
391    // 4. Quads.
392    let mut vertices: Vec<[f32; 4]> = Vec::new();
393    let mut seen: BTreeMap<[u32; 4], u16> = BTreeMap::new();
394    let mut index = |v: [f32; 4], vertices: &mut Vec<[f32; 4]>| -> u16 {
395        // Equal floats share a vertex (0 and −0 too).
396        let key = v.map(|f| if f == 0.0 { 0 } else { f.to_bits() });
397        *seen.entry(key).or_insert_with(|| {
398            vertices.push(v);
399            (vertices.len() - 1) as u16
400        })
401    };
402    let mut indices = Vec::new();
403    for r in 0..rows_a.len().saturating_sub(1) {
404        let (a0, va0) = rows_a[r];
405        let (a1, va1) = rows_a[r + 1];
406        let (b0, vb0) = rows_b[r];
407        let (b1, vb1) = rows_b[r + 1];
408        let lerp =
409            |p: [f32; 2], q: [f32; 2], t: f32| [p[0] + t * (q[0] - p[0]), p[1] + (q[1] - p[1]) * t];
410        let (mut u0, mut u1) = (0.0f32, 0.25f32);
411        let (mut p0, mut p1, mut s0, mut s1) = (a0, a1, va0, va1);
412        for _ in 0..4 {
413            let q0 = lerp(a0, b0, u1);
414            let q1 = lerp(a1, b1, u1);
415            let t0 = va0 + (vb0 - va0) * u1;
416            let t1 = va1 + (vb1 - va1) * u1;
417            let ia = index([p0[0], p0[1], u0, s0], &mut vertices);
418            let ib = index([q0[0], q0[1], u1, t0], &mut vertices);
419            let ic = index([q1[0], q1[1], u1, t1], &mut vertices);
420            let id = index([p1[0], p1[1], u0, s1], &mut vertices);
421            indices.extend_from_slice(&[ia, ic, ib, ia, id, ic]);
422            u0 = u1;
423            u1 += 0.25;
424            (p0, p1, s0, s1) = (q0, q1, t0, t1);
425        }
426    }
427    (vertices, indices)
428}
429
430// MapComponent +0x1a0 (vertices), +0x1b0 (indices), +0x1c0 / +0x1d0 (the ranges).
431/// The map's water mesh: the strips of the rivers, then the surfaces of the lakes.
432///
433/// A vertex is 16 bytes: `i16 x, y` (1/48 cell), `i16 z` (1/256), `0`, `i16 u, v`, the normal
434/// bytes `0x7f, 0x7f`, an alpha byte, and `0`.
435///
436/// Original: `FUN_1400b0730`.
437#[derive(Debug, Clone, Default, PartialEq)]
438pub struct WaterMesh {
439    /// The vertices.
440    pub vertices: Vec<[u8; 16]>,
441    /// The indices, each relative to the first vertex of its range.
442    pub indices: Vec<u16>,
443    /// The range of each river: first index, first vertex, vertex count, index count.
444    pub rivers: Vec<[i32; 4]>,
445    /// The range of each lake, in the same form as [`rivers`](Self::rivers).
446    pub lakes: Vec<[i32; 4]>,
447}
448
449fn water_vertex(x: i16, y: i16, z: i16, u: i16, v: i16, alpha: u8) -> [u8; 16] {
450    let mut out = [0u8; 16];
451    for (k, s) in [x, y, z, 0, u, v].into_iter().enumerate() {
452        out[2 * k..2 * k + 2].copy_from_slice(&s.to_le_bytes());
453    }
454    out[12] = 0x7f;
455    out[13] = 0x7f;
456    out[14] = alpha;
457    out
458}
459
460// Reads MapComponent +0xc0, +0xd0, +0xd8 and the generator's +0x18 (paths), +0x48 (lakes).
461/// The water mesh of a generated map. `paths` and `lakes` are the generator's, `heights` is the
462/// map's `(size + 1)²` height grid, and `size` is its size in cells.
463///
464/// Original: `FUN_1400b0730`.
465pub fn water_mesh(
466    paths: &[Vec<PathPoint>],
467    lakes: &[[f32; 3]],
468    heights: &[i16],
469    size: usize,
470) -> WaterMesh {
471    let mut mesh = WaterMesh::default();
472    // Each river a little lower than the one before (`−192 − i`, 1/256).
473    for (i, path) in paths.iter().enumerate() {
474        let (vertices, indices) = river_strip(path);
475        mesh.rivers.push([
476            mesh.indices.len() as i32,
477            mesh.vertices.len() as i32,
478            vertices.len() as i32,
479            indices.len() as i32,
480        ]);
481        let z = (-192.0 - i as f32) as i32 as i16;
482        mesh.vertices.extend(vertices.iter().map(|v| {
483            water_vertex(
484                (v[0] * 48.0) as i32 as i16,
485                (v[1] * 48.0) as i32 as i16,
486                z,
487                (v[2] * 1024.0) as i32 as i16,
488                (v[3] * 1024.0) as i32 as i16,
489                0xff,
490            )
491        }));
492        mesh.indices.extend_from_slice(&indices);
493    }
494    let width = size + 1;
495    let half = (size >> 1) as i32;
496    let height = |gx: i32, gy: i32| -> f32 {
497        let (x, y) = (gx.clamp(0, width as i32 - 1), gy.clamp(0, width as i32 - 1));
498        heights[y as usize * width + x as usize] as i32 as f32 * 0.00390625
499    };
500    for (i, lake) in lakes.iter().enumerate() {
501        let [cx, cy, r] = *lake;
502        let outer = r + 2.0;
503        let first_index = mesh.indices.len();
504        let first_vertex = mesh.vertices.len();
505        let (x0, y0) = ((cx - outer) as i32, (cy - outer) as i32);
506        let (x1, y1) = ((cx + outer) as i32, (cy + outer) as i32);
507        let fade = outer - r;
508        let z = (i as f32 - 191.0) as i32 as i16;
509        let mut local: Vec<[u8; 16]> = Vec::new();
510        let mut seen: HashMap<[u8; 16], u16> = HashMap::new();
511        let mut indices = Vec::new();
512        for y in y0..y1 {
513            for x in x0..x1 {
514                let corners = [
515                    (x, y, height(half + x, half + y)),
516                    (x + 1, y, height(half + 1 + x, half + y)),
517                    (x + 1, y + 1, height(half + 1 + x, half + 1 + y)),
518                    (x, y + 1, height(half + x, half + 1 + y)),
519                ];
520                let mut above = true;
521                let mut clear = true;
522                let mut alpha = [0.0f32; 4];
523                for (k, &(px, py, pz)) in corners.iter().enumerate() {
524                    if pz <= -0.75 {
525                        above = false;
526                    }
527                    let d = [px as f32 - cx, py as f32 - cy, pz - 0.0];
528                    // `Vector3::Dot`: `(y² + x²) + z²`.
529                    let dist = (d[1] * d[1] + d[0] * d[0] + d[2] * d[2]).sqrt();
530                    let a = 1.0 - (dist - r) / fade;
531                    alpha[k] = if a < 0.0 {
532                        0.0
533                    } else if 1.0 < a {
534                        clear = false;
535                        1.0
536                    } else {
537                        if 0.0 < a {
538                            clear = false;
539                        }
540                        a
541                    };
542                }
543                if clear || above {
544                    continue;
545                }
546                let mut quad = [0u16; 4];
547                for (k, &(px, py, _)) in corners.iter().enumerate() {
548                    let (vx, vy) = (
549                        (px as f32 * 48.0) as i32 as i16,
550                        (py as f32 * 48.0) as i32 as i16,
551                    );
552                    let v = water_vertex(vx, vy, z, vx, vy, (alpha[k] * 255.0) as i64 as u8);
553                    quad[k] = *seen.entry(v).or_insert_with(|| {
554                        local.push(v);
555                        (local.len() - 1) as u16
556                    });
557                }
558                indices.extend_from_slice(&[quad[0], quad[1], quad[2], quad[0], quad[2], quad[3]]);
559            }
560        }
561        mesh.lakes.push([
562            first_index as i32,
563            first_vertex as i32,
564            local.len() as i32,
565            indices.len() as i32,
566        ]);
567        mesh.vertices.extend(local);
568        mesh.indices.extend(indices);
569    }
570    mesh
571}