use bytemuck::{Pod, Zeroable};
use std::collections::HashMap;
const A: f32 = std::f32::consts::FRAC_1_SQRT_2;
const B: f32 = SQRT_3 * A;
const S45: f32 = std::f32::consts::FRAC_1_SQRT_2;
const C45: f32 = S45;
const SQRT_3: f32 = 1.7320508;
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)]
pub struct TerrainVertexAttributes {
position: [f32; 3],
normal: [f32; 3],
colour: [u8; 4],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Pod, Zeroable)]
pub struct WaterVertexAttributes {
position: [i16; 2],
offsets: [i8; 4],
}
#[derive(Copy, Clone, Debug)]
pub struct TerrainVertex {
pub position: glam::Vec3,
pub colour: [u8; 4],
}
fn surrounding_hexagonal_points(x: isize, y: isize) -> [(isize, isize); 6] {
[
(x, y - 1),
(x + 1, y - 1),
(x + 1, y),
(x, y + 1),
(x - 1, y + 1),
(x - 1, y),
]
}
fn surrounding_point_values_iter<T>(
hashmap: &HashMap<(isize, isize), T>,
x: isize,
y: isize,
for_each: impl FnMut((&T, &T)),
) {
let points = surrounding_hexagonal_points(x, y);
let points = [
points[0], points[1], points[2], points[3], points[4], points[5], points[0],
];
points
.windows(2)
.map(|x| (hashmap.get(&x[0]), hashmap.get(&x[1])))
.flat_map(|(a, b)| a.and_then(|x| b.map(|y| (x, y))))
.for_each(for_each);
}
pub fn calculate_normal(a: glam::Vec3, b: glam::Vec3, c: glam::Vec3) -> glam::Vec3 {
(b - a).normalize().cross((c - a).normalize()).normalize()
}
fn q_given_r(radius: f32) -> usize {
((((((4.0 * radius) / SQRT_3) + 1.0).floor() / 2.0).floor() * 2.0) + 1.0) as usize
}
#[derive(Clone)]
pub struct HexTerrainMesh {
pub vertices: HashMap<(isize, isize), TerrainVertex>,
half_size: isize,
}
impl HexTerrainMesh {
pub fn generate(radius: f32, mut gen_vertex: impl FnMut([f32; 2]) -> TerrainVertex) -> Self {
let width = q_given_r(radius);
let half_width = (width / 2) as isize;
let mut map = HashMap::new();
let mut max = f32::NEG_INFINITY;
for i in -half_width..=half_width {
let x_o = i as f32;
for j in -half_width..=half_width {
let y_o = j as f32;
let x = A * (x_o * C45 - y_o * S45);
let z = B * (x_o * S45 + y_o * C45);
if x.hypot(z) < radius {
let vertex = gen_vertex([x, z]);
if vertex.position.y > max {
max = vertex.position.y;
}
map.insert((i, j), vertex);
}
}
}
Self {
vertices: map,
half_size: width as isize / 2,
}
}
pub fn make_buffer_data(&self) -> Vec<TerrainVertexAttributes> {
let mut vertices = Vec::new();
fn middle(p1: &TerrainVertex, p2: &TerrainVertex, p: &TerrainVertex) -> glam::Vec3 {
(p1.position + p2.position + p.position) / 3.0
}
fn half(p1: &TerrainVertex, p2: &TerrainVertex) -> glam::Vec3 {
(p1.position + p2.position) / 2.0
}
let mut push_triangle = |p1: &TerrainVertex,
p2: &TerrainVertex,
p: &TerrainVertex,
c: [u8; 4]| {
let m = middle(p1, p2, p);
let ap = half(p1, p);
let bp = half(p2, p);
let p = p.position;
let n1 = calculate_normal(ap, m, p);
let n2 = calculate_normal(m, bp, p);
vertices.extend(
[ap, m, p, m, bp, p]
.iter()
.zip(
std::iter::repeat::<[f32; 3]>(n1.into())
.chain(std::iter::repeat::<[f32; 3]>(n2.into())),
)
.zip(std::iter::repeat(c))
.map(|((pos, normal), colour)| TerrainVertexAttributes {
position: *pos.as_ref(),
normal,
colour,
}),
);
};
for i in -self.half_size..=self.half_size {
for j in -self.half_size..=self.half_size {
if let Some(p) = self.vertices.get(&(i, j)) {
surrounding_point_values_iter(&self.vertices, i, j, |(a, b)| {
push_triangle(a, b, p, p.colour)
});
}
}
}
vertices
}
}
pub struct HexWaterMesh {
pub vertices: HashMap<(isize, isize), [i16; 2]>,
half_size: isize,
}
impl HexWaterMesh {
pub fn generate(radius: f32) -> Self {
let width = q_given_r(radius);
let half_width = (width / 2) as isize;
let mut map = HashMap::new();
for i in -half_width..=half_width {
let x_o = i as f32;
for j in -half_width..=half_width {
let y_o = j as f32;
let x = A * (x_o * C45 - y_o * S45);
let z = B * (x_o * S45 + y_o * C45);
if x.hypot(z) < radius {
let x = (x * 2.0).round() as i16;
let z = ((z / B) * std::f32::consts::SQRT_2).round() as i16;
map.insert((i, j), [x, z]);
}
}
}
Self {
vertices: map,
half_size: half_width,
}
}
pub fn generate_points(&self) -> Vec<WaterVertexAttributes> {
let mut vertices = Vec::new();
fn calculate_differences(a: [i16; 2], b: [i16; 2], c: [i16; 2]) -> [i8; 4] {
[
(b[0] - a[0]) as i8,
(b[1] - a[1]) as i8,
(c[0] - a[0]) as i8,
(c[1] - a[1]) as i8,
]
}
let mut push_triangle = |a: [i16; 2], b: [i16; 2], c: [i16; 2]| {
let bc = calculate_differences(a, b, c);
let ca = calculate_differences(b, c, a);
let ab = calculate_differences(c, a, b);
vertices.extend(
[a, b, c]
.iter()
.zip([bc, ca, ab].iter())
.map(|(&position, &offsets)| WaterVertexAttributes { position, offsets }),
);
};
for i in -self.half_size..=self.half_size {
for j in -self.half_size..=self.half_size {
if (i - j) % 3 == 0 {
if let Some(&p) = self.vertices.get(&(i, j)) {
surrounding_point_values_iter(&self.vertices, i, j, |(a, b)| {
push_triangle(*a, *b, p)
});
}
}
}
}
vertices
}
}