use glam::{Quat, Vec3};
use super::{BASE_FACING, MeshInfo, face::Quad};
use crate::{
EdgeDirection, FaceOptions, Hex, HexLayout, InsetOptions, PlaneMeshBuilder, UVOptions,
};
#[derive(Debug, Clone)]
pub struct ColumnMeshBuilder<'l> {
pub layout: &'l HexLayout,
pub height: f32,
pub pos: Hex,
pub offset: Option<Vec3>,
pub scale: Option<Vec3>,
pub rotation: Option<Quat>,
pub subdivisions: Option<usize>,
pub top_face: Option<PlaneMeshBuilder<'l>>,
pub bottom_face: Option<PlaneMeshBuilder<'l>>,
pub sides_options: [Option<FaceOptions>; 6],
pub center_aligned: bool,
}
impl<'l> ColumnMeshBuilder<'l> {
#[must_use]
pub const fn new(layout: &'l HexLayout, height: f32) -> Self {
Self {
layout,
height,
pos: Hex::ZERO,
rotation: None,
subdivisions: None,
offset: None,
scale: None,
top_face: Some(PlaneMeshBuilder::new(layout)),
bottom_face: Some(PlaneMeshBuilder::new(layout)),
sides_options: [Some(FaceOptions::new()); 6],
center_aligned: false,
}
}
#[must_use]
#[inline]
pub const fn at(mut self, pos: Hex) -> Self {
self.pos = pos;
self
}
#[must_use]
#[inline]
pub fn facing(mut self, facing: Vec3) -> Self {
self.rotation = Some(Quat::from_rotation_arc(BASE_FACING, facing));
self
}
#[must_use]
pub const fn with_rotation(mut self, rotation: Quat) -> Self {
self.rotation = Some(rotation);
self
}
#[must_use]
#[inline]
pub const fn with_offset(mut self, offset: Vec3) -> Self {
self.offset = Some(offset);
self
}
#[must_use]
pub const fn with_scale(mut self, scale: Vec3) -> Self {
self.scale = Some(scale);
self
}
#[must_use]
#[inline]
pub const fn with_subdivisions(mut self, subdivisions: usize) -> Self {
self.subdivisions = Some(subdivisions);
self
}
#[must_use]
#[inline]
pub const fn without_bottom_face(mut self) -> Self {
self.bottom_face = None;
self
}
#[must_use]
#[inline]
pub const fn without_top_face(mut self) -> Self {
self.top_face = None;
self
}
#[must_use]
#[inline]
pub const fn with_caps_uv_options(mut self, uv_options: UVOptions) -> Self {
if let Some(builder) = self.top_face {
self.top_face = Some(builder.with_uv_options(uv_options));
}
if let Some(builder) = self.bottom_face {
self.bottom_face = Some(builder.with_uv_options(uv_options));
}
self
}
#[must_use]
#[inline]
pub const fn with_caps_inset_options(mut self, opts: InsetOptions) -> Self {
if let Some(builder) = self.top_face {
self.top_face = Some(builder.with_inset_options(opts));
}
if let Some(builder) = self.bottom_face {
self.bottom_face = Some(builder.with_inset_options(opts));
}
self
}
#[must_use]
#[inline]
pub const fn with_sides_options(mut self, options: FaceOptions) -> Self {
self.sides_options = [Some(options); 6];
self
}
#[must_use]
#[inline]
#[expect(clippy::cast_possible_truncation)]
pub fn with_sides_options_fn(
mut self,
options: impl Fn(EdgeDirection) -> Option<FaceOptions>,
) -> Self {
self.sides_options = std::array::from_fn(|i| options(EdgeDirection(i as u8)));
self
}
#[must_use]
#[inline]
pub fn with_multi_sides_options(mut self, options: [FaceOptions; 6]) -> Self {
self.sides_options = options.map(Some);
self
}
#[must_use]
#[inline]
pub const fn with_multi_custom_sides_options(
mut self,
options: [Option<FaceOptions>; 6],
) -> Self {
self.sides_options = options;
self
}
#[must_use]
#[inline]
pub const fn center_aligned(mut self) -> Self {
self.center_aligned = true;
self
}
#[must_use]
#[expect(clippy::cast_precision_loss)]
pub fn build(self) -> MeshInfo {
let pos = if self.center_aligned {
self.layout.hex_to_center_aligned_world_pos(self.pos)
} else {
self.layout.hex_to_world_pos(self.pos)
};
let mut offset = Vec3::new(pos.x, 0.0, pos.y);
let mut mesh = MeshInfo::default();
let subidivisions = self.subdivisions.unwrap_or(0).max(1);
let delta = self.height / subidivisions as f32;
let corners = self.layout.center_aligned_edge_corners();
(0..6).for_each(|side| {
let [left, right] = corners[side];
let Some(options) = self.sides_options[side] else {
return;
};
for div in 0..subidivisions {
let bottom_height = delta * div as f32;
let mut quad = Quad::new([left, right], bottom_height, bottom_height + delta);
options.uv.alter_uvs(&mut quad.uvs);
let quad = if let Some(opts) = options.insetting {
quad.inset(opts.mode, opts.scale, opts.keep_inner_face)
} else {
quad.into()
};
mesh.merge_with(quad);
}
});
if let Some(builder) = self.top_face {
mesh.merge_with(
builder
.center_aligned()
.with_offset(Vec3::Y * self.height)
.build(),
);
}
if let Some(builder) = self.bottom_face {
let rotation = Quat::from_rotation_arc(BASE_FACING, -BASE_FACING);
let bottom_face = builder.center_aligned().build().rotated(rotation);
mesh.merge_with(bottom_face);
}
if let Some(scale) = self.scale {
mesh = mesh.with_scale(scale);
}
if let Some(rotation) = self.rotation {
mesh = mesh.rotated(rotation);
}
if let Some(custom_offset) = self.offset {
offset += custom_offset;
}
mesh = mesh.with_offset(offset);
mesh
}
}