diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ad1e1c..c4ca833 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ take a new `HexOrientation` parameter (#189) * (**BREAKING**) `HexLayout` Y axis is no longer inverted by default (#187) * `HexLayout` builder patter (#187) +* (**BREAKING**) `HexLayout` removed the `invert_x` and `invert_y` fields (#190) +* (**BREAKING**) `HexLayout` `hex_size` field is now `scale` (#190) +* Added the following `HexLayout` methods: (#190) + - `transform_point` + - `transform_vector` + - `inverse_transform_point` + - `inverse_transform_vector` + - `invert_x` + - `invert_y` +* Added `world_unit_vector` methods to `EdgeDirection` and `VertexDirection` (#190) ## 0.19.1 diff --git a/README.md b/README.md index 4116082..7b096fa 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ // Define your layout let layout = HexLayout { - hex_size: Vec2::new(1.0, 1.0), + scale: Vec2::new(1.0, 1.0), orientation: HexOrientation::Flat, ..Default::default() }; diff --git a/examples/3d_columns.rs b/examples/3d_columns.rs index a55dac1..08abb5a 100644 --- a/examples/3d_columns.rs +++ b/examples/3d_columns.rs @@ -60,7 +60,7 @@ fn setup_grid( mut materials: ResMut>, ) { let layout = HexLayout { - hex_size: HEX_SIZE, + scale: HEX_SIZE, ..default() }; // materials diff --git a/examples/3d_picking.rs b/examples/3d_picking.rs index d95a9ce..86cca35 100644 --- a/examples/3d_picking.rs +++ b/examples/3d_picking.rs @@ -54,7 +54,7 @@ fn setup_grid( mut materials: ResMut>, ) { let layout = HexLayout { - hex_size: HEX_SIZE, + scale: HEX_SIZE, ..default() }; // materials diff --git a/examples/a_star.rs b/examples/a_star.rs index aa8e30a..79fd8b2 100644 --- a/examples/a_star.rs +++ b/examples/a_star.rs @@ -48,7 +48,7 @@ fn setup_grid( mut materials: ResMut>, ) { let layout = HexLayout { - hex_size: HEX_SIZE, + scale: HEX_SIZE, ..default() }; let mesh = meshes.add(hexagonal_plane(&layout)); diff --git a/examples/chunks.rs b/examples/chunks.rs index a2ef2c0..b38503a 100644 --- a/examples/chunks.rs +++ b/examples/chunks.rs @@ -35,7 +35,7 @@ fn setup_grid( mut materials: ResMut>, ) { let layout = HexLayout { - hex_size: HEX_SIZE, + scale: HEX_SIZE, ..default() }; // materials diff --git a/examples/field_of_movement.rs b/examples/field_of_movement.rs index 9c25947..b4729d9 100644 --- a/examples/field_of_movement.rs +++ b/examples/field_of_movement.rs @@ -87,7 +87,7 @@ fn setup_grid( mut materials: ResMut>, ) { let layout = HexLayout { - hex_size: HEX_SIZE, + scale: HEX_SIZE, ..default() }; let mesh = meshes.add(hexagonal_plane(&layout)); diff --git a/examples/field_of_view.rs b/examples/field_of_view.rs index bcd8d22..6b4eabe 100644 --- a/examples/field_of_view.rs +++ b/examples/field_of_view.rs @@ -48,7 +48,7 @@ fn setup_grid( mut materials: ResMut>, ) { let layout = HexLayout { - hex_size: HEX_SIZE, + scale: HEX_SIZE, ..default() }; let mesh = meshes.add(hexagonal_plane(&layout)); diff --git a/examples/hex_area.rs b/examples/hex_area.rs index 0ed59d5..695993c 100644 --- a/examples/hex_area.rs +++ b/examples/hex_area.rs @@ -55,16 +55,14 @@ fn setup_grid( mut materials: ResMut>, ) { let flat_layout = HexLayout { - hex_size: HEX_SIZE, + scale: HEX_SIZE, orientation: HexOrientation::Flat, origin: Vec2::new(-480.0, 0.0), - ..default() }; let pointy_layout = HexLayout { - hex_size: HEX_SIZE, + scale: HEX_SIZE, orientation: HexOrientation::Pointy, origin: Vec2::new(480.0, 0.0), - ..default() }; // materials let area_material = materials.add(Color::Srgba(GOLD)); diff --git a/examples/hex_grid.rs b/examples/hex_grid.rs index 9e1ea92..2684054 100644 --- a/examples/hex_grid.rs +++ b/examples/hex_grid.rs @@ -64,7 +64,7 @@ fn setup_grid( mut materials: ResMut>, ) { let layout = HexLayout { - hex_size: HEX_SIZE, + scale: HEX_SIZE, ..default() }; // materials diff --git a/examples/merged_columns.rs b/examples/merged_columns.rs index 1b7d791..a805933 100644 --- a/examples/merged_columns.rs +++ b/examples/merged_columns.rs @@ -66,7 +66,7 @@ fn setup_grid( commands.entity(map.0).despawn_recursive(); } let layout = HexLayout { - hex_size: settings.hex_size, + scale: settings.hex_size, ..default() }; // Materials shouldn't be added to assets every time, this is just to keep the diff --git a/examples/mesh_builder.rs b/examples/mesh_builder.rs index 20f8926..20ee409 100644 --- a/examples/mesh_builder.rs +++ b/examples/mesh_builder.rs @@ -266,8 +266,8 @@ fn gizmos( // Local axis let mut transform = *transform; transform.scale.y += params.height / 2.0; - transform.scale.x += info.layout.hex_size.x; - transform.scale.z += info.layout.hex_size.y; + transform.scale.x += info.layout.scale.x; + transform.scale.z += info.layout.scale.y; transform.scale *= params.scale; draw.axes(transform, 1.0); } diff --git a/examples/scroll_map.rs b/examples/scroll_map.rs index 525f8da..02a033d 100644 --- a/examples/scroll_map.rs +++ b/examples/scroll_map.rs @@ -42,7 +42,7 @@ fn setup_grid( mut materials: ResMut>, ) { let layout = HexLayout { - hex_size: HEX_SIZE, + scale: HEX_SIZE, ..default() }; let mesh = meshes.add(hexagonal_plane(&layout)); diff --git a/examples/shapes.rs b/examples/shapes.rs index b4fcdb1..b6f8cd2 100644 --- a/examples/shapes.rs +++ b/examples/shapes.rs @@ -84,7 +84,7 @@ impl Shape { pub fn setup(mut commands: Commands, mut mats: ResMut>) { commands.spawn(Camera2d); let layout = HexLayout { - hex_size: HEX_SIZE, + scale: HEX_SIZE, ..default() }; let mat = mats.add(Color::WHITE); @@ -112,6 +112,10 @@ fn show_ui(world: &mut World) { ui.label("Orientation"); bevy_inspector::ui_for_value(&mut map.layout.orientation, ui, world); }); + ui.horizontal(|ui| { + ui.label("scale"); + bevy_inspector::ui_for_value(&mut map.layout.scale, ui, world); + }); }); world.resource_scope(|world, mut shape: Mut| { diff --git a/examples/sprite_sheet.rs b/examples/sprite_sheet.rs index 893faf0..aa819b2 100644 --- a/examples/sprite_sheet.rs +++ b/examples/sprite_sheet.rs @@ -40,7 +40,7 @@ fn setup_grid( let atlas_layout = atlas_layouts.add(atlas_layout); let layout = HexLayout { orientation: HexOrientation::Pointy, - hex_size: HEX_SIZE, + scale: HEX_SIZE, ..default() }; let sprite_size = layout.rect_size(); diff --git a/examples/wrap_map.rs b/examples/wrap_map.rs index 8728831..59c5d4d 100644 --- a/examples/wrap_map.rs +++ b/examples/wrap_map.rs @@ -46,7 +46,7 @@ fn setup_grid( mut materials: ResMut>, ) { let layout = HexLayout { - hex_size: HEX_SIZE, + scale: HEX_SIZE, ..default() }; let mesh = meshes.add(hexagonal_plane(&layout)); diff --git a/src/direction/edge_direction.rs b/src/direction/edge_direction.rs index 3965b44..fe3ef4a 100644 --- a/src/direction/edge_direction.rs +++ b/src/direction/edge_direction.rs @@ -3,7 +3,7 @@ use crate::{ DIRECTION_ANGLE_DEGREES, DIRECTION_ANGLE_OFFSET_DEGREES, DIRECTION_ANGLE_OFFSET_RAD, DIRECTION_ANGLE_RAD, }, - Hex, HexOrientation, VertexDirection, + Hex, HexLayout, HexOrientation, VertexDirection, }; use glam::Vec2; use std::{f32::consts::TAU, fmt::Debug}; @@ -384,12 +384,27 @@ impl EdgeDirection { #[inline] #[must_use] - /// Returns the unit vector of the direction in the given `orientation` + /// Returns the unit vector of the direction in the given `orientation`. + /// + /// The vector is normalized and in local hex space. To use within a + /// [`HexLayout`] use [`HexLayout::transform_vector`] or + /// [`Self::world_unit_vector`] pub fn unit_vector(self, orientation: HexOrientation) -> Vec2 { let angle = self.angle(orientation); Vec2::new(angle.cos(), angle.sin()) } + #[inline] + #[must_use] + /// Returns the unit vector of the direction in the given `layout`. + /// + /// The vector is provided in pixel/workd space. To use in local hex + /// space use [`Self::unit_vector`] + pub fn world_unit_vector(self, layout: &HexLayout) -> Vec2 { + let vector = self.unit_vector(layout.orientation); + layout.transform_vector(vector) + } + #[inline] #[must_use] /// Returns the angle in degrees of the given direction for *pointy* diff --git a/src/direction/vertex_direction.rs b/src/direction/vertex_direction.rs index 8d50211..413ab28 100644 --- a/src/direction/vertex_direction.rs +++ b/src/direction/vertex_direction.rs @@ -3,7 +3,7 @@ use crate::{ DIRECTION_ANGLE_DEGREES, DIRECTION_ANGLE_OFFSET_DEGREES, DIRECTION_ANGLE_OFFSET_RAD, DIRECTION_ANGLE_RAD, }, - EdgeDirection, Hex, HexOrientation, + EdgeDirection, Hex, HexLayout, HexOrientation, }; use glam::Vec2; use std::{f32::consts::TAU, fmt::Debug}; @@ -386,11 +386,26 @@ impl VertexDirection { #[inline] #[must_use] /// Returns the unit vector of the direction in the given `orientation` + /// + /// The vector is normalized and in local hex space. To use within a + /// [`HexLayout`] use [`HexLayout::transform_vector`] or + /// [`Self::world_unit_vector`] pub fn unit_vector(self, orientation: HexOrientation) -> Vec2 { let angle = self.angle(orientation); Vec2::new(angle.cos(), angle.sin()) } + #[inline] + #[must_use] + /// Returns the unit vector of the direction in the given `layout`. + /// + /// The vector is provided in pixel/workd space. To use in local hex + /// space use [`Self::unit_vector`] + pub fn world_unit_vector(self, layout: &HexLayout) -> Vec2 { + let vector = self.unit_vector(layout.orientation); + layout.transform_vector(vector) + } + #[inline] #[must_use] /// Returns the angle in degrees of the given direction for *pointy* diff --git a/src/layout.rs b/src/layout.rs index 45dc397..4ad2796 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -21,12 +21,8 @@ use glam::Vec2; /// orientation: HexOrientation::Flat, /// // We define the world space origin equivalent of `Hex::ZERO` in hex space /// origin: Vec2::new(1.0, 2.0), -/// // We define the world space size of the hexagons -/// hex_size: Vec2::new(1.0, 1.0), -/// // We invert the y axis which will now go down -/// invert_y: true, -/// // But not the x axis -/// invert_x: false, +/// // We define the world space scale of the hexagons +/// scale: Vec2::new(1.0, 1.0), /// }; /// // You can now find the world positon (center) of any given hexagon /// let world_pos = layout.hex_to_world_pos(Hex::ZERO); @@ -41,11 +37,13 @@ use glam::Vec2; /// ```rust /// # use hexx::*; /// -/// let layout = HexLayout::new(HexOrientation::Flat) -/// .with_size(Vec2::new(2.0, 3.0)) // Individual Hexagon size -/// .with_origin(Vec2::new(-1.0, 0.0)) // World origin -/// .invert_x() // Invert the x axis, which will now go left -/// .invert_y(); // Invert the y axis, which will now go down +/// let mut layout = HexLayout::new(HexOrientation::Flat) +/// .with_scale(Vec2::new(2.0, 3.0)) // Individual Hexagon size +/// .with_origin(Vec2::new(-1.0, 0.0)); // World origin +/// // Invert the x axis, which will now go left. Will change `scale.x` to `-2.0` +/// layout.invert_x(); +/// // Invert the y axis, which will now go down. Will change `scale.y` to `-3.0` +/// layout.invert_y(); /// ``` #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -57,12 +55,52 @@ pub struct HexLayout { /// [`Vec2::ZERO`] pub origin: Vec2, /// The size of individual hexagons in world/pixel space. The size can be - /// irregular - pub hex_size: Vec2, - /// If set to `true`, the `Hex` `x` axis will be inverted - pub invert_x: bool, - /// If set to `true`, the `Hex` `y` axis will be inverted - pub invert_y: bool, + /// irregular or negative + pub scale: Vec2, +} + +impl HexLayout { + /// Inverts the layout `X` axis + pub fn invert_x(&mut self) { + self.scale.x *= -1.0; + } + + /// Inverts the layout `Y` axis + pub fn invert_y(&mut self) { + self.scale.y *= -1.0; + } + + /// Transforms a local hex space vector to world space + /// by applying the layout `scale` + #[must_use] + #[inline] + pub fn transform_vector(&self, vector: Vec2) -> Vec2 { + vector * self.scale + } + + /// Transforms a local hex point to world space + /// by applying the layout `scale` and `origin` + #[must_use] + #[inline] + pub fn transform_point(&self, point: Vec2) -> Vec2 { + self.origin + self.transform_vector(point) + } + + /// Transforms a world space vector to local hex space + /// by applying the layout `scale` + #[must_use] + #[inline] + pub fn inverse_transform_vector(&self, vector: Vec2) -> Vec2 { + vector / self.scale + } + + /// Transforms a world pace point to local hex space + /// by applying the layout `scale` and `origin` + #[must_use] + #[inline] + pub fn inverse_transform_point(&self, point: Vec2) -> Vec2 { + self.inverse_transform_vector(point - self.origin) + } } impl HexLayout { @@ -79,7 +117,7 @@ impl HexLayout { /// ignoring [`HexLayout::origin`] pub(crate) fn hex_to_center_aligned_world_pos(&self, hex: Hex) -> Vec2 { let [x, y] = self.orientation.forward(hex.to_array_f32()); - Vec2::new(x, y) * self.hex_size * self.axis_scale() + self.transform_vector(Vec2::new(x, y)) } #[must_use] @@ -88,7 +126,7 @@ impl HexLayout { /// coordinates pub fn fract_hex_to_world_pos(&self, hex: Vec2) -> Vec2 { let [x, y] = self.orientation.forward(hex.to_array()); - Vec2::new(x, y) * self.hex_size * self.axis_scale() + self.origin + self.transform_point(Vec2::new(x, y)) } #[must_use] @@ -104,7 +142,7 @@ impl HexLayout { /// Computes world/pixel coordinates `pos` into fractional hexagonal /// coordinates pub fn world_pos_to_fract_hex(&self, pos: Vec2) -> Vec2 { - let point = (pos - self.origin) * self.axis_scale() / self.hex_size; + let point = self.inverse_transform_point(pos); let [x, y] = self.orientation.inverse(point.to_array()); Vec2::new(x, y) } @@ -114,22 +152,14 @@ impl HexLayout { /// `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 * self.axis_scale()) + center) + self.center_aligned_hex_corners().map(|c| c + center) } #[must_use] /// Unscaled, non offsetted hex corners pub(crate) fn center_aligned_hex_corners(&self) -> [Vec2; 6] { - VertexDirection::ALL_DIRECTIONS.map(|dir| dir.unit_vector(self.orientation) * self.hex_size) - } - - #[inline] - /// Returns a signum axis coefficient, allowing for inverted axis - const fn axis_scale(&self) -> Vec2 { - let x = if self.invert_x { -1.0 } else { 1.0 }; - let y = if self.invert_y { -1.0 } else { 1.0 }; - Vec2::new(x, y) + VertexDirection::ALL_DIRECTIONS + .map(|dir| self.transform_vector(dir.unit_vector(self.orientation))) } #[inline] @@ -137,7 +167,7 @@ impl HexLayout { /// Returns the size of the bounding box/rect of an hexagon /// This uses both the `hex_size` and `orientation` of the layout. pub fn rect_size(&self) -> Vec2 { - self.hex_size + self.scale * match self.orientation { HexOrientation::Pointy => Vec2::new(SQRT_3, 2.0), HexOrientation::Flat => Vec2::new(2.0, SQRT_3), @@ -175,7 +205,7 @@ impl HexLayout { } fn __vertex_coordinates(&self, vertex: crate::GridVertex) -> Vec2 { - vertex.direction.unit_vector(self.orientation) * self.hex_size * self.axis_scale() + self.transform_vector(vertex.direction.unit_vector(self.orientation)) } } @@ -189,9 +219,7 @@ impl HexLayout { Self { orientation, origin: Vec2::ZERO, - hex_size: Vec2::ONE, - invert_x: false, - invert_y: false, + scale: Vec2::ONE, } } @@ -205,25 +233,9 @@ impl HexLayout { #[must_use] #[inline] - /// Specifies the world/pixel size of individual hexagons - pub const fn with_size(mut self, hex_size: Vec2) -> Self { - self.hex_size = hex_size; - self - } - - /// Inverts the world/pixel `x` axis - #[must_use] - #[inline] - pub const fn invert_x(mut self) -> Self { - self.invert_x = true; - self - } - - /// Inverts the world/pixel `y` axis - #[must_use] - #[inline] - pub const fn invert_y(mut self) -> Self { - self.invert_y = true; + /// Specifies the world/pixel scale of individual hexagons + pub const fn with_scale(mut self, scale: Vec2) -> Self { + self.scale = scale; self } } @@ -242,32 +254,30 @@ mod tests { #[test] fn flat_corners() { let point = Hex::new(0, 0); - let layout = HexLayout::new(HexOrientation::Flat) - .with_size(Vec2::new(10., 10.)) - .invert_y(); + let mut layout = HexLayout::new(HexOrientation::Flat).with_scale(Vec2::new(10., 10.)); let corners = layout.hex_corners(point).map(Vec2::round); assert_eq!( corners, [ Vec2::new(10.0, 0.0), - Vec2::new(5.0, -9.0), - Vec2::new(-5.0, -9.0), - Vec2::new(-10.0, 0.0), - Vec2::new(-5.0, 9.0), Vec2::new(5.0, 9.0), + Vec2::new(-5.0, 9.0), + Vec2::new(-10.0, 0.0), + Vec2::new(-5.0, -9.0), + Vec2::new(5.0, -9.0), ] ); - let layout = HexLayout::new(HexOrientation::Flat).with_size(Vec2::new(10., 10.)); + layout.invert_y(); let corners = layout.hex_corners(point).map(Vec2::round); assert_eq!( corners, [ Vec2::new(10.0, 0.0), - Vec2::new(5.0, 9.0), - Vec2::new(-5.0, 9.0), - Vec2::new(-10.0, 0.0), - Vec2::new(-5.0, -9.0), Vec2::new(5.0, -9.0), + Vec2::new(-5.0, -9.0), + Vec2::new(-10.0, 0.0), + Vec2::new(-5.0, 9.0), + Vec2::new(5.0, 9.0), ] ); } @@ -275,32 +285,30 @@ mod tests { #[test] fn pointy_corners() { let point = Hex::new(0, 0); - let layout = HexLayout::new(HexOrientation::Pointy) - .with_size(Vec2::new(10., 10.)) - .invert_y(); + let mut layout = HexLayout::new(HexOrientation::Pointy).with_scale(Vec2::new(10., 10.)); let corners = layout.hex_corners(point).map(Vec2::round); assert_eq!( corners, [ - Vec2::new(9.0, 5.0), Vec2::new(9.0, -5.0), - Vec2::new(-0.0, -10.0), - Vec2::new(-9.0, -5.0), + Vec2::new(9.0, 5.0), + Vec2::new(-0.0, 10.0), Vec2::new(-9.0, 5.0), - Vec2::new(0.0, 10.0), + Vec2::new(-9.0, -5.0), + Vec2::new(0.0, -10.0), ] ); - let layout = HexLayout::new(HexOrientation::Pointy).with_size(Vec2::new(10., 10.)); + layout.invert_y(); let corners = layout.hex_corners(point).map(Vec2::round); assert_eq!( corners, [ - Vec2::new(9.0, -5.0), Vec2::new(9.0, 5.0), - Vec2::new(-0.0, 10.0), - Vec2::new(-9.0, 5.0), + Vec2::new(9.0, -5.0), + Vec2::new(-0.0, -10.0), Vec2::new(-9.0, -5.0), - Vec2::new(0.0, -10.0), + Vec2::new(-9.0, 5.0), + Vec2::new(0.0, 10.0), ] ); } diff --git a/src/lib.rs b/src/lib.rs index f28fb60..75fbd87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,7 +107,7 @@ //! //! // Define your layout //! let layout = HexLayout { -//! hex_size: Vec2::new(1.0, 1.0), +//! scale: Vec2::new(1.0, 1.0), //! orientation: HexOrientation::Flat, //! ..Default::default() //! }; diff --git a/src/orientation.rs b/src/orientation.rs index 2b4965d..a865942 100644 --- a/src/orientation.rs +++ b/src/orientation.rs @@ -1,5 +1,4 @@ use std::ops::Deref; - pub(crate) const SQRT_3: f32 = 1.732_050_8; /// Pointy orientation matrices and offset