From f42a3785a2a7eeabdf19df0927ed8210d75e7e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Lescaudey=20de=20Maneville?= Date: Mon, 22 Jan 2024 12:55:01 +0100 Subject: [PATCH] Center aligned meshes (#139) > Closes #136 ## What does it solve - [x] Mesh generation was broken if used with an `HexLayout` with a non zero `origin`, as the origin offset would be applied twice ( `hexagonal_plane` + the mesh builders) - [x] In addition to this, if used with `bevy` the offset would be also applied by hexagon entities transforms ## Solution * Added a `center_aligned` builder method to all mesh builders. this negates the `HexLayout` offset * Fixed `PlaneMeshBuilder::build` method which was applying the offset twice --- CHANGELOG.md | 3 +++ examples/3d_columns.rs | 1 + examples/a_star.rs | 1 + examples/chunks.rs | 1 + examples/field_of_movement.rs | 5 ++++- examples/field_of_view.rs | 1 + examples/hex_grid.rs | 1 + examples/merged_columns.rs | 1 + examples/scroll_map.rs | 5 ++++- examples/wrap_map.rs | 5 ++++- src/layout.rs | 16 ++++++++++++---- src/mesh/column_builder.rs | 19 ++++++++++++++++++- src/mesh/mod.rs | 27 +++++++++++++++++++++++++++ src/mesh/plane_builder.rs | 23 ++++++++++++++++++++--- 14 files changed, 98 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cb3805..95ffb78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ nodes instead of one, allowing for more use cases (#130, #128) * Bumped `bevy_inspector_egui` dependency (#129) * Added a `sprite_sheet` bevy example (#135) * Added `HexLayout::rect_size` method (#135) +* Added `ColumnMeshBuilder::center_aligned` option (#139) +* Added `PlaneMeshBuilder::center_aligned` option (#139) +* Deprecated `MeshInfo::hexagonal_plane` in favor of `PlaneMeshBuilder` (#139) ## 0.12.0 diff --git a/examples/3d_columns.rs b/examples/3d_columns.rs index 1888268..f927184 100644 --- a/examples/3d_columns.rs +++ b/examples/3d_columns.rs @@ -124,6 +124,7 @@ fn hexagonal_column(hex_layout: &HexLayout) -> Mesh { let mesh_info = ColumnMeshBuilder::new(hex_layout, COLUMN_HEIGHT) .without_bottom_face() .with_scale(Vec3::splat(0.9)) + .center_aligned() .build(); Mesh::new(PrimitiveTopology::TriangleList) .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, mesh_info.vertices) diff --git a/examples/a_star.rs b/examples/a_star.rs index 0b80f53..61ec4cc 100644 --- a/examples/a_star.rs +++ b/examples/a_star.rs @@ -154,6 +154,7 @@ fn hexagonal_plane(hex_layout: &HexLayout) -> Mesh { let mesh_info = PlaneMeshBuilder::new(hex_layout) .facing(Vec3::Z) .with_scale(Vec3::splat(0.9)) + .center_aligned() .build(); Mesh::new(PrimitiveTopology::TriangleList) .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, mesh_info.vertices) diff --git a/examples/chunks.rs b/examples/chunks.rs index d552566..141c109 100644 --- a/examples/chunks.rs +++ b/examples/chunks.rs @@ -61,6 +61,7 @@ fn hexagonal_plane(hex_layout: &HexLayout) -> Mesh { let mesh_info = PlaneMeshBuilder::new(hex_layout) .with_scale(Vec3::splat(0.9)) .facing(Vec3::Z) + .center_aligned() .build(); Mesh::new(PrimitiveTopology::TriangleList) .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, mesh_info.vertices) diff --git a/examples/field_of_movement.rs b/examples/field_of_movement.rs index 3b812c2..09f5298 100644 --- a/examples/field_of_movement.rs +++ b/examples/field_of_movement.rs @@ -135,7 +135,10 @@ fn setup_grid( /// Compute a bevy mesh from the layout fn hexagonal_plane(hex_layout: &HexLayout) -> Mesh { - let mesh_info = PlaneMeshBuilder::new(hex_layout).facing(Vec3::Z).build(); + let mesh_info = PlaneMeshBuilder::new(hex_layout) + .facing(Vec3::Z) + .center_aligned() + .build(); Mesh::new(PrimitiveTopology::TriangleList) .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, mesh_info.vertices) .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, mesh_info.normals) diff --git a/examples/field_of_view.rs b/examples/field_of_view.rs index a215a07..392cfb1 100644 --- a/examples/field_of_view.rs +++ b/examples/field_of_view.rs @@ -144,6 +144,7 @@ fn hexagonal_plane(hex_layout: &HexLayout) -> Mesh { let mesh_info = PlaneMeshBuilder::new(hex_layout) .facing(Vec3::Z) .with_scale(Vec3::splat(0.9)) + .center_aligned() .build(); Mesh::new(PrimitiveTopology::TriangleList) .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, mesh_info.vertices) diff --git a/examples/hex_grid.rs b/examples/hex_grid.rs index fe6f5df..f4e91a0 100644 --- a/examples/hex_grid.rs +++ b/examples/hex_grid.rs @@ -202,6 +202,7 @@ fn hexagonal_plane(hex_layout: &HexLayout) -> Mesh { let mesh_info = PlaneMeshBuilder::new(hex_layout) .facing(Vec3::Z) .with_scale(Vec3::splat(0.95)) + .center_aligned() .build(); Mesh::new(PrimitiveTopology::TriangleList) .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, mesh_info.vertices) diff --git a/examples/merged_columns.rs b/examples/merged_columns.rs index 6c206ce..cc14263 100644 --- a/examples/merged_columns.rs +++ b/examples/merged_columns.rs @@ -102,6 +102,7 @@ fn setup_grid( let info = ColumnMeshBuilder::new(&layout, height) .at(c) .without_bottom_face() + .center_aligned() .build(); mesh.merge_with(info); mesh diff --git a/examples/scroll_map.rs b/examples/scroll_map.rs index edbe6f3..cbe4aec 100644 --- a/examples/scroll_map.rs +++ b/examples/scroll_map.rs @@ -99,7 +99,10 @@ fn handle_input( /// Compute a bevy mesh from the layout fn hexagonal_plane(hex_layout: &HexLayout) -> Mesh { - let mesh_info = PlaneMeshBuilder::new(hex_layout).facing(Vec3::Z).build(); + let mesh_info = PlaneMeshBuilder::new(hex_layout) + .facing(Vec3::Z) + .center_aligned() + .build(); Mesh::new(PrimitiveTopology::TriangleList) .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, mesh_info.vertices) .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, mesh_info.normals) diff --git a/examples/wrap_map.rs b/examples/wrap_map.rs index 3bed7d9..3e86c50 100644 --- a/examples/wrap_map.rs +++ b/examples/wrap_map.rs @@ -108,7 +108,10 @@ fn handle_input( /// Compute a bevy mesh from the layout fn hexagonal_plane(hex_layout: &HexLayout) -> Mesh { - let mesh_info = PlaneMeshBuilder::new(hex_layout).facing(Vec3::Z).build(); + let mesh_info = PlaneMeshBuilder::new(hex_layout) + .facing(Vec3::Z) + .center_aligned() + .build(); Mesh::new(PrimitiveTopology::TriangleList) .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, mesh_info.vertices) .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, mesh_info.normals) diff --git a/src/layout.rs b/src/layout.rs index 081ddd5..9121c03 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -52,17 +52,21 @@ pub struct HexLayout { } impl HexLayout { - #[allow(clippy::cast_precision_loss)] #[must_use] /// Computes hexagonal coordinates `hex` into world/pixel coordinates pub fn hex_to_world_pos(&self, hex: Hex) -> Vec2 { + self.hex_to_center_aligned_world_pos(hex) + self.origin + } + + #[allow(clippy::cast_precision_loss)] + #[must_use] + pub(crate) fn hex_to_center_aligned_world_pos(&self, hex: Hex) -> Vec2 { let matrix = self.orientation.forward_matrix; Vec2::new( matrix[0].mul_add(hex.x() as f32, matrix[1] * hex.y() as f32), matrix[2].mul_add(hex.x() as f32, matrix[3] * hex.y() as f32), ) * self.hex_size * self.axis_scale() - + self.origin } #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)] @@ -77,15 +81,19 @@ impl HexLayout { ]) } - #[allow(clippy::cast_precision_loss)] #[must_use] /// Retrieves all 6 corner coordinates of the given hexagonal coordinates /// `hex` pub fn hex_corners(&self, hex: Hex) -> [Vec2; 6] { let center = self.hex_to_world_pos(hex); + self.center_aligned_hex_corners().map(|c| c + center) + } + + #[must_use] + pub(crate) fn center_aligned_hex_corners(&self) -> [Vec2; 6] { Direction::ALL_DIRECTIONS.map(|dir| { let angle = dir.angle_pointy() + self.orientation.angle_offset; - center + Vec2::new(self.hex_size.x * angle.cos(), self.hex_size.y * angle.sin()) + Vec2::new(self.hex_size.x * angle.cos(), self.hex_size.y * angle.sin()) }) } diff --git a/src/mesh/column_builder.rs b/src/mesh/column_builder.rs index 15c8a5b..a5627a2 100644 --- a/src/mesh/column_builder.rs +++ b/src/mesh/column_builder.rs @@ -63,6 +63,8 @@ pub struct ColumnMeshBuilder<'l> { pub sides_uv_options: UVOptions, /// UV mapping options for top and bottom faces pub caps_uv_options: UVOptions, + /// If set to `true`, the mesh will ignore [`HexLayout::origin`] + pub center_aligned: bool, } impl<'l> ColumnMeshBuilder<'l> { @@ -81,6 +83,7 @@ impl<'l> ColumnMeshBuilder<'l> { bottom_face: true, sides_uv_options: UVOptions::quad_default(), caps_uv_options: UVOptions::cap_default(), + center_aligned: false, } } @@ -190,6 +193,15 @@ impl<'l> ColumnMeshBuilder<'l> { self } + #[must_use] + #[inline] + /// Ignores the [`HexLayout::origin`] offset, generating a mesh centered + /// around `(0.0, 0.0)`. + pub const fn center_aligned(mut self) -> Self { + self.center_aligned = true; + self + } + #[must_use] #[allow(clippy::cast_precision_loss)] #[allow(clippy::many_single_char_names)] @@ -198,9 +210,14 @@ impl<'l> ColumnMeshBuilder<'l> { // We compute the mesh at the origin to allow scaling let cap_mesh = PlaneMeshBuilder::new(self.layout) .with_uv_options(self.caps_uv_options) + .center_aligned() .build(); // We store the offset to match the `self.pos` - let pos = self.layout.hex_to_world_pos(self.pos); + 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); // We create the final mesh let mut mesh = MeshInfo::default(); diff --git a/src/mesh/mod.rs b/src/mesh/mod.rs index f3cb92d..253011a 100644 --- a/src/mesh/mod.rs +++ b/src/mesh/mod.rs @@ -135,6 +135,7 @@ impl MeshInfo { /// * rotation /// * etc #[must_use] + #[deprecated(since = "0.13.0", note = "Use `PlaneMeshBuilder` instead")] pub fn hexagonal_plane(layout: &HexLayout, hex: Hex) -> Self { let corners = layout.hex_corners(hex); let corners_arr = corners.map(|p| Vec3::new(p.x, 0., p.y)); @@ -160,6 +161,32 @@ impl MeshInfo { } } + #[must_use] + pub(crate) fn center_aligned_hexagonal_plane(layout: &HexLayout) -> Self { + let corners = layout.center_aligned_hex_corners(); + let corners_arr = corners.map(|p| Vec3::new(p.x, 0., p.y)); + Self { + vertices: vec![ + corners_arr[0], + corners_arr[1], + corners_arr[2], + corners_arr[3], + corners_arr[4], + corners_arr[5], + ], + uvs: vec![ + corners[0], corners[1], corners[2], corners[3], corners[4], corners[5], + ], + normals: [Vec3::Y; 6].to_vec(), + indices: vec![ + 0, 2, 1, // Top tri + 3, 5, 4, // Bot tri + 0, 5, 3, // Mid Quad + 3, 2, 0, // Mid Quad + ], + } + } + /// Computes cheap mesh data for an hexagonal column facing `Vec3::Y` /// without the bottom face. /// diff --git a/src/mesh/plane_builder.rs b/src/mesh/plane_builder.rs index 19b02af..dd11bfb 100644 --- a/src/mesh/plane_builder.rs +++ b/src/mesh/plane_builder.rs @@ -34,6 +34,8 @@ pub struct PlaneMeshBuilder<'l> { pub rotation: Option, /// UV mapping options pub uv_options: UVOptions, + /// If set to `true`, the mesh will ignore [`HexLayout::origin`] + pub center_aligned: bool, } impl<'l> PlaneMeshBuilder<'l> { @@ -47,6 +49,7 @@ impl<'l> PlaneMeshBuilder<'l> { offset: None, scale: None, uv_options: UVOptions::cap_default(), + center_aligned: false, } } @@ -102,13 +105,26 @@ impl<'l> PlaneMeshBuilder<'l> { self } + #[must_use] + #[inline] + /// Ignores the [`HexLayout::origin`] offset, generating a mesh centered + /// around `(0.0, 0.0)`. + pub const fn center_aligned(mut self) -> Self { + self.center_aligned = true; + self + } + /// Comsumes the builder to return the computed mesh data #[must_use] pub fn build(self) -> MeshInfo { - // We compute the mesh at the origin to allow scaling - let mut mesh = MeshInfo::hexagonal_plane(self.layout, Hex::ZERO); + // We compute the mesh at the origin and no offset to allow scaling + let mut mesh = MeshInfo::center_aligned_hexagonal_plane(self.layout); // We store the offset to match the `self.pos` - let pos = self.layout.hex_to_world_pos(self.pos); + 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); // **S** - We apply optional scale if let Some(scale) = self.scale { @@ -122,6 +138,7 @@ impl<'l> PlaneMeshBuilder<'l> { if let Some(custom_offset) = self.offset { offset += custom_offset; } + mesh = mesh.with_offset(offset); self.uv_options.alter_uvs(&mut mesh.uvs); mesh }