wgpu_examples/water/
point_gen.rs

1//!
2//! This module covers generating points in a hexagonal fashion.
3//!
4
5use bytemuck::{Pod, Zeroable};
6use std::collections::HashMap;
7
8// The following constants are used in calculations.
9// A and B are multiplication factors for x and y.
10
11///
12/// X multiplication factor.
13/// 1.0 / sqrt(2)
14///
15const A: f32 = std::f32::consts::FRAC_1_SQRT_2;
16
17///
18/// Y multiplication factor.
19/// sqrt(3) / sqrt(2) == sqrt(1.5)
20///
21const B: f32 = SQRT_3 * A;
22
23///
24/// `sin(45deg)` is used to rotate the points.
25///
26const S45: f32 = std::f32::consts::FRAC_1_SQRT_2;
27///
28/// `cos(45deg)` is used to rotate the points.
29///
30const C45: f32 = S45;
31
32const SQRT_3: f32 = 1.7320508;
33
34#[repr(C)]
35#[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)]
36pub struct TerrainVertexAttributes {
37    position: [f32; 3],
38    normal: [f32; 3],
39    colour: [u8; 4],
40}
41
42#[repr(C)]
43#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
44pub struct WaterVertexAttributes {
45    position: [i16; 2],
46    offsets: [i8; 4],
47}
48
49///
50/// Represents the center of a single hexagon.
51///
52#[derive(Copy, Clone, Debug)]
53pub struct TerrainVertex {
54    pub position: glam::Vec3,
55    pub colour: [u8; 4],
56}
57
58///
59/// Gets the surrounding hexagonal points from a point.
60///
61/// +---0---1
62/// | / |   |
63/// 5---p---2
64/// |   | / |
65/// 4---3---+
66///
67fn surrounding_hexagonal_points(x: isize, y: isize) -> [(isize, isize); 6] {
68    [
69        (x, y - 1),
70        (x + 1, y - 1),
71        (x + 1, y),
72        (x, y + 1),
73        (x - 1, y + 1),
74        (x - 1, y),
75    ]
76}
77
78fn surrounding_point_values_iter<T>(
79    hashmap: &HashMap<(isize, isize), T>,
80    x: isize,
81    y: isize,
82    for_each: impl FnMut((&T, &T)),
83) {
84    let points = surrounding_hexagonal_points(x, y);
85    let points = [
86        points[0], points[1], points[2], points[3], points[4], points[5], points[0],
87    ];
88    points
89        .windows(2)
90        .map(|x| (hashmap.get(&x[0]), hashmap.get(&x[1])))
91        .flat_map(|(a, b)| a.and_then(|x| b.map(|y| (x, y))))
92        .for_each(for_each);
93}
94
95///
96/// Used in calculating terrain normals.
97///
98pub fn calculate_normal(a: glam::Vec3, b: glam::Vec3, c: glam::Vec3) -> glam::Vec3 {
99    (b - a).normalize().cross((c - a).normalize()).normalize()
100}
101
102///
103/// Given the radius, how large of a square do we need to make a unit hexagon grid?
104///
105fn q_given_r(radius: f32) -> usize {
106    ((((((4.0 * radius) / SQRT_3) + 1.0).floor() / 2.0).floor() * 2.0) + 1.0) as usize
107}
108
109///
110/// Represents terrain, however it contains the vertices only once.
111///
112#[derive(Clone)]
113pub struct HexTerrainMesh {
114    pub vertices: HashMap<(isize, isize), TerrainVertex>,
115    half_size: isize,
116}
117
118impl HexTerrainMesh {
119    ///
120    /// Generates the vertices (or the centers of the hexagons). The colour and height is determined by
121    /// a function passed in by the user.
122    ///
123    pub fn generate(radius: f32, mut gen_vertex: impl FnMut([f32; 2]) -> TerrainVertex) -> Self {
124        let width = q_given_r(radius);
125        let half_width = (width / 2) as isize;
126        let mut map = HashMap::new();
127        let mut max = f32::NEG_INFINITY;
128        for i in -half_width..=half_width {
129            let x_o = i as f32;
130            for j in -half_width..=half_width {
131                let y_o = j as f32;
132                let x = A * (x_o * C45 - y_o * S45);
133                let z = B * (x_o * S45 + y_o * C45);
134                if x.hypot(z) < radius {
135                    let vertex = gen_vertex([x, z]);
136                    if vertex.position.y > max {
137                        max = vertex.position.y;
138                    }
139                    map.insert((i, j), vertex);
140                }
141            }
142        }
143        Self {
144            vertices: map,
145            half_size: width as isize / 2,
146        }
147    }
148
149    ///
150    /// Creates the points required to render the mesh.
151    ///
152    pub fn make_buffer_data(&self) -> Vec<TerrainVertexAttributes> {
153        let mut vertices = Vec::new();
154        fn middle(p1: &TerrainVertex, p2: &TerrainVertex, p: &TerrainVertex) -> glam::Vec3 {
155            (p1.position + p2.position + p.position) / 3.0
156        }
157        fn half(p1: &TerrainVertex, p2: &TerrainVertex) -> glam::Vec3 {
158            (p1.position + p2.position) / 2.0
159        }
160        let mut push_triangle = |p1: &TerrainVertex,
161                                 p2: &TerrainVertex,
162                                 p: &TerrainVertex,
163                                 c: [u8; 4]| {
164            let m = middle(p1, p2, p);
165            let ap = half(p1, p);
166            let bp = half(p2, p);
167            let p = p.position;
168            let n1 = calculate_normal(ap, m, p);
169            let n2 = calculate_normal(m, bp, p);
170
171            vertices.extend(
172                [ap, m, p, m, bp, p]
173                    .iter()
174                    .zip(
175                        std::iter::repeat::<[f32; 3]>(n1.into())
176                            .chain(std::iter::repeat::<[f32; 3]>(n2.into())),
177                    )
178                    .zip(std::iter::repeat(c))
179                    .map(|((pos, normal), colour)| TerrainVertexAttributes {
180                        position: *pos.as_ref(),
181                        normal,
182                        colour,
183                    }),
184            );
185        };
186        for i in -self.half_size..=self.half_size {
187            for j in -self.half_size..=self.half_size {
188                if let Some(p) = self.vertices.get(&(i, j)) {
189                    surrounding_point_values_iter(&self.vertices, i, j, |(a, b)| {
190                        push_triangle(a, b, p, p.colour)
191                    });
192                }
193            }
194        }
195        vertices
196    }
197}
198
199///
200/// Water mesh which contains vertex data for the water mesh.
201///
202/// It stores the values multiplied and rounded to the
203/// nearest whole number to be more efficient with space when
204/// sending large meshes to the GPU.
205///
206pub struct HexWaterMesh {
207    pub vertices: HashMap<(isize, isize), [i16; 2]>,
208    half_size: isize,
209}
210
211impl HexWaterMesh {
212    pub fn generate(radius: f32) -> Self {
213        let width = q_given_r(radius);
214        let half_width = (width / 2) as isize;
215        let mut map = HashMap::new();
216
217        for i in -half_width..=half_width {
218            let x_o = i as f32;
219            for j in -half_width..=half_width {
220                let y_o = j as f32;
221                let x = A * (x_o * C45 - y_o * S45);
222                let z = B * (x_o * S45 + y_o * C45);
223                if x.hypot(z) < radius {
224                    let x = (x * 2.0).round() as i16;
225                    let z = ((z / B) * std::f32::consts::SQRT_2).round() as i16;
226                    map.insert((i, j), [x, z]);
227                }
228            }
229        }
230        Self {
231            vertices: map,
232            half_size: half_width,
233        }
234    }
235    ///
236    /// Generates the points required to render the mesh.
237    ///
238    pub fn generate_points(&self) -> Vec<WaterVertexAttributes> {
239        let mut vertices = Vec::new();
240
241        fn calculate_differences(a: [i16; 2], b: [i16; 2], c: [i16; 2]) -> [i8; 4] {
242            [
243                (b[0] - a[0]) as i8,
244                (b[1] - a[1]) as i8,
245                (c[0] - a[0]) as i8,
246                (c[1] - a[1]) as i8,
247            ]
248        }
249
250        let mut push_triangle = |a: [i16; 2], b: [i16; 2], c: [i16; 2]| {
251            let bc = calculate_differences(a, b, c);
252            let ca = calculate_differences(b, c, a);
253            let ab = calculate_differences(c, a, b);
254
255            vertices.extend(
256                [a, b, c]
257                    .iter()
258                    .zip([bc, ca, ab].iter())
259                    .map(|(&position, &offsets)| WaterVertexAttributes { position, offsets }),
260            );
261        };
262
263        for i in -self.half_size..=self.half_size {
264            for j in -self.half_size..=self.half_size {
265                if (i - j) % 3 == 0 {
266                    if let Some(&p) = self.vertices.get(&(i, j)) {
267                        surrounding_point_values_iter(&self.vertices, i, j, |(a, b)| {
268                            push_triangle(*a, *b, p)
269                        });
270                    }
271                }
272            }
273        }
274
275        vertices
276    }
277}