From df55a891e44a6438892d96b0de24804f6e55a1b7 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Fri, 8 Sep 2023 16:59:37 -0700 Subject: [PATCH] Add `math::Axis` and replace uses of integers for axis selection. I've wanted to do this for a while, but the immediate motivation is that I am considering switching vector libraries, and the replacement does not have `impl Index for Vector3` and such. --- CHANGELOG.md | 5 + all-is-cubes-content/src/atrium.rs | 4 +- .../src/dungeon/demo_dungeon.rs | 17 ++- all-is-cubes-content/src/dungeon/generic.rs | 2 +- all-is-cubes-content/src/tree.rs | 15 +- all-is-cubes-mesh/src/planar.rs | 4 +- all-is-cubes-ui/src/vui/layout.rs | 14 +- all-is-cubes/src/camera.rs | 4 +- all-is-cubes/src/content.rs | 11 +- all-is-cubes/src/math.rs | 4 +- all-is-cubes/src/math/aab.rs | 12 +- all-is-cubes/src/math/axis.rs | 141 ++++++++++++++++++ all-is-cubes/src/math/face.rs | 35 +++-- all-is-cubes/src/math/grid_aab.rs | 36 +++-- all-is-cubes/src/math/rotation.rs | 6 +- all-is-cubes/src/physics/body.rs | 2 +- all-is-cubes/src/physics/collision.rs | 2 +- all-is-cubes/src/raycast.rs | 17 ++- test-renderers/src/test_cases.rs | 2 +- 19 files changed, 239 insertions(+), 94 deletions(-) create mode 100644 all-is-cubes/src/math/axis.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a4bd442f2..f60566435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Added - `all-is-cubes` library: + - `math::Axis` is an enum of coordinate axes. + - `math::Cube` represents a unit cube on the grid; it replaces many previous uses of `GridPoint` to identify cubes. - `math::Gridgid` represents rigid transformations, a useful subset of what `GridMatrix` already could do. @@ -23,6 +25,9 @@ - All functions manipulating volume data in `Space`, `GridArray`, `Evoxels`, etc. have changed signature to use the new type `math::Cube` instead of `math::GridPoint`. + - All functions using `usize` to identify a coordinate axis now use `math::Axis` instead. + `Face6::axis_number()` and `Face7::axis_number()` are now called `axis()`. + - The following functions have changed signature to use the new type `math::Gridgid`: - `math::GridAab::transform()` - `math::GridMatrix::decompose()` diff --git a/all-is-cubes-content/src/atrium.rs b/all-is-cubes-content/src/atrium.rs index 1d1671f87..c650d7a04 100644 --- a/all-is-cubes-content/src/atrium.rs +++ b/all-is-cubes-content/src/atrium.rs @@ -11,7 +11,7 @@ use all_is_cubes::character::Spawn; use all_is_cubes::content::{free_editing_starter_inventory, palette}; use all_is_cubes::linking::{BlockModule, BlockProvider, InGenError}; use all_is_cubes::math::{ - Cube, Face6, FaceMap, FreeCoordinate, GridAab, GridArray, GridCoordinate, GridPoint, + Axis, Cube, Face6, FaceMap, FreeCoordinate, GridAab, GridArray, GridCoordinate, GridPoint, GridRotation, GridVector, Gridgid, Rgb, Rgba, }; use all_is_cubes::space::{SetCubeError, Space, SpacePhysics, SpaceTransaction}; @@ -333,7 +333,7 @@ fn arch_row( for i in 0..section_count { let column_base = first_column_base + offset * (i + 1); - let banner_color = if parallel.axis_number() == 2 { + let banner_color = if parallel.axis() == Axis::Y { match i.rem_euclid(3) { 0 => Some(BannerColor::Red), 1 => Some(BannerColor::Green), diff --git a/all-is-cubes-content/src/dungeon/demo_dungeon.rs b/all-is-cubes-content/src/dungeon/demo_dungeon.rs index e49d73fd7..c42cc2cda 100644 --- a/all-is-cubes-content/src/dungeon/demo_dungeon.rs +++ b/all-is-cubes-content/src/dungeon/demo_dungeon.rs @@ -14,8 +14,8 @@ use all_is_cubes::drawing::VoxelBrush; use all_is_cubes::inv::Tool; use all_is_cubes::linking::{BlockModule, BlockProvider, GenError, InGenError}; use all_is_cubes::math::{ - Cube, Face6, FaceMap, GridAab, GridArray, GridCoordinate, GridPoint, GridRotation, GridVector, - Rgb, Rgba, + Axis, Cube, Face6, FaceMap, GridAab, GridArray, GridCoordinate, GridPoint, GridRotation, + GridVector, Rgb, Rgba, }; use all_is_cubes::space::{LightPhysics, Space}; use all_is_cubes::time; @@ -117,7 +117,7 @@ impl DemoTheme { face: Face6, has_gate: bool, ) -> Result<(), InGenError> { - let passage_axis = face.axis_number(); + let passage_axis = face.axis(); let mut room_1_box = self.actual_room_box( room_position, @@ -136,8 +136,8 @@ impl DemoTheme { } let wall_parallel = GridRotation::CLOCKWISE.transform(face); - let parallel_axis = wall_parallel.axis_number(); - assert!(parallel_axis != 1); + let parallel_axis = wall_parallel.axis(); + assert!(parallel_axis != Axis::Y); let rotate_nz_to_face = GridRotation::from_to(Face6::NZ, face, Face6::PY).unwrap(); @@ -173,9 +173,10 @@ impl DemoTheme { // Gate if has_gate { - let gate_box = doorway_box.abut(face, -1).unwrap().translate( - face.opposite().normal_vector() * doorway_box.size()[face.axis_number()] / 2, - ); + let gate_box = doorway_box + .abut(face, -1) + .unwrap() + .translate(face.opposite().normal_vector() * doorway_box.size()[face.axis()] / 2); let gate_side_1 = gate_box.abut(wall_parallel.opposite(), -1).unwrap(); let gate_side_2 = gate_box.abut(wall_parallel, -1).unwrap(); space.fill_uniform( diff --git a/all-is-cubes-content/src/dungeon/generic.rs b/all-is-cubes-content/src/dungeon/generic.rs index c3355b128..9a8c4dfc1 100644 --- a/all-is-cubes-content/src/dungeon/generic.rs +++ b/all-is-cubes-content/src/dungeon/generic.rs @@ -65,7 +65,7 @@ impl DungeonGrid { face, GridCoordinate::from(self.room_wall_thickness[face]) + GridCoordinate::from(self.room_wall_thickness[face.opposite()]) - + GridCoordinate::from(self.gap_between_walls[face.axis_number()]), + + GridCoordinate::from(self.gap_between_walls[face.axis()]), ) .unwrap() } diff --git a/all-is-cubes-content/src/tree.rs b/all-is-cubes-content/src/tree.rs index fa270fce5..2668e66d7 100644 --- a/all-is-cubes-content/src/tree.rs +++ b/all-is-cubes-content/src/tree.rs @@ -208,6 +208,7 @@ pub(crate) fn make_tree( use graph::Growph; mod graph { use super::*; + use all_is_cubes::math::Axis; /// A graph of connections between adjacent blocks of a tree we're going to grow. /// @@ -234,11 +235,11 @@ mod graph { // TODO: could, theoretically, numeric overflow self.data .get(cube + neighbor_face.normal_vector()) - .map(|cell| cell.pos_neighbors[neighbor_face.axis_number()]) + .map(|cell| cell.pos_neighbors[neighbor_face.axis()]) } else { self.data .get(cube) - .map(|cell| cell.pos_neighbors[neighbor_face.axis_number()]) + .map(|cell| cell.pos_neighbors[neighbor_face.axis()]) } } @@ -252,12 +253,12 @@ mod graph { // TODO: could, theoretically, numeric overflow self.data .get_mut(cube + neighbor_face.normal_vector()) - .map(|cell| &mut cell.pos_neighbors[neighbor_face.axis_number()]) + .map(|cell| &mut cell.pos_neighbors[neighbor_face.axis()]) } else { // TODO: reject attempts to modify the extraneous edges exiting the upper bounds self.data .get_mut(cube) - .map(|cell| &mut cell.pos_neighbors[neighbor_face.axis_number()]) + .map(|cell| &mut cell.pos_neighbors[neighbor_face.axis()]) } } @@ -310,7 +311,7 @@ mod graph { impl petgraph::visit::GraphBase for Growph { type NodeId = Cube; - type EdgeId = (Cube, usize); // TODO: this is evidence for wanting an axis-ID enum + type EdgeId = (Cube, Axis); } impl petgraph::visit::Data for Growph { type NodeWeight = Option; @@ -376,7 +377,7 @@ mod graph { } impl petgraph::visit::EdgeRef for GrowphEdgeRef { type NodeId = Cube; - type EdgeId = (Cube, usize); + type EdgeId = (Cube, Axis); type Weight = Option; fn source(&self) -> Self::NodeId { @@ -397,7 +398,7 @@ mod graph { } else { self.source() }, - self.face.axis_number(), + self.face.axis(), ) } } diff --git a/all-is-cubes-mesh/src/planar.rs b/all-is-cubes-mesh/src/planar.rs index fb52562cd..2ba121aca 100644 --- a/all-is-cubes-mesh/src/planar.rs +++ b/all-is-cubes-mesh/src/planar.rs @@ -6,7 +6,7 @@ use all_is_cubes::block::Resolution; use all_is_cubes::cgmath::{ ElementWise as _, EuclideanSpace as _, Matrix4, Point2, Point3, Transform as _, Vector2, }; -use all_is_cubes::math::{Face6, FreeCoordinate, GridCoordinate, Rgba}; +use all_is_cubes::math::{Axis, Face6, FreeCoordinate, GridCoordinate, Rgba}; use crate::texture::{self, TextureCoordinate}; use crate::{BlockVertex, Coloring, IndexVec}; @@ -198,7 +198,7 @@ pub(super) fn push_quad>, Tex: texture::Plane>( }); // Ensure the transformed clamp range is not inverted. - for axis in 0..3 { + for axis in Axis::ALL { all_is_cubes::math::sort_two(&mut clamp_min[axis], &mut clamp_max[axis]); } diff --git a/all-is-cubes-ui/src/vui/layout.rs b/all-is-cubes-ui/src/vui/layout.rs index 4ad614f74..586d4e49b 100644 --- a/all-is-cubes-ui/src/vui/layout.rs +++ b/all-is-cubes-ui/src/vui/layout.rs @@ -2,7 +2,9 @@ use std::rc::Rc; use std::sync::Arc; use all_is_cubes::cgmath::{Vector3, Zero as _}; -use all_is_cubes::math::{Cube, Face6, FaceMap, GridAab, GridCoordinate, GridPoint, GridVector}; +use all_is_cubes::math::{ + Axis, Cube, Face6, FaceMap, GridAab, GridCoordinate, GridPoint, GridVector, +}; use all_is_cubes::space::{Space, SpaceBuilder, SpaceTransaction}; use all_is_cubes::transaction::{self, Merge as _, Transaction as _}; @@ -92,7 +94,7 @@ impl LayoutGrant { ); if enlarge_for_symmetry { - for axis in 0..3 { + for axis in Axis::ALL { if self.gravity[axis] == Align::Center && self.bounds.size()[axis].rem_euclid(2) != sizes[axis].rem_euclid(2) { @@ -105,7 +107,7 @@ impl LayoutGrant { let sizes = sizes.zip(self.bounds.size(), GridCoordinate::min); let mut origin = GridPoint::new(0, 0, 0); - for axis in 0..3 { + for axis in Axis::ALL { let l = self.bounds.lower_bounds()[axis]; let h = self.bounds.upper_bounds()[axis] - sizes[axis]; origin[axis] = match self.gravity[axis] { @@ -309,7 +311,7 @@ impl LayoutTree { let mut bounds = grant.bounds; for child in children { let requirements = child.requirements(); - let axis = direction.axis_number(); + let axis = direction.axis(); let size_on_axis = requirements.minimum[axis]; let available_size = bounds.size()[axis]; if size_on_axis > available_size { @@ -445,10 +447,10 @@ impl Layoutable for LayoutTree { ref children, } => { let mut accumulator = GridVector::zero(); - let stack_axis = direction.axis_number(); + let stack_axis = direction.axis(); for child in children { let child_req = child.requirements(); - for axis in 0..3 { + for axis in Axis::ALL { if axis == stack_axis { accumulator[axis] += child_req.minimum[axis]; } else { diff --git a/all-is-cubes/src/camera.rs b/all-is-cubes/src/camera.rs index d63879bef..f002381f4 100644 --- a/all-is-cubes/src/camera.rs +++ b/all-is-cubes/src/camera.rs @@ -9,7 +9,7 @@ use itertools::Itertools as _; use ordered_float::NotNan; use crate::chunking::OctantMask; -use crate::math::{Aab, FreeCoordinate, GridAab, Rgba}; +use crate::math::{Aab, Axis, FreeCoordinate, GridAab, Rgba}; use crate::raycast::Ray; mod flaws; @@ -503,7 +503,7 @@ pub fn eye_for_look_at( direction: Vector3, ) -> Point3 { let mut space_radius: FreeCoordinate = 0.0; - for axis in 0..3 { + for axis in Axis::ALL { space_radius = space_radius.max(bounds.size()[axis].into()); } bounds.center() + direction.normalize() * space_radius // TODO: allow for camera FoV diff --git a/all-is-cubes/src/content.rs b/all-is-cubes/src/content.rs index 35312efc1..c3a5a5015 100644 --- a/all-is-cubes/src/content.rs +++ b/all-is-cubes/src/content.rs @@ -179,19 +179,14 @@ pub fn make_slab( /// ``` pub fn axes(space: &mut Space) -> Result<(), SetCubeError> { for face in Face6::ALL { - let axis = face.axis_number(); - let axis_color = [ - palette::UNIFORM_LUMINANCE_RED, - palette::UNIFORM_LUMINANCE_GREEN, - palette::UNIFORM_LUMINANCE_BLUE, - ][axis]; + let axis = face.axis(); let direction = face.normal_vector::()[axis]; let raycaster = Raycaster::new([0.5, 0.5, 0.5], face.normal_vector::()) .within(space.bounds()); for step in raycaster { let i = step.cube_ahead().lower_bounds()[axis] * direction; // always positive let (color, display_name): (Rgb, Cow<'static, str>) = if i.rem_euclid(2) == 0 { - (axis_color, i.rem_euclid(10).to_string().into()) + (axis.color(), i.rem_euclid(10).to_string().into()) } else { if direction > 0 { (rgb_const!(1.0, 1.0, 1.0), ["X", "Y", "Z"][axis].into()) @@ -204,7 +199,7 @@ pub fn axes(space: &mut Space) -> Result<(), SetCubeError> { Block::builder() .display_name(display_name) .color(color.with_alpha_one()) - .light_emission(axis_color * 3.0) + .light_emission(axis.color() * 3.0) .build(), )?; } diff --git a/all-is-cubes/src/math.rs b/all-is-cubes/src/math.rs index 43228a499..9c7ee1d53 100644 --- a/all-is-cubes/src/math.rs +++ b/all-is-cubes/src/math.rs @@ -1,7 +1,5 @@ //! Mathematical utilities and decisions. -use std::fmt; - use cgmath::{Point3, Vector3}; use num_traits::identities::Zero; pub use ordered_float::{FloatIsNan, NotNan}; @@ -10,6 +8,8 @@ use crate::util::{ConciseDebug, CustomFormat}; mod aab; pub use aab::*; +mod axis; +pub use axis::*; #[macro_use] mod color; pub use color::*; diff --git a/all-is-cubes/src/math/aab.rs b/all-is-cubes/src/math/aab.rs index 523d32635..0908f2dd5 100644 --- a/all-is-cubes/src/math/aab.rs +++ b/all-is-cubes/src/math/aab.rs @@ -4,7 +4,7 @@ use std::iter::FusedIterator; use cgmath::{EuclideanSpace as _, Point3, Vector3, Zero as _}; -use crate::math::{Face6, FreeCoordinate, Geometry, GridAab, GridCoordinate, LineVertex}; +use crate::math::{Axis, Face6, FreeCoordinate, Geometry, GridAab, GridCoordinate, LineVertex}; /// Axis-Aligned Box data type. /// @@ -175,7 +175,9 @@ impl Aab { /// /// TODO: example + tests pub fn contains(&self, point: Point3) -> bool { - for axis in 0..3 { + // I tried changing this to an Iterator::all() and the asm was longer. + // I tried changing this to be completely unrolled and it was more or less the same. + for axis in Axis::ALL { if !(self.lower_bounds[axis] <= point[axis] && point[axis] <= self.upper_bounds[axis]) { return false; } @@ -187,7 +189,7 @@ impl Aab { /// /// TODO: example + tests pub fn intersects(&self, other: Aab) -> bool { - for axis in 0..3 { + for axis in Axis::ALL { let intersection_min = self.lower_bounds[axis].max(other.lower_bounds[axis]); let intersection_max = self.upper_bounds[axis].min(other.upper_bounds[axis]); match intersection_min.partial_cmp(&intersection_max) { @@ -252,7 +254,7 @@ impl Aab { direction: Vector3, ) -> Vector3 { let mut leading_corner = Vector3::zero(); - for axis in 0..3 { + for axis in Axis::ALL { if direction[axis] >= 0.0 { leading_corner[axis] = self.upper_bounds[axis]; } else { @@ -482,7 +484,7 @@ mod tests { { let leading_corner = aab.leading_corner(direction); - for axis in 0..3 { + for axis in Axis::ALL { // Note that this condition is not true in general, but only if the AAB // contains the origin. assert_eq!(leading_corner[axis].signum(), direction[axis].signum()); diff --git a/all-is-cubes/src/math/axis.rs b/all-is-cubes/src/math/axis.rs new file mode 100644 index 000000000..134c93863 --- /dev/null +++ b/all-is-cubes/src/math/axis.rs @@ -0,0 +1,141 @@ +use core::fmt; + +use crate::content::palette; +use crate::math::Rgb; + +#[cfg(doc)] +use crate::math::Face6; + +/// Enumeration of the axes of three-dimensional space. +/// +/// Can be used to infallibly index 3-component arrays and vectors. +/// +/// See also: +/// +/// * [`Face6`] specifies an axis and a direction on the axis. +#[allow(clippy::exhaustive_enums)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, exhaust::Exhaust)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +// do after tests:#[cfg_attr(feature = "save", derive(serde::Serialize, serde::Deserialize))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Axis { + X = 0, + Y = 1, + Z = 2, +} + +impl Axis { + /// All three axes in the standard order, [X, Y, Z]. + pub const ALL: [Self; 3] = [Self::X, Self::Y, Self::Z]; + + /// Returns a standard color to denote this axis among the three axes. + /// All colors have equal luminance. + /// + /// * X = red + /// * Y = green + /// * Z = blue + pub fn color(&self) -> Rgb { + match self { + Axis::X => palette::UNIFORM_LUMINANCE_RED, + Axis::Y => palette::UNIFORM_LUMINANCE_GREEN, + Axis::Z => palette::UNIFORM_LUMINANCE_BLUE, + } + } + + /// Convert the axis to a number for indexing 3-element arrays. + #[inline] + pub const fn index(self) -> usize { + self as usize + } +} + +/// Format the axis as one of the strings "x", "y", or "z" (lowercase). +impl fmt::LowerHex for Axis { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Axis::X => "x", + Axis::Y => "y", + Axis::Z => "z", + }) + } +} +/// Format the axis as one of the strings "X", "Y", or "Z" (uppercase). +impl fmt::UpperHex for Axis { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Axis::X => "X", + Axis::Y => "Y", + Axis::Z => "Z", + }) + } +} + +impl From for u8 { + #[inline] + fn from(value: Axis) -> Self { + value as u8 + } +} +impl From for usize { + #[inline] + fn from(value: Axis) -> Self { + value as usize + } +} + +mod impl_index_axis { + use super::Axis; + use core::ops; + + impl ops::Index for [T; 3] { + type Output = T; + + fn index(&self, index: Axis) -> &Self::Output { + &self[index as usize] + } + } + impl ops::IndexMut for [T; 3] { + fn index_mut(&mut self, index: Axis) -> &mut Self::Output { + &mut self[index as usize] + } + } + + macro_rules! impl_xyz { + ($($type:tt)*) => { + impl ops::Index for $($type)* { + type Output = T; + + fn index(&self, index: Axis) -> &Self::Output { + match index { + Axis::X => &self.x, + Axis::Y => &self.y, + Axis::Z => &self.z, + } + } + } + impl ops::IndexMut for $($type)* { + fn index_mut(&mut self, index: Axis) -> &mut Self::Output { + match index { + Axis::X => &mut self.x, + Axis::Y => &mut self.y, + Axis::Z => &mut self.z, + } + } + } + }; + } + impl_xyz!(cgmath::Vector3); + impl_xyz!(cgmath::Point3); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn axis_fmt() { + use Axis::*; + assert_eq!(format!("{X:x} {Y:x} {Z:x} {X:X} {Y:X} {Z:X}"), "x y z X Y Z"); + } +} diff --git a/all-is-cubes/src/math/face.rs b/all-is-cubes/src/math/face.rs index d88b30418..042a466a2 100644 --- a/all-is-cubes/src/math/face.rs +++ b/all-is-cubes/src/math/face.rs @@ -1,12 +1,16 @@ //! Axis-aligned unit vectors: the [`Face6`] and [`Face7`] types. //! This module is private but reexported by its parent. -use std::ops::{Index, IndexMut}; +use core::fmt; +use core::ops::{Index, IndexMut}; use cgmath::{BaseNum, Vector3}; pub use ordered_float::{FloatIsNan, NotNan}; -use crate::math::*; +use crate::math::{ + Axis, ConciseDebug, Cube, CustomFormat, FreeCoordinate, Geometry, GridCoordinate, GridPoint, + GridRotation, GridVector, Gridgid, LineVertex, Point3, Zero, +}; /// Identifies a face of a cube or an orthogonal unit vector. /// @@ -90,17 +94,14 @@ impl Face6 { } } - /// Returns which axis this face's normal vector is parallel to, with the numbering - /// X = 0, Y = 1, Z = 2, which matches the indexes used by most arrays. - /// - /// The numeric type is [`usize`] for convenient use in array indexing. + /// Returns which axis this face's normal vector is parallel to. #[inline] #[must_use] - pub const fn axis_number(self) -> usize { + pub const fn axis(self) -> Axis { match self { - Self::NX | Self::PX => 0, - Self::NY | Self::PY => 1, - Self::NZ | Self::PZ => 2, + Self::NX | Self::PX => Axis::X, + Self::NY | Self::PY => Axis::Y, + Self::NZ | Self::PZ => Axis::Z, } } @@ -277,18 +278,16 @@ impl Face7 { } } - /// Returns which axis this face's normal vector is parallel to, with the numbering - /// X = 0, Y = 1, Z = 2, or [`None`] if the face is [`Face7::Within`]. - /// - /// The numeric type is [`usize`] for convenient use in array indexing. + /// Returns which axis this face's normal vector is parallel to, + /// or [`None`] if the face is [`Face7::Within`]. #[inline] #[must_use] - pub const fn axis_number(self) -> Option { + pub const fn axis(self) -> Option { match self { Face7::Within => None, - Face7::NX | Face7::PX => Some(0), - Face7::NY | Face7::PY => Some(1), - Face7::NZ | Face7::PZ => Some(2), + Face7::NX | Face7::PX => Some(Axis::X), + Face7::NY | Face7::PY => Some(Axis::Y), + Face7::NZ | Face7::PZ => Some(Axis::Z), } } diff --git a/all-is-cubes/src/math/grid_aab.rs b/all-is-cubes/src/math/grid_aab.rs index cb320d4ec..61a388d04 100644 --- a/all-is-cubes/src/math/grid_aab.rs +++ b/all-is-cubes/src/math/grid_aab.rs @@ -9,8 +9,8 @@ use cgmath::{EuclideanSpace as _, Point3, Vector3}; use crate::block::Resolution; use crate::math::{ - sort_two, Aab, Cube, Face6, FaceMap, FreeCoordinate, GridCoordinate, GridPoint, GridVector, - Gridgid, + sort_two, Aab, Axis, Cube, Face6, FaceMap, FreeCoordinate, GridCoordinate, GridPoint, + GridVector, Gridgid, }; /// An axis-aligned box with integer coordinates, whose volume is no larger than [`usize::MAX`]. @@ -89,15 +89,15 @@ impl GridAab { // TODO: Test these error cases. // TODO: Replace string error construction with an error enum. - for i in 0..3 { - if sizes[i] < 0 { + for axis in Axis::ALL { + if sizes[axis] < 0 { return Err(GridOverflowError(format!( - "sizes[{}] must be ≥ 0, not {}", - i, sizes[i] + "sizes.{axis:x} must be ≥ 0, not {sa}", + sa = sizes[axis] ))); } - lower_bounds[i].checked_add(sizes[i]).ok_or_else(|| { - GridOverflowError(format!("lower_bounds[{i}] too large for sizes")) + lower_bounds[axis].checked_add(sizes[axis]).ok_or_else(|| { + GridOverflowError(format!("lower_bounds.{axis:x} too large for sizes")) })?; } Self::checked_volume_helper(sizes) @@ -209,7 +209,7 @@ impl GridAab { /// of the `?` operator without which this is even worse. fn checked_volume_helper(sizes: GridVector) -> Result { let mut volume: usize = 1; - for i in 0..3 { + for i in Axis::ALL { volume = volume .checked_mul(usize::try_from(sizes[i]).map_err(|_| ())?) .ok_or(())?; @@ -336,26 +336,24 @@ impl GridAab { /// The range of X coordinates for unit cubes within the box. #[inline] pub fn x_range(&self) -> Range { - self.axis_range(0) + self.axis_range(Axis::X) } /// The range of Y coordinates for unit cubes within the box. #[inline] pub fn y_range(&self) -> Range { - self.axis_range(1) + self.axis_range(Axis::Y) } /// The range of Z coordinates for unit cubes within the box. #[inline] pub fn z_range(&self) -> Range { - self.axis_range(2) + self.axis_range(Axis::Z) } /// The range of coordinates for cubes within the box along the given axis. - /// - /// Panics if `axis >= 3`. #[inline] - pub fn axis_range(&self, axis: usize) -> Range { + pub fn axis_range(&self, axis: Axis) -> Range { (self.lower_bounds()[axis])..(self.upper_bounds()[axis]) } @@ -424,7 +422,7 @@ impl GridAab { pub fn contains_box(&self, other: GridAab) -> bool { let self_upper = self.upper_bounds(); let other_upper = other.upper_bounds(); - for axis in 0..3 { + for axis in Axis::ALL { if other.lower_bounds[axis] < self.lower_bounds[axis] || other_upper[axis] > self_upper[axis] { @@ -459,7 +457,7 @@ impl GridAab { let upper = self .upper_bounds() .zip(other.upper_bounds(), GridCoordinate::min); - for axis in 0..3 { + for axis in Axis::ALL { if upper[axis] <= lower[axis] { return None; } @@ -563,7 +561,7 @@ impl GridAab { let mut p2 = transform.transform_point(self.upper_bounds()); // Swap coordinates in case of rotation or reflection. - for axis in 0..3 { + for axis in Axis::ALL { sort_two(&mut p1[axis], &mut p2[axis]); } Some(Self::from_lower_upper(p1, p2)) @@ -707,7 +705,7 @@ impl GridAab { /// ``` #[inline] pub fn abut(self, face: Face6, thickness: GridCoordinate) -> Result { - let axis = face.axis_number(); + let axis = face.axis(); let mut size = self.size(); size[axis] = thickness.max(-size[axis]).abs(); diff --git a/all-is-cubes/src/math/rotation.rs b/all-is-cubes/src/math/rotation.rs index a6359f650..fe6c5ea54 100644 --- a/all-is-cubes/src/math/rotation.rs +++ b/all-is-cubes/src/math/rotation.rs @@ -346,7 +346,7 @@ impl GridRotation { pub fn transform(self, face: Face6) -> Face6 { // TODO: there ought to be a much cleaner way to express this // ... and it should be a const fn, too - let p = self.to_basis()[face.axis_number()]; + let p = self.to_basis()[face.axis()]; if face.is_negative() { p.opposite() } else { @@ -591,8 +591,8 @@ mod tests { } None => { assert!( - up_face.axis_number() == from_face.axis_number() - || up_face.axis_number() == to_face.axis_number(), + up_face.axis() == from_face.axis() + || up_face.axis() == to_face.axis(), "returned None incorrectly: {info:?}" ); } diff --git a/all-is-cubes/src/physics/body.rs b/all-is-cubes/src/physics/body.rs index 8e97ecd2a..8f0eb55ed 100644 --- a/all-is-cubes/src/physics/body.rs +++ b/all-is-cubes/src/physics/body.rs @@ -253,7 +253,7 @@ impl Body { let axis = collision .contact .normal() - .axis_number() + .axis() .expect("Face7::Within collisions should not reach here"); // Advance however much straight-line distance is available. // But a little bit back from that, to avoid floating point error pushing us diff --git a/all-is-cubes/src/physics/collision.rs b/all-is-cubes/src/physics/collision.rs index 1280dd0d7..22431f5b8 100644 --- a/all-is-cubes/src/physics/collision.rs +++ b/all-is-cubes/src/physics/collision.rs @@ -729,7 +729,7 @@ mod tests { let step = aab_raycast(moving_aab, ray, false) .nth(rng.gen_range(1..10)) .unwrap(); - let axis = step.face().axis_number().expect("should have an axis"); + let axis = step.face().axis().expect("should have an axis"); let segment = ray.scale_direction(step.t_distance()); // TODO: this should be a function? Should aab_raycast return a special step type with these features? let unnudged_aab = moving_aab.translate(segment.unit_endpoint().to_vec()); let face_to_nudge: Face6 = Face6::try_from(step.face().opposite()).unwrap(); diff --git a/all-is-cubes/src/raycast.rs b/all-is-cubes/src/raycast.rs index fb85c1e9d..4443ffc66 100644 --- a/all-is-cubes/src/raycast.rs +++ b/all-is-cubes/src/raycast.rs @@ -8,7 +8,8 @@ use cgmath::{EuclideanSpace as _, InnerSpace as _, Point3, Vector3, Zero as _}; use crate::math::{ - Cube, CubeFace, Face7, FreeCoordinate, Geometry, GridAab, GridCoordinate, GridPoint, LineVertex, + Axis, Cube, CubeFace, Face7, FreeCoordinate, Geometry, GridAab, GridCoordinate, GridPoint, + LineVertex, }; /// A ray; a half-infinite line segment (sometimes used as finite by the length of the @@ -321,7 +322,7 @@ impl Raycaster { // Save t position before we update it. // We could back-compute this instead as - // let axis = self.last_face.axis_number().unwrap(); + // let axis = self.last_face.axis().unwrap(); // self.t_max[axis] - self.t_delta[axis] // but that seems an excessive computation to save a field. self.last_t_distance = self.t_max[axis]; @@ -365,7 +366,7 @@ impl Raycaster { Some(bound_v) => { let mut oob_enter = false; let mut oob_exit = false; - for axis in 0..3 { + for axis in Axis::ALL { let oob_low = self.cube[axis] < bound_v[axis].start; let oob_high = self.cube[axis] >= bound_v[axis].end; if self.step[axis] == 0 { @@ -400,7 +401,7 @@ impl Raycaster { // intersect with. (Strictly speaking, this could be combined with the next // loop, but it seems more elegant to have a well-defined point.) let mut plane_origin = bounds.lower_bounds(); - for axis in 0..3 { + for axis in Axis::ALL { if self.step[axis] < 0 { // Iff the ray is going negatively, then we must use the upper bound // for the plane origin in this axis. Otherwise, either it doesn't @@ -411,7 +412,7 @@ impl Raycaster { // Perform intersections. let mut max_t: FreeCoordinate = 0.0; - for axis in 0..3 { + for axis in Axis::ALL { let mut plane_normal = Vector3::zero(); let direction = self.step[axis]; if direction == 0 { @@ -596,13 +597,13 @@ impl RaycastStep { /// assert_eq!(next(), Point3::new(2.0, 0.5, 0.5)); /// ``` pub fn intersection_point(&self, ray: Ray) -> Point3 { - let current_face_axis = self.cube_face.face.axis_number(); + let current_face_axis = self.cube_face.face.axis(); if current_face_axis.is_none() { ray.origin } else { let mut intersection_point = self.cube_face.cube.lower_bounds().map(FreeCoordinate::from); - for axis in 0..3 { + for axis in Axis::ALL { let step_direction = signum_101(ray.direction[axis]); if Some(axis) == current_face_axis { // This is the plane we just hit. @@ -1156,7 +1157,7 @@ mod tests { let point = step.intersection_point(ray); let mut surfaces = 0; let mut interiors = 0; - for axis in 0..3 { + for axis in Axis::ALL { if point[axis] == 0.0 || point[axis] == 1.0 { surfaces += 1; } else if point[axis] > 0.0 && point[axis] < 1.0 { diff --git a/test-renderers/src/test_cases.rs b/test-renderers/src/test_cases.rs index 4506ddccd..73fb5e772 100644 --- a/test-renderers/src/test_cases.rs +++ b/test-renderers/src/test_cases.rs @@ -947,7 +947,7 @@ async fn fog_test_universe() -> Arc { .color(rgba_const!(1.0, 0.05, 0.05, 1.0)) .light_emission(rgb_const!(40.0, 0.05, 0.05)) .build(); - for z in bounds.axis_range(2).step_by(2) { + for z in bounds.z_range().step_by(2) { let x = (z * 19i32).rem_euclid(bounds.size().x) + bounds.lower_bounds().x; space