diff --git a/CHANGELOG.md b/CHANGELOG.md index fa0c0d3..5592005 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added + +* `unstable-v3` feature flag and related `v3` module: an exploration of how could look like the next major version + ## [2.0.0] - 2023-08-03 diff --git a/Cargo.toml b/Cargo.toml index d173f13..744cf89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ all-features = true [features] default = ["std"] std = ["glam/std", "bvh-arena?/std"] +unstable-v3 = [] [dependencies] # Public @@ -23,6 +24,7 @@ bvh-arena = { version = "1.1.3", default-features = false, optional = true } # Private glam = { version = "0.24.1", default-features = false, features = ["libm"] } +sealed = "0.5.0" smallvec = { version = "1.11.0", default-features = false } [dev-dependencies] diff --git a/src/lib.rs b/src/lib.rs index 7b9af62..554953f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,9 +55,16 @@ //! * `std` (enabled by default) Allow to use rust the standard library (need to be disabled for `no_std` apps) //! * `bvh-arena` Integration with [bvh-arena](https://crates.io/crates/bvh-arena) bounding volumes //! +//! +//! ## Unstable feature flags +//! +//! The following features may receive breaking changes or be removed in a patch release. +//! +//! * `unstable-v3` `v3` module, an exploration of what could be the next major version of the API +//! mod v2; -#[cfg(test)] -mod v3; +#[cfg(feature = "unstable-v3")] +pub mod v3; pub use v2::*; diff --git a/src/v3/math.rs b/src/v3/math.rs index 6ffb845..99efeda 100644 --- a/src/v3/math.rs +++ b/src/v3/math.rs @@ -1,7 +1,7 @@ use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; #[derive(Debug, Copy, Clone, Default, PartialEq)] -pub(crate) struct Vec2 { +pub struct Vec2 { pub(super) x: f32, pub(super) y: f32, } @@ -11,23 +11,31 @@ impl Vec2 { pub const X: Self = Self::new(1.0, 0.0); pub const Y: Self = Self::new(0.0, 1.0); + #[must_use] pub const fn new(x: f32, y: f32) -> Self { Self { x, y } } - pub fn dot(self, other: Self) -> f32 { + #[must_use] + pub(super) fn dot(self, other: Self) -> f32 { (self.x * other.x) + (self.y * other.y) } - pub fn magnitude_squared(self) -> f32 { + #[must_use] + #[cfg(test)] + pub(super) fn magnitude_squared(self) -> f32 { self.x * self.x + self.y * self.y } - pub fn magnitude(self) -> f32 { + #[must_use] + #[cfg(test)] + pub(super) fn magnitude(self) -> f32 { self.magnitude_squared().sqrt() } - pub fn normalize(self) -> Option { + #[must_use] + #[cfg(test)] + pub(super) fn normalize(self) -> Option { let normal = self / self.magnitude(); if !normal.x.is_finite() { return None; @@ -35,7 +43,9 @@ impl Vec2 { Some(normal) } - pub fn perp(self) -> Self { + #[must_use] + #[cfg(test)] + pub(super) fn perp(self) -> Self { Self { x: -self.y, y: self.x, diff --git a/src/v3/mod.rs b/src/v3/mod.rs index a651cc9..092703c 100644 --- a/src/v3/mod.rs +++ b/src/v3/mod.rs @@ -1,12 +1,16 @@ +#![allow(missing_docs)] + mod math; mod range; mod shapes; -use math::Vec2; +pub use math::Vec2; use range::Range; -use shapes::Point; +use sealed::sealed; +pub use shapes::{Aabb, Point}; -trait SatShape { +#[sealed] +pub trait Shape { type AxisIter: Iterator; fn axes(&self) -> Self::AxisIter; @@ -14,8 +18,9 @@ trait SatShape { } #[derive(Debug, Clone, PartialEq)] -struct Contact { - point: Point, +#[non_exhaustive] +pub struct Contact { + pub point: Point, } /// Given ranges of projected shapes, @@ -47,7 +52,7 @@ fn cast_projection(mut source: Range, mut vector: f32, mut target: Range) -> Opt }) } -fn cast_ray(origin: Point, vector: Vec2, target: &impl SatShape) -> Option { +pub fn cast_ray(origin: Point, vector: Vec2, target: &impl Shape) -> Option { let mut max_t1 = f32::MIN; let mut min_t2 = f32::MAX; for axis in target.axes() { @@ -78,61 +83,61 @@ mod tests { #[case( Vec2::ZERO, Vec2::X, - Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(1.9, 0.0)), + Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(1.9, 0.0)), Vec2::new(0.9, 0.0) )] #[case( Vec2::ZERO, -Vec2::X, - Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(-1.9, 0.0)), + Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(-1.9, 0.0)), Vec2::new(-0.9, 0.0) )] #[case( Vec2::X, Vec2::X, - Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(2.9, 0.0)), + Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(2.9, 0.0)), Vec2::new(1.9, 0.0) )] #[case( Vec2::ZERO, Vec2::X * 2.0, - Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(2.9, 0.0)), + Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(2.9, 0.0)), Vec2::new(1.9, 0.0) )] #[case( Vec2::ZERO, Vec2::Y, - Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(0.0, 1.9)), + Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(0.0, 1.9)), Vec2::new(0.0, 0.9) )] #[case( Vec2::Y, Vec2::Y, - Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(0.0, 2.9)), + Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(0.0, 2.9)), Vec2::new(0.0, 1.9) )] #[case( Vec2::ZERO, Vec2::Y * 2.0, - Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(0.0, 2.9)), + Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(0.0, 2.9)), Vec2::new(0.0, 1.9) )] #[case( Vec2::ZERO, Vec2::new(1.0, 1.0), - Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(1.9, 1.9)), + Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(1.9, 1.9)), Vec2::new(0.9, 0.9), )] #[case( Vec2::ZERO, Vec2::new(1.0, 1.0), - Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(0.5, 1.9)), + Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(0.5, 1.9)), Vec2::new(0.9, 0.9), )] fn ray_cast_should_find_contact_point( #[case] origin: impl Into, #[case] vector: Vec2, - #[case] target: impl SatShape, + #[case] target: impl Shape, #[case] expected_point: impl Into, ) { let point = cast_ray(origin.into(), vector, &target).unwrap().point; @@ -140,17 +145,17 @@ mod tests { } #[rstest] - #[case(Vec2::ZERO, Vec2::X, Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(2.1, 0.0)))] - #[case(Vec2::ZERO, Vec2::X, Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(-2.1, 0.0)))] - #[case(Vec2::ZERO, Vec2::X, Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::ZERO))] - #[case(Vec2::ZERO, Vec2::X, Aabb::from_size(Vec2::new(1.0, 1.0)).with_position(Vec2::ZERO))] - #[case(-Vec2::X, Vec2::X, Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(1.1, 0.0)))] - #[case(Vec2::ZERO, Vec2::X, Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(1.9, 5.0)))] - #[case(Vec2::ZERO, Vec2::X, Aabb::from_size(Vec2::new(2.0, 2.0)).with_position(Vec2::new(1.9, -5.0)))] + #[case(Vec2::ZERO, Vec2::X, Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(2.1, 0.0)))] + #[case(Vec2::ZERO, Vec2::X, Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(-2.1, 0.0)))] + #[case(Vec2::ZERO, Vec2::X, Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::ZERO))] + #[case(Vec2::ZERO, Vec2::X, Aabb::from_size(Vec2::new(1.0, 1.0)).with_center_at(Vec2::ZERO))] + #[case(-Vec2::X, Vec2::X, Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(1.1, 0.0)))] + #[case(Vec2::ZERO, Vec2::X, Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(1.9, 5.0)))] + #[case(Vec2::ZERO, Vec2::X, Aabb::from_size(Vec2::new(2.0, 2.0)).with_center_at(Vec2::new(1.9, -5.0)))] fn ray_cast_should_return_none_when_there_is_no_hit( #[case] origin: impl Into, #[case] vector: Vec2, - #[case] target: impl SatShape, + #[case] target: impl Shape, ) { let result = cast_ray(origin.into(), vector, &target); assert_eq!(result, None); diff --git a/src/v3/range.rs b/src/v3/range.rs index 5805652..72e9e13 100644 --- a/src/v3/range.rs +++ b/src/v3/range.rs @@ -1,5 +1,5 @@ #[derive(Debug, Copy, Clone, PartialEq)] -pub(super) struct Range { +pub struct Range { pub(super) min: f32, pub(super) max: f32, } @@ -14,6 +14,7 @@ impl Range { self.min <= other.max && self.max >= other.min } + #[cfg(test)] pub(super) fn contains(self, point: f32) -> bool { point >= self.min && point <= self.max } diff --git a/src/v3/shapes.rs b/src/v3/shapes.rs index 65c46a6..e243f9a 100644 --- a/src/v3/shapes.rs +++ b/src/v3/shapes.rs @@ -1,10 +1,12 @@ mod point { use core::ops::Add; - use crate::v3::{math::Vec2, Range, SatShape}; + use sealed::sealed; + + use crate::v3::{math::Vec2, Range, Shape, __seal_shape}; #[derive(Debug, Copy, Clone, PartialEq)] - pub(crate) struct Point(Vec2); + pub struct Point(Vec2); impl From for Point { fn from(value: Vec2) -> Self { @@ -18,7 +20,8 @@ mod point { } } - impl SatShape for Point { + #[sealed] + impl Shape for Point { type AxisIter = core::iter::Empty; fn axes(&self) -> Self::AxisIter { core::iter::empty() @@ -82,28 +85,33 @@ mod point { } mod aabb { - use crate::v3::{math::Vec2, Range, SatShape}; + use sealed::sealed; + + use crate::v3::{math::Vec2, Range, Shape, __seal_shape}; - pub(crate) struct Aabb { + pub struct Aabb { center: Vec2, half_size: Vec2, } impl Aabb { - pub(crate) fn from_size(size: Vec2) -> Self { + #[must_use] + pub fn from_size(size: Vec2) -> Self { Self { center: Vec2::default(), half_size: size / 2.0, } } - pub(crate) fn with_position(mut self, center: Vec2) -> Self { + #[must_use] + pub fn with_center_at(mut self, center: Vec2) -> Self { self.center = center; self } } - impl SatShape for Aabb { + #[sealed] + impl Shape for Aabb { type AxisIter = core::array::IntoIter; fn axes(&self) -> Self::AxisIter { @@ -138,10 +146,10 @@ mod aabb { #[case(Aabb::from_size(Vec2::new(3.0, 4.0)), Vec2::new(1.5, 2.0), -6.25, 6.25)] #[case(Aabb::from_size(Vec2::new(3.0, 4.0)), Vec2::new(1.5, -2.0), -6.25, 6.25)] #[case(Aabb::from_size(Vec2::new(3.0, 4.0)), Vec2::new(-1.5, 2.0), -6.25, 6.25)] - #[case(Aabb::from_size(Vec2::new(3.0, 4.0)).with_position(Vec2::new(0.0, 0.0)), Vec2::new(1.0, 0.0), -1.5, 1.5)] - #[case(Aabb::from_size(Vec2::new(3.0, 4.0)).with_position(Vec2::new(1.0, 0.0)), Vec2::new(1.0, 0.0), -0.5, 2.5)] - #[case(Aabb::from_size(Vec2::new(3.0, 4.0)).with_position(Vec2::new(0.0, 1.0)), Vec2::new(1.0, 0.0), -1.5, 1.5)] - #[case(Aabb::from_size(Vec2::new(3.0, 4.0)).with_position(Vec2::new(0.0, 1.0)), Vec2::new(0.0, 1.0), -1.0, 3.0)] + #[case(Aabb::from_size(Vec2::new(3.0, 4.0)).with_center_at(Vec2::new(0.0, 0.0)), Vec2::new(1.0, 0.0), -1.5, 1.5)] + #[case(Aabb::from_size(Vec2::new(3.0, 4.0)).with_center_at(Vec2::new(1.0, 0.0)), Vec2::new(1.0, 0.0), -0.5, 2.5)] + #[case(Aabb::from_size(Vec2::new(3.0, 4.0)).with_center_at(Vec2::new(0.0, 1.0)), Vec2::new(1.0, 0.0), -1.5, 1.5)] + #[case(Aabb::from_size(Vec2::new(3.0, 4.0)).with_center_at(Vec2::new(0.0, 1.0)), Vec2::new(0.0, 1.0), -1.0, 3.0)] fn test_axis_project( #[case] shape: Aabb, #[case] axis: Vec2, @@ -157,7 +165,7 @@ mod aabb { fn test_polygon_axes( #[values( Aabb::from_size(Vec2::ZERO), - Aabb::from_size(Vec2::new(2.0, 3.0)).with_position(Vec2::new(4.0, 5.0)) + Aabb::from_size(Vec2::new(2.0, 3.0)).with_center_at(Vec2::new(4.0, 5.0)) )] shape: Aabb, ) { @@ -169,5 +177,5 @@ mod aabb { } } -pub(super) use aabb::Aabb; -pub(super) use point::Point; +pub use aabb::Aabb; +pub use point::Point;