use crate::{BASE_FACING, HexLayout, InsetScaleMode, MeshInfo, UVOptions};
use glam::{Vec2, Vec3};
use super::FaceOptions;
type VertexIdx = u16;
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "facet", derive(facet::Facet))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[repr(transparent)]
pub struct Tri(pub [VertexIdx; 3]);
#[derive(Debug, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct Face<const VERTS: usize, const TRIS: usize> {
pub positions: [Vec3; VERTS],
pub normals: [Vec3; VERTS],
pub uvs: [Vec2; VERTS],
pub triangles: [Tri; TRIS],
}
pub type Quad = Face<4, 2>;
pub type Hexagon = Face<6, 4>;
impl Tri {
pub const fn flip(&mut self) {
let [a, b, c] = self.0;
self.0 = [c, b, a];
}
}
impl Quad {
#[must_use]
pub fn new([left, right]: [Vec2; 2], bottom_height: f32, top_height: f32) -> Self {
let normal = (left + right).normalize();
let normal = Vec3::new(normal.x, 0.0, normal.y);
let positions = [
Vec3::new(right.x, bottom_height, right.y),
Vec3::new(right.x, top_height, right.y),
Vec3::new(left.x, top_height, left.y),
Vec3::new(left.x, bottom_height, left.y),
];
Self {
positions,
normals: [normal; 4],
uvs: [Vec2::X, Vec2::ONE, Vec2::Y, Vec2::ZERO],
triangles: [
Tri([2, 1, 0]), Tri([0, 3, 2]), ],
}
}
#[must_use]
pub(crate) fn new_bounded(
sides: [Vec2; 2],
bottom_height: f32,
top_height: f32,
[min_height, max_height]: [f32; 2],
) -> Self {
let delta = max_height - min_height;
let mut quad = Self::new(sides, bottom_height, top_height);
let bottom_v = (bottom_height - min_height) / delta;
let top_v = (top_height - min_height) / delta;
quad.uvs[0][1] = bottom_v;
quad.uvs[1][1] = top_v;
quad.uvs[2][1] = top_v;
quad.uvs[3][1] = bottom_v;
quad
}
}
impl Hexagon {
#[must_use]
pub fn center_aligned(layout: &HexLayout) -> Self {
let corners = layout.center_aligned_hex_corners();
let uvs = corners.map(UVOptions::wrap_uv);
let positions = corners.map(|p| Vec3::new(p.x, 0., p.y));
Self {
positions,
uvs,
normals: [BASE_FACING; 6],
triangles: [
Tri([0, 2, 1]), Tri([3, 5, 4]), Tri([0, 5, 3]), Tri([3, 2, 0]), ],
}
}
}
impl<const VERTS: usize, const TRIS: usize> Face<VERTS, TRIS> {
#[inline]
#[must_use]
#[expect(clippy::cast_precision_loss)]
pub fn centroid(&self) -> Vec3 {
self.positions.iter().sum::<Vec3>() / VERTS as f32
}
#[inline]
#[must_use]
#[expect(clippy::cast_precision_loss)]
pub fn uv_centroid(&self) -> Vec2 {
self.uvs.iter().sum::<Vec2>() / VERTS as f32
}
#[must_use]
pub fn apply_options(mut self, opts: &FaceOptions) -> MeshInfo {
opts.uv.alter_uvs(&mut self.uvs);
match opts.insetting {
None => self.into(),
Some(inset) => self.inset(inset.mode, inset.scale, inset.keep_inner_face),
}
}
#[expect(clippy::cast_possible_truncation)]
#[must_use]
pub fn inset(self, mode: InsetScaleMode, scale: f32, keep_inner_face: bool) -> MeshInfo {
let mut inset_face = self.clone();
match mode {
InsetScaleMode::Centroid => {
let centroid = inset_face.centroid();
inset_face.positions.iter_mut().for_each(|v| {
*v = *v + ((centroid - *v) * scale);
});
let uv_centroid = inset_face.uv_centroid();
inset_face.uvs.iter_mut().for_each(|uv| {
*uv = *uv + ((uv_centroid - *uv) * scale);
});
}
InsetScaleMode::SmallestEdge => {
let mut new_positions = inset_face.positions;
let mut new_uvs = inset_face.uvs;
for idx in 0..VERTS {
let [prev_idx, next_idx] = [(idx + VERTS - 1) % VERTS, (idx + 1) % VERTS];
let [pos, prev, next] =
[idx, prev_idx, next_idx].map(|i| inset_face.positions[i]);
let [dir_prev, dir_next] = [(prev - pos), (next - pos)];
let [prev_len, next_len] = [dir_prev.length(), dir_next.length()];
let dist = prev_len.min(next_len) * scale;
new_positions[idx] =
pos + dir_next.normalize() * dist + dir_prev.normalize() * dist;
let [disp_prev, disp_next] = [dist / prev_len, dist / next_len];
let [pos, prev, next] = [idx, prev_idx, next_idx].map(|i| inset_face.uvs[i]);
let [dir_prev, dir_next] = [(prev - pos), (next - pos)];
new_uvs[idx] = pos + dir_next * disp_next + dir_prev * disp_prev;
}
inset_face.positions = new_positions;
inset_face.uvs = new_uvs;
}
}
let mut inset_face = MeshInfo::from(inset_face);
if !keep_inner_face {
inset_face.indices.clear();
}
let mut mesh = MeshInfo::from(self);
mesh.indices.clear();
let vertex_count = VERTS as u16;
let connection_indices = (0..vertex_count).flat_map(|v_idx| {
let next_v_idx = (v_idx + 1) % vertex_count;
let inset_v_idx = v_idx + vertex_count;
let next_inset_v_idx = next_v_idx + vertex_count;
let [mut a, mut b] = [
Tri([next_inset_v_idx, next_v_idx, v_idx]),
Tri([v_idx, inset_v_idx, next_inset_v_idx]),
];
if scale < 0.0 {
a.flip();
b.flip();
}
a.0.into_iter().chain(b.0)
});
mesh.indices.extend(connection_indices);
mesh.merge_with(inset_face);
mesh
}
}
impl<const VERTS: usize, const TRIS: usize> From<Face<VERTS, TRIS>> for MeshInfo {
fn from(face: Face<VERTS, TRIS>) -> Self {
Self {
vertices: face.positions.to_vec(),
normals: face.normals.to_vec(),
uvs: face.uvs.to_vec(),
indices: face.triangles.into_iter().flat_map(|t| t.0).collect(),
}
}
}