use crate::math::calc_triangle_surface_normal; use crate::mesh::Mesh; use crate::util::builder; use crate::vector::{Vec2, Vec3}; use crate::vertex::{Builder as VertexBuilder, Vertex}; builder! { /// Cube mesh creation specification. #[builder(name = CreationSpecBuilder, derives = (Debug, Default))] #[derive(Debug, Default)] #[non_exhaustive] pub struct CreationSpec { pub width: f32, pub height: f32, pub depth: f32, } } impl CreationSpec { /// Returns a new `CreationSpec` builder. #[must_use] pub fn builder() -> CreationSpecBuilder { CreationSpecBuilder::default() } } /// Describes a single side of a cube (obviously). #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum Side { /// +Z Front, /// -Z Back, /// -X Left, /// +X Right, /// +Y Top, /// -Y Bottom, } /// Describes what location on a side of a cube a face is. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum FaceLocation { /// 🮝 RightUp, /// 🮟 LeftDown, } /// Creates a cube mesh. /// /// By default, the texture coordinates are arranged so that the full texture is visible /// on every side. This can be changed inside of the `face_cb` function. pub fn create( creation_spec: CreationSpec, face_cb: impl FnMut(FaceVertices, Side, FaceLocation) -> FaceVertices, ) -> Mesh { let mut data = Data::default(); create_side(&SidePositions::new_top(&creation_spec), &mut data); create_side(&SidePositions::new_bottom(&creation_spec), &mut data); create_side(&SidePositions::new_left(&creation_spec), &mut data); create_side(&SidePositions::new_right(&creation_spec), &mut data); create_side(&SidePositions::new_back(&creation_spec), &mut data); create_side(&SidePositions::new_front(&creation_spec), &mut data); data.into_mesh(face_cb) } #[derive(Debug, Default)] struct Data { faces: Vec, vertex_data: VertexData, } #[derive(Debug, Default)] struct VertexData { vertex_positions: Vec>, vertex_normals: Vec>, } impl Data { fn into_mesh( self, mut face_cb: impl FnMut(FaceVertices, Side, FaceLocation) -> FaceVertices, ) -> Mesh { let mut vertices = Vec::::with_capacity(self.faces.len() * 3); let mut indices = Vec::::with_capacity(self.faces.len() * 3); let mut face_location = FaceLocation::RightUp; let Self { faces, vertex_data } = self; for face in faces { let side = face.side; let face_vertices = face_cb( FaceVertices::new(face, &vertex_data) .with_full_per_side_tex_coords(face_location), side, face_location, ); for vertex in face_vertices.vertices { if let Some((prev_vertex_index, _)) = vertices .iter() .enumerate() .find(|(_, prev_vertex)| *prev_vertex == &vertex) { indices .push(u32::try_from(prev_vertex_index).expect( "Vertex index does not fit into 32-bit unsigned int", )); continue; } vertices.push(vertex); let vertex_index = u32::try_from(vertices.len() - 1) .expect("Vertex index does not fit into 32-bit unsigned int"); indices.push(vertex_index); } match face_location { FaceLocation::RightUp => face_location = FaceLocation::LeftDown, FaceLocation::LeftDown => face_location = FaceLocation::RightUp, } } Mesh::new(vertices, Some(indices)) } } /// The vertices of a single face of a cube. #[derive(Debug, Default, Clone)] pub struct FaceVertices { /// The three vertices of a face in counter-clockwise order. /// /// Order when [`FaceLocation::RightUp`]: /// ```text /// ₂ ₁ /// 🮝 /// ³ /// ``` /// /// Order when [`FaceLocation::LeftDown`]: /// ```text /// ₁ /// 🮟 /// ² ³ /// ``` pub vertices: [Vertex; 3], } impl FaceVertices { fn new(face: Face, vertex_data: &VertexData) -> Self { Self { vertices: face.vertices.map(|face_vertex| { let vertex_pos = vertex_data .vertex_positions .get(face_vertex.pos_index as usize) .expect("Vertex position index is out of bounds") .clone(); let vertex_normal = vertex_data .vertex_normals .get(face_vertex.normal_index as usize) .expect("Vertex normal index is out of bounds") .clone(); VertexBuilder::default() .pos(vertex_pos) .normal(vertex_normal) .build() }), } } fn with_full_per_side_tex_coords(mut self, face_location: FaceLocation) -> Self { match face_location { FaceLocation::RightUp => { self.vertices[0].texture_coords = Vec2 { x: 1.0, y: 1.0 }; self.vertices[1].texture_coords = Vec2 { x: 0.0, y: 1.0 }; self.vertices[2].texture_coords = Vec2 { x: 1.0, y: 0.0 }; } FaceLocation::LeftDown => { self.vertices[0].texture_coords = Vec2 { x: 0.0, y: 1.0 }; self.vertices[1].texture_coords = Vec2 { x: 0.0, y: 0.0 }; self.vertices[2].texture_coords = Vec2 { x: 1.0, y: 0.0 }; } }; self } } #[derive(Debug)] struct Face { vertices: [FaceVertex; 3], side: Side, } #[derive(Debug, PartialEq, Eq, Hash, Clone)] struct FaceVertex { pos_index: u32, normal_index: u32, } #[derive(Debug)] struct SidePositions { up_left: Vec3, up_right: Vec3, down_left: Vec3, down_right: Vec3, normal_calc_order: NormalCalcOrder, side: Side, } impl SidePositions { fn new_top(creation_spec: &CreationSpec) -> Self { let up_left = Vec3 { x: -(creation_spec.width / 2.0), y: creation_spec.height / 2.0, z: creation_spec.depth / 2.0, }; let down_right = Vec3 { x: creation_spec.width / 2.0, y: creation_spec.height / 2.0, z: -(creation_spec.depth / 2.0), }; Self { up_left, up_right: Vec3 { x: down_right.x, ..up_left.clone() }, down_left: Vec3 { x: up_left.x, ..down_right.clone() }, down_right, normal_calc_order: NormalCalcOrder::Clockwise, side: Side::Top, } } fn new_bottom(creation_spec: &CreationSpec) -> Self { let up_left = Vec3 { x: -(creation_spec.width / 2.0), y: -creation_spec.height / 2.0, z: creation_spec.depth / 2.0, }; let down_right = Vec3 { x: creation_spec.width / 2.0, y: -creation_spec.height / 2.0, z: -(creation_spec.depth / 2.0), }; Self { up_left, up_right: Vec3 { x: down_right.x, ..up_left.clone() }, down_left: Vec3 { x: up_left.x, ..down_right.clone() }, down_right, normal_calc_order: NormalCalcOrder::CounterClockwise, side: Side::Bottom, } } fn new_left(creation_spec: &CreationSpec) -> Self { let up_left = Vec3 { x: -(creation_spec.width / 2.0), y: creation_spec.height / 2.0, z: -(creation_spec.depth / 2.0), }; let down_right = Vec3 { x: -(creation_spec.width / 2.0), y: -(creation_spec.height / 2.0), z: creation_spec.depth / 2.0, }; Self { up_left, up_right: Vec3 { z: down_right.z, ..up_left.clone() }, down_left: Vec3 { z: up_left.z, ..down_right.clone() }, down_right, normal_calc_order: NormalCalcOrder::CounterClockwise, side: Side::Left, } } fn new_right(creation_spec: &CreationSpec) -> Self { let up_left = Vec3 { x: (creation_spec.width / 2.0), y: creation_spec.height / 2.0, z: -(creation_spec.depth / 2.0), }; let down_right = Vec3 { x: (creation_spec.width / 2.0), y: -(creation_spec.height / 2.0), z: creation_spec.depth / 2.0, }; Self { up_left, up_right: Vec3 { z: down_right.z, ..up_left.clone() }, down_left: Vec3 { z: up_left.z, ..down_right.clone() }, down_right, normal_calc_order: NormalCalcOrder::Clockwise, side: Side::Right, } } fn new_back(creation_spec: &CreationSpec) -> Self { let up_left = Vec3 { x: -(creation_spec.width / 2.0), y: creation_spec.height / 2.0, z: -creation_spec.depth / 2.0, }; let down_right = Vec3 { x: creation_spec.width / 2.0, y: -(creation_spec.height / 2.0), z: -creation_spec.depth / 2.0, }; Self { up_left, up_right: Vec3 { x: down_right.x, ..up_left.clone() }, down_left: Vec3 { x: up_left.x, ..down_right.clone() }, down_right, normal_calc_order: NormalCalcOrder::Clockwise, side: Side::Back, } } fn new_front(creation_spec: &CreationSpec) -> Self { let up_left = Vec3 { x: -(creation_spec.width / 2.0), y: creation_spec.height / 2.0, z: creation_spec.depth / 2.0, }; let down_right = Vec3 { x: creation_spec.width / 2.0, y: -(creation_spec.height / 2.0), z: creation_spec.depth / 2.0, }; Self { up_left, up_right: Vec3 { x: down_right.x, ..up_left.clone() }, down_left: Vec3 { x: up_left.x, ..down_right.clone() }, down_right, normal_calc_order: NormalCalcOrder::CounterClockwise, side: Side::Front, } } } #[derive(Debug)] enum NormalCalcOrder { Clockwise, CounterClockwise, } fn create_side(side_positions: &SidePositions, data: &mut Data) { let normal = match side_positions.normal_calc_order { NormalCalcOrder::Clockwise => calc_triangle_surface_normal( &side_positions.up_left, &side_positions.up_right, &side_positions.down_left, ), NormalCalcOrder::CounterClockwise => calc_triangle_surface_normal( &side_positions.up_left, &side_positions.down_left, &side_positions.up_right, ), }; data.vertex_data.vertex_normals.push(normal); let top_normal_index = data.vertex_data.vertex_normals.len() - 1; data.vertex_data .vertex_positions .push(side_positions.up_right); let up_right_pos_index = data.vertex_data.vertex_positions.len() - 1; data.vertex_data .vertex_positions .push(side_positions.up_left); let up_left_pos_index = data.vertex_data.vertex_positions.len() - 1; data.vertex_data .vertex_positions .push(side_positions.down_left); let down_left_pos_index = data.vertex_data.vertex_positions.len() - 1; data.vertex_data .vertex_positions .push(side_positions.down_right); let down_right_pos_index = data.vertex_data.vertex_positions.len() - 1; // 🮝 data.faces.push(Face { vertices: [ FaceVertex { pos_index: up_right_pos_index as u32, normal_index: top_normal_index as u32, }, FaceVertex { pos_index: up_left_pos_index as u32, normal_index: top_normal_index as u32, }, FaceVertex { pos_index: down_right_pos_index as u32, normal_index: top_normal_index as u32, }, ], side: side_positions.side, }); // 🮟 data.faces.push(Face { vertices: [ FaceVertex { pos_index: up_left_pos_index as u32, normal_index: top_normal_index as u32, }, FaceVertex { pos_index: down_left_pos_index as u32, normal_index: top_normal_index as u32, }, FaceVertex { pos_index: down_right_pos_index as u32, normal_index: top_normal_index as u32, }, ], side: side_positions.side, }); }