1use bytemuck::{Pod, Zeroable};
6use std::collections::HashMap;
7
8const A: f32 = std::f32::consts::FRAC_1_SQRT_2;
16
17const B: f32 = SQRT_3 * A;
22
23const S45: f32 = std::f32::consts::FRAC_1_SQRT_2;
27const 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#[derive(Copy, Clone, Debug)]
53pub struct TerrainVertex {
54 pub position: glam::Vec3,
55 pub colour: [u8; 4],
56}
57
58fn 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
95pub 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
102fn 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#[derive(Clone)]
113pub struct HexTerrainMesh {
114 pub vertices: HashMap<(isize, isize), TerrainVertex>,
115 half_size: isize,
116}
117
118impl HexTerrainMesh {
119 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 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
199pub 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 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}