use super::{FaceOptions, InsetOptions, MeshInfo, face::Quad};
use crate::{EdgeDirection, Hex, HexLayout, PlaneMeshBuilder, UVOptions, storage::HexStore};
use glam::{Quat, Vec3};
use std::{ops::RangeInclusive, sync::Arc};
type MapFringeHeightFn = dyn Fn(Hex) -> f32;
type CapOptionsFn = dyn Fn(Hex) -> Option<FaceOptions>;
type SideOptionsFn = dyn Fn(Hex, Hex) -> Option<FaceOptions>;
pub struct HeightMapMeshBuilder<'l, 'm, HeightMap> {
pub layout: &'l HexLayout,
pub height_range: Option<RangeInclusive<f32>>,
pub map: &'m HeightMap,
pub top_face_options: Option<FaceOptions>,
pub side_options: Option<FaceOptions>,
pub offset: Option<Vec3>,
pub scale: Option<Vec3>,
pub rotation: Option<Quat>,
pub center_aligned: bool,
pub fringe_heights: Option<Arc<MapFringeHeightFn>>,
pub custom_caps_options: Option<Arc<CapOptionsFn>>,
pub custom_sides_options: Option<Arc<SideOptionsFn>>,
}
impl<'l, 'm, HeightMap: HexStore<f32>> HeightMapMeshBuilder<'l, 'm, HeightMap> {
#[must_use]
pub const fn new(layout: &'l HexLayout, map: &'m HeightMap) -> Self {
Self {
layout,
map,
height_range: None,
top_face_options: Some(FaceOptions::new()),
side_options: Some(FaceOptions::new()),
offset: None,
scale: None,
rotation: None,
center_aligned: false,
fringe_heights: None,
custom_caps_options: None,
custom_sides_options: None,
}
}
#[must_use]
pub const fn with_height_range(mut self, range: RangeInclusive<f32>) -> Self {
self.height_range = Some(range);
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 without_top_face(mut self) -> Self {
self.top_face_options = None;
self
}
#[must_use]
#[inline]
pub const fn with_cap_options(mut self, options: FaceOptions) -> Self {
self.top_face_options = Some(options);
self
}
#[must_use]
#[inline]
pub const fn with_cap_uv_options(mut self, uv_options: UVOptions) -> Self {
if let Some(opts) = &mut self.top_face_options {
opts.uv = uv_options;
}
self
}
#[must_use]
#[inline]
pub const fn with_cap_inset_options(mut self, inset: InsetOptions) -> Self {
if let Some(opts) = &mut self.top_face_options {
opts.insetting = Some(inset);
}
self
}
#[must_use]
#[inline]
pub fn with_custom_cap_options(
mut self,
func: impl Fn(Hex) -> Option<FaceOptions> + 'static,
) -> Self {
self.custom_caps_options = Some(Arc::new(func));
self
}
#[must_use]
#[inline]
pub const fn without_sides(mut self) -> Self {
self.side_options = None;
self
}
#[must_use]
#[inline]
pub const fn with_side_options(mut self, options: FaceOptions) -> Self {
self.side_options = Some(options);
self
}
#[must_use]
#[inline]
pub fn with_custom_sides_options(
mut self,
func: impl Fn(Hex, Hex) -> Option<FaceOptions> + 'static,
) -> Self {
self.custom_sides_options = Some(Arc::new(func));
self
}
#[must_use]
#[inline]
pub fn with_fringe_heights(mut self, func: impl Fn(Hex) -> f32 + 'static) -> Self {
self.fringe_heights = Some(Arc::new(func));
self
}
#[must_use]
#[inline]
pub fn with_default_height(mut self, default_height: f32) -> Self {
self.fringe_heights = Some(Arc::new(move |_| default_height));
self
}
#[must_use]
#[inline]
pub const fn center_aligned(mut self) -> Self {
self.center_aligned = true;
self
}
pub fn build(self) -> MeshInfo {
let mut mesh = MeshInfo::default();
let [min, max] = match self.height_range {
Some(r) => [*r.start(), *r.end()],
None => [
self.map.values().copied().reduce(f32::min).unwrap_or(0.0),
self.map.values().copied().reduce(f32::max).unwrap_or(0.0),
],
};
for (hex, &height) in self.map.iter() {
if let Some(opts) = self.top_face_options {
let opts = self
.custom_caps_options
.as_ref()
.and_then(|f| f(hex))
.unwrap_or(opts);
let mut plane = PlaneMeshBuilder::new(self.layout)
.at(hex)
.center_aligned()
.with_offset(Vec3::Y * height)
.with_uv_options(opts.uv);
if let Some(inset) = opts.insetting {
plane = plane.with_inset_options(inset);
}
mesh.merge_with(plane.build());
}
if let Some(side_opts) = self.side_options {
let corners = self.layout.hex_edge_corners(hex);
for dir in EdgeDirection::ALL_DIRECTIONS {
let neighbor = hex + dir;
let opt_height = self.map.get(hex + dir).copied();
let side_opts = self
.custom_sides_options
.as_ref()
.and_then(|f| f(hex, neighbor))
.unwrap_or(side_opts);
let [a, b] = corners[dir.index() as usize];
let Some(neighbor_height) =
opt_height.or_else(|| self.fringe_heights.as_ref().map(|f| f(neighbor)))
else {
continue;
};
if neighbor_height >= height {
continue;
}
let quad = Quad::new_bounded([a, b], neighbor_height, height, [min, max]);
mesh.merge_with(quad.apply_options(&side_opts));
}
}
}
if let Some(scale) = self.scale {
mesh = mesh.with_scale(scale);
}
if let Some(rotation) = self.rotation {
mesh = mesh.rotated(rotation);
}
if let Some(offset) = self.offset {
mesh = mesh.with_offset(offset);
}
if !self.center_aligned {
mesh = mesh.with_offset(Vec3::new(self.layout.origin.x, 0.0, self.layout.origin.y));
}
mesh
}
}