From 0152a0f226ad0a81d985a9c7d69bd3818fbb6d6d Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Wed, 22 Nov 2023 19:56:36 +0300 Subject: [PATCH 01/82] Added Nurbs spline --- crates/bevy_math/src/cubic_splines.rs | 95 ++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 9f4fc365f7799..2c449c1dc4320 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -1,13 +1,13 @@ //! Provides types for building cubic splines for rendering curves and use with animation easing. -use glam::{Vec2, Vec3, Vec3A}; - use std::{ fmt::Debug, iter::Sum, ops::{Add, Mul, Sub}, }; +use glam::{Vec2, Vec3, Vec3A}; + /// A point in space of any dimension that supports the math ops needed for cubic spline /// interpolation. pub trait Point: @@ -286,6 +286,97 @@ impl CubicGenerator

for CubicBSpline

{ } } +/// A Non-Uniform Rational B-Spline +pub struct CubicNurbs { + control_points: Vec

, + weights: Vec, + knot_vector: Vec, +} +impl CubicNurbs

{ + /// Generates a Non-Uniform Rational B-Spline. + /// + /// If provided, weights vector must have the same amount of items as the control points vector + /// + /// If provided, the knot vector must have n + 4 elements, where n is the amoutn of control + /// points + pub fn new( + control_points: impl Into>, + weights: Option>>, + knot_vector: Option>>, + ) -> Self { + let control_points: Vec

= control_points.into(); + let control_points_len = control_points.len(); + + let weights = weights + .map(Into::into) + .unwrap_or_else(|| vec![1.0; control_points_len]); + + let knot_vector: Vec = knot_vector.map(Into::into).unwrap_or_else(|| { + let start_end_knots_multiplicity = 4; + // This iterator chain generates a default knot vector such that the curve passes through end + // and start points. It has start and end knots of multiplicity equal to the order of a + // curve (cubic curve has order of 4) with a step of 1 for knots inbetween. + std::iter::repeat(0.0) + .take(start_end_knots_multiplicity) + .chain((1..(control_points_len - 1)).map(|int_val| int_val as f32)) + .chain( + std::iter::repeat(control_points_len as f32).take(start_end_knots_multiplicity), + ) + .collect() + }); + + let knot_vector_required_length = control_points_len + 4; // Number of control points + + // curve order + + // Check the knot vector length + if knot_vector.len() != knot_vector_required_length { + // TODO: change to result + panic!( + "Invalid knot vector length: {knot_vector_required_length} expected, {} provided", + knot_vector.len() + ); + } + + // Check the knot vector for being nondescending (previous elements is less than or equal + // to the next) + { + let mut is_nondescending = true; + + for window_slice in knot_vector.windows(2) { + let prev = window_slice[0]; + let next = window_slice[1]; + is_nondescending = is_nondescending && (prev <= next); + } + + if !is_nondescending { + // TODO: change to result + panic!("Invalid knot vector, elements are not nondescending"); + } + } + + // Check the weights vector length + if weights.len() != control_points_len { + // TODO: change to result + panic!( + "Invalid weights vector length: {control_points_len} expected, {} provided", + weights.len() + ); + } + + Self { + control_points, + weights, + knot_vector, + } + } +} +impl CubicGenerator

for CubicNurbs

{ + #[inline] + fn to_curve(&self) -> CubicCurve

{ + todo!() + } +} + /// Implement this on cubic splines that can generate a curve from their spline parameters. pub trait CubicGenerator { /// Build a [`CubicCurve`] by computing the interpolation coefficients for each curve segment. From 344c9420d94cea6c70ea5ce5eda3994037d0958c Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Fri, 24 Nov 2023 20:53:53 +0300 Subject: [PATCH 02/82] Helper methods for generating various knot vectors --- crates/bevy_math/src/cubic_splines.rs | 32 ++++++++++++++++----------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 2c449c1dc4320..eea36d0fcbeb5 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -311,19 +311,9 @@ impl CubicNurbs

{ .map(Into::into) .unwrap_or_else(|| vec![1.0; control_points_len]); - let knot_vector: Vec = knot_vector.map(Into::into).unwrap_or_else(|| { - let start_end_knots_multiplicity = 4; - // This iterator chain generates a default knot vector such that the curve passes through end - // and start points. It has start and end knots of multiplicity equal to the order of a - // curve (cubic curve has order of 4) with a step of 1 for knots inbetween. - std::iter::repeat(0.0) - .take(start_end_knots_multiplicity) - .chain((1..(control_points_len - 1)).map(|int_val| int_val as f32)) - .chain( - std::iter::repeat(control_points_len as f32).take(start_end_knots_multiplicity), - ) - .collect() - }); + let knot_vector: Vec = knot_vector + .map(Into::into) + .unwrap_or_else(|| Self::open_uniform_knot_vector(control_points_len)); let knot_vector_required_length = control_points_len + 4; // Number of control points + // curve order @@ -369,6 +359,22 @@ impl CubicNurbs

{ knot_vector, } } + + /// Generates a uniform knot vector that will generate the same curve as [`CubicBSpline`] + pub fn uniform_knot_vector(control_points: usize) -> Vec { + let length = control_points + 4; + (0..length).map(|v| v as f32).collect() + } + + /// Generates an open uniform knot vector, which makes the ends of the curve meet the end and + /// start control points + pub fn open_uniform_knot_vector(control_points: usize) -> Vec { + std::iter::repeat(0.0) + .take(4) + .chain((1..(control_points - 1)).map(|v| v as f32)) + .chain(std::iter::repeat(control_points as f32).take(4)) + .collect() + } } impl CubicGenerator

for CubicNurbs

{ #[inline] From 70a13c52a2bd28bcad2ba4eefd149e960f164dbd Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 25 Nov 2023 17:41:43 +0300 Subject: [PATCH 03/82] Added IntoIterator and Extend implementations for CubicCurve --- crates/bevy_math/src/cubic_splines.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index eea36d0fcbeb5..a2b0e80638ac2 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -643,6 +643,22 @@ impl CubicCurve

{ } } +impl Extend> for CubicCurve

{ + fn extend>>(&mut self, iter: T) { + self.segments.extend(iter) + } +} + +impl IntoIterator for CubicCurve

{ + type IntoIter = > as IntoIterator>::IntoIter; + + type Item = CubicSegment

; + + fn into_iter(self) -> Self::IntoIter { + self.segments.into_iter() + } +} + #[cfg(test)] mod tests { use glam::{vec2, Vec2}; From 19e6772ca48bcdfd1ac752863a727b460abb1df9 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 25 Nov 2023 18:15:49 +0300 Subject: [PATCH 04/82] Added push_segment method --- crates/bevy_math/src/cubic_splines.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index a2b0e80638ac2..ccf9e4fd82692 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -616,6 +616,12 @@ impl CubicCurve

{ self.iter_samples(subdivisions, Self::acceleration) } + #[inline] + /// Adds a segment to the curve + pub fn push_segment(&mut self, segment: CubicSegment

) { + self.segments.push(segment) + } + /// Returns the [`CubicSegment`] and local `t` value given a spline's global `t` value. #[inline] fn segment(&self, t: f32) -> (&CubicSegment

, f32) { From 004a72cc67a24e059f24139befa9f820d856f8c1 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 25 Nov 2023 18:23:08 +0300 Subject: [PATCH 05/82] Added linear spline --- crates/bevy_math/src/cubic_splines.rs | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index ccf9e4fd82692..9ab40cca42090 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -383,6 +383,36 @@ impl CubicGenerator

for CubicNurbs

{ } } +/// A spline interpolated linearly across nearest 2 points. +pub struct LinearSpline { + points: Vec

, +} +impl LinearSpline

{ + /// Create a new linear spline + pub fn new(points: impl Into>) -> Self { + Self { + points: points.into(), + } + } +} +impl CubicGenerator

for LinearSpline

{ + #[inline] + fn to_curve(&self) -> CubicCurve

{ + let segments = self + .points + .windows(2) + .map(|points| { + let a = points[0]; + let b = points[1]; + CubicSegment { + coeff: [a, b - a, P::default(), P::default()], + } + }) + .collect(); + CubicCurve { segments } + } +} + /// Implement this on cubic splines that can generate a curve from their spline parameters. pub trait CubicGenerator { /// Build a [`CubicCurve`] by computing the interpolation coefficients for each curve segment. From ae6319639fe3730b79652b5ece239aa4b52d1c3c Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 10:33:24 +0300 Subject: [PATCH 06/82] Make Point trait impl a blanket impl --- crates/bevy_math/src/cubic_splines.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 9ab40cca42090..9e5df0e49fc49 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -23,10 +23,20 @@ pub trait Point: + Copy { } -impl Point for Vec3 {} -impl Point for Vec3A {} -impl Point for Vec2 {} -impl Point for f32 {} + +impl Point for T where + T: Mul + + Add + + Sub + + Add + + Sum + + Default + + Debug + + Clone + + PartialEq + + Copy +{ +} /// A spline composed of a single cubic Bezier curve. /// From 22542c1a425ba7a754896a2e56de0f12f353bdb6 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 10:33:45 +0300 Subject: [PATCH 07/82] Removed unused imports --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 9e5df0e49fc49..b6224dc1cc84f 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -6,7 +6,7 @@ use std::{ ops::{Add, Mul, Sub}, }; -use glam::{Vec2, Vec3, Vec3A}; +use glam::Vec2; /// A point in space of any dimension that supports the math ops needed for cubic spline /// interpolation. From 7972a724c4a643a80c979b9dfe496931592671e6 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 10:46:06 +0300 Subject: [PATCH 08/82] Moved some math to helper associated functions --- crates/bevy_math/src/cubic_splines.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index b6224dc1cc84f..1b8ebb25d74b7 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -325,14 +325,13 @@ impl CubicNurbs

{ .map(Into::into) .unwrap_or_else(|| Self::open_uniform_knot_vector(control_points_len)); - let knot_vector_required_length = control_points_len + 4; // Number of control points + - // curve order + let knot_vector_expected_length = Self::knot_vector_length(control_points_len); // Check the knot vector length - if knot_vector.len() != knot_vector_required_length { + if knot_vector.len() != knot_vector_expected_length { // TODO: change to result panic!( - "Invalid knot vector length: {knot_vector_required_length} expected, {} provided", + "Invalid knot vector length: {knot_vector_expected_length} expected, {} provided", knot_vector.len() ); } @@ -385,6 +384,16 @@ impl CubicNurbs

{ .chain(std::iter::repeat(control_points as f32).take(4)) .collect() } + + #[inline(always)] + const fn knot_vector_length(control_points_len: usize) -> usize { + control_points_len + 4 + } + + /// Based on + fn generate_matrix(knot_vector_segment: &[f32; 6]) -> [[f32; 4]; 4] { + todo!() + } } impl CubicGenerator

for CubicNurbs

{ #[inline] From 60845568b5e3aeafffe5e36c1c8e8b53930b0438 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 10:48:29 +0300 Subject: [PATCH 09/82] Fixed open uniform knot vector generation function --- crates/bevy_math/src/cubic_splines.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 1b8ebb25d74b7..2a980cfb908ee 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -378,10 +378,11 @@ impl CubicNurbs

{ /// Generates an open uniform knot vector, which makes the ends of the curve meet the end and /// start control points pub fn open_uniform_knot_vector(control_points: usize) -> Vec { + let last_knots_value = control_points - 3; std::iter::repeat(0.0) .take(4) - .chain((1..(control_points - 1)).map(|v| v as f32)) - .chain(std::iter::repeat(control_points as f32).take(4)) + .chain((1..last_knots_value).map(|v| v as f32)) + .chain(std::iter::repeat(last_knots_value as f32).take(4)) .collect() } From d738eede2ea984f33fd661e0b6afe5ee93b2476e Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 10:59:21 +0300 Subject: [PATCH 10/82] Implementation for basis matrix generation --- crates/bevy_math/src/cubic_splines.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 2a980cfb908ee..d130194b549fa 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -392,8 +392,24 @@ impl CubicNurbs

{ } /// Based on - fn generate_matrix(knot_vector_segment: &[f32; 6]) -> [[f32; 4]; 4] { - todo!() + fn generate_matrix(knot_vector_segment: &[f32; 8]) -> [[f32; 4]; 4] { + let t = knot_vector_segment; + let i = 3; + let m00 = (t[i + 1] - t[i]).powi(2) / ((t[i + 1] - t[i - 1]) * (t[i + 1] - t[i - 2])); + let m02 = (t[i] - t[i - 1]).powi(2) / ((t[i + 2] - t[i - 1]) * (t[i + 1] - t[i - 1])); + let m12 = (3.0 * (t[i + 1] - t[i]) * (t[i] - t[i - 1])) + / ((t[i + 2] - t[i - 1]) * (t[i + 1] - t[i - 1])); + let m22 = 3.0 * (t[i + 1] - t[i]).powi(2) / ((t[i + 2] - t[i - 1]) * (t[i + 1] - t[i - 1])); + let m33 = (t[i + 1] - t[i]).powi(2) / ((t[i + 3] - t[i]) * (t[i + 2] - t[i])); + let m32 = -m22 / 3.0 + - m33 + - (t[i + 1] - t[i]).powi(2) / ((t[i + 2] - t[i]) * (t[i + 2] - t[i - 1])); + [ + [m00, 1.0 - m00 - m02, m02, 0.0], + [-3.0 * m00, 3.0 * m00 - m12, m12, 0.0], + [3.0 * m00, -3.0 * m00 - m22, m22, 0.0], + [-m00, m00 - m32 - m33, m32, m33], + ] } } impl CubicGenerator

for CubicNurbs

{ From 624684879c8737626e7784a0375a68a892cf0285 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 11:08:35 +0300 Subject: [PATCH 11/82] Implemented cubci generator for nurbs --- crates/bevy_math/src/cubic_splines.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index d130194b549fa..dbccb9011f12b 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -415,7 +415,21 @@ impl CubicNurbs

{ impl CubicGenerator

for CubicNurbs

{ #[inline] fn to_curve(&self) -> CubicCurve

{ - todo!() + let segments = self + .control_points + .windows(4) + .enumerate() + .map(|(i, points)| { + // Unwrap will not fail because the slice take nis of length 8 + let knot_vector_segment = (&self.knot_vector[(i * 3)..(i * 3 + 8)]) + .try_into() + .unwrap(); + let matrix = Self::generate_matrix(knot_vector_segment); + // Unwrap will not fail because the windows slice is of length 4 + CubicCurve::coefficients(points.try_into().unwrap(), 1.0, matrix) + }) + .collect(); + CubicCurve { segments } } } From dec3be59fb5d7b35f096a741a9ac9be0c6c000b6 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 11:26:50 +0300 Subject: [PATCH 12/82] Weight normalization and application --- crates/bevy_math/src/cubic_splines.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index dbccb9011f12b..a25dbf00c36c7 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -299,7 +299,6 @@ impl CubicGenerator

for CubicBSpline

{ /// A Non-Uniform Rational B-Spline pub struct CubicNurbs { control_points: Vec

, - weights: Vec, knot_vector: Vec, } impl CubicNurbs

{ @@ -314,10 +313,10 @@ impl CubicNurbs

{ weights: Option>>, knot_vector: Option>>, ) -> Self { - let control_points: Vec

= control_points.into(); + let mut control_points: Vec

= control_points.into(); let control_points_len = control_points.len(); - let weights = weights + let mut weights = weights .map(Into::into) .unwrap_or_else(|| vec![1.0; control_points_len]); @@ -362,9 +361,15 @@ impl CubicNurbs

{ ); } + Self::normalize_weights(&mut weights); + + control_points + .iter_mut() + .zip(weights) + .for_each(|(p, w)| *p = *p * w); + Self { control_points, - weights, knot_vector, } } @@ -411,6 +416,19 @@ impl CubicNurbs

{ [-m00, m00 - m32 - m33, m32, m33], ] } + + fn normalize_weights(weights: &mut [f32]) { + let mut max = weights[0]; + let mut min = weights[0]; + weights.iter().for_each(|w| { + max = f32::max(max, *w); + min = f32::min(min, *w); + }); + let g = weights.len() as f32; + let weights_norm = weights.iter().fold(0.0, |sum, w| sum + w.powi(2)); + let mul = g / weights_norm.sqrt(); + weights.iter_mut().for_each(|w| *w *= mul); + } } impl CubicGenerator

for CubicNurbs

{ #[inline] From 95c8e0816d78ef53f6f6469e45919f0120344495 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 11:30:26 +0300 Subject: [PATCH 13/82] Fixed lint error --- crates/bevy_math/src/cubic_splines.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index a25dbf00c36c7..59d92699b08ba 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -717,7 +717,7 @@ impl CubicCurve

{ #[inline] /// Adds a segment to the curve pub fn push_segment(&mut self, segment: CubicSegment

) { - self.segments.push(segment) + self.segments.push(segment); } /// Returns the [`CubicSegment`] and local `t` value given a spline's global `t` value. @@ -749,7 +749,7 @@ impl CubicCurve

{ impl Extend> for CubicCurve

{ fn extend>>(&mut self, iter: T) { - self.segments.extend(iter) + self.segments.extend(iter); } } From 0101867fd80a0e24e735257464a976c740cf290b Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 11:39:47 +0300 Subject: [PATCH 14/82] Moved coefficients computation to CubicSegment --- crates/bevy_math/src/cubic_splines.rs | 40 +++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 59d92699b08ba..09971c402e86a 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -91,7 +91,7 @@ impl CubicGenerator

for CubicBezier

{ let segments = self .control_points .iter() - .map(|p| CubicCurve::coefficients(*p, 1.0, char_matrix)) + .map(|p| CubicSegment::coefficients(*p, 1.0, char_matrix)) .collect(); CubicCurve { segments } @@ -162,7 +162,7 @@ impl CubicGenerator

for CubicHermite

{ .windows(2) .map(|p| { let (p0, v0, p1, v1) = (p[0].0, p[0].1, p[1].0, p[1].1); - CubicCurve::coefficients([p0, v0, p1, v1], 1.0, char_matrix) + CubicSegment::coefficients([p0, v0, p1, v1], 1.0, char_matrix) }) .collect(); @@ -233,7 +233,7 @@ impl CubicGenerator

for CubicCardinalSpline

{ let segments = self .control_points .windows(4) - .map(|p| CubicCurve::coefficients([p[0], p[1], p[2], p[3]], 1.0, char_matrix)) + .map(|p| CubicSegment::coefficients([p[0], p[1], p[2], p[3]], 1.0, char_matrix)) .collect(); CubicCurve { segments } @@ -289,7 +289,7 @@ impl CubicGenerator

for CubicBSpline

{ let segments = self .control_points .windows(4) - .map(|p| CubicCurve::coefficients([p[0], p[1], p[2], p[3]], 1.0 / 6.0, char_matrix)) + .map(|p| CubicSegment::coefficients([p[0], p[1], p[2], p[3]], 1.0 / 6.0, char_matrix)) .collect(); CubicCurve { segments } @@ -444,7 +444,7 @@ impl CubicGenerator

for CubicNurbs

{ .unwrap(); let matrix = Self::generate_matrix(knot_vector_segment); // Unwrap will not fail because the windows slice is of length 4 - CubicCurve::coefficients(points.try_into().unwrap(), 1.0, matrix) + CubicSegment::coefficients(points.try_into().unwrap(), 1.0, matrix) }) .collect(); CubicCurve { segments } @@ -516,6 +516,21 @@ impl CubicSegment

{ let [_, _, c, d] = self.coeff; c * 2.0 + d * 6.0 * t } + + #[inline] + fn coefficients(p: [P; 4], multiplier: f32, char_matrix: [[f32; 4]; 4]) -> Self { + let [c0, c1, c2, c3] = char_matrix; + // These are the polynomial coefficients, computed by multiplying the characteristic + // matrix by the point matrix. + let mut coeff = [ + p[0] * c0[0] + p[1] * c0[1] + p[2] * c0[2] + p[3] * c0[3], + p[0] * c1[0] + p[1] * c1[1] + p[2] * c1[2] + p[3] * c1[3], + p[0] * c2[0] + p[1] * c2[1] + p[2] * c2[2] + p[3] * c2[3], + p[0] * c3[0] + p[1] * c3[1] + p[2] * c3[2] + p[3] * c3[3], + ]; + coeff.iter_mut().for_each(|c| *c = *c * multiplier); + Self { coeff } + } } /// The `CubicSegment` can be used as a 2-dimensional easing curve for animation. @@ -730,21 +745,6 @@ impl CubicCurve

{ (&self.segments[i], t - i as f32) } } - - #[inline] - fn coefficients(p: [P; 4], multiplier: f32, char_matrix: [[f32; 4]; 4]) -> CubicSegment

{ - let [c0, c1, c2, c3] = char_matrix; - // These are the polynomial coefficients, computed by multiplying the characteristic - // matrix by the point matrix. - let mut coeff = [ - p[0] * c0[0] + p[1] * c0[1] + p[2] * c0[2] + p[3] * c0[3], - p[0] * c1[0] + p[1] * c1[1] + p[2] * c1[2] + p[3] * c1[3], - p[0] * c2[0] + p[1] * c2[1] + p[2] * c2[2] + p[3] * c2[3], - p[0] * c3[0] + p[1] * c3[1] + p[2] * c3[2] + p[3] * c3[3], - ]; - coeff.iter_mut().for_each(|c| *c = *c * multiplier); - CubicSegment { coeff } - } } impl Extend> for CubicCurve

{ From c1a51056d4f360d7139afc7f80de3fa7c5be868f Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 11:42:21 +0300 Subject: [PATCH 15/82] Fixed typo in comment --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 09971c402e86a..3b22617c15475 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -438,7 +438,7 @@ impl CubicGenerator

for CubicNurbs

{ .windows(4) .enumerate() .map(|(i, points)| { - // Unwrap will not fail because the slice take nis of length 8 + // Unwrap will not fail because the slice is of length 8 let knot_vector_segment = (&self.knot_vector[(i * 3)..(i * 3 + 8)]) .try_into() .unwrap(); From 1407647e60859138ff5a078d8f850eecba2a464e Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 11:43:20 +0300 Subject: [PATCH 16/82] Extract multiplied index to a variable --- crates/bevy_math/src/cubic_splines.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 3b22617c15475..27a9a5690929d 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -438,10 +438,9 @@ impl CubicGenerator

for CubicNurbs

{ .windows(4) .enumerate() .map(|(i, points)| { + let i = i * 3; // Unwrap will not fail because the slice is of length 8 - let knot_vector_segment = (&self.knot_vector[(i * 3)..(i * 3 + 8)]) - .try_into() - .unwrap(); + let knot_vector_segment = (&self.knot_vector[i..i + 8]).try_into().unwrap(); let matrix = Self::generate_matrix(knot_vector_segment); // Unwrap will not fail because the windows slice is of length 4 CubicSegment::coefficients(points.try_into().unwrap(), 1.0, matrix) From 2e0a2a4343f609d9acc12295db76a52e299c06aa Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 11:56:09 +0300 Subject: [PATCH 17/82] Remoevd panics with Result and error enum --- crates/bevy_math/Cargo.toml | 1 + crates/bevy_math/src/cubic_splines.rs | 53 +++++++++++++++++++-------- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index 7ee418a42158e..129e30473650a 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -10,6 +10,7 @@ keywords = ["bevy"] [dependencies] glam = { version = "0.24.1", features = ["bytemuck"] } +thiserror = "1.0" serde = { version = "1", features = ["derive"], optional = true } [features] diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 27a9a5690929d..a16fa10cb8e43 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -7,6 +7,7 @@ use std::{ }; use glam::Vec2; +use thiserror::Error; /// A point in space of any dimension that supports the math ops needed for cubic spline /// interpolation. @@ -296,6 +297,31 @@ impl CubicGenerator

for CubicBSpline

{ } } +/// Error during construction of [`CubicNurbs`] +#[derive(Debug, Error)] +pub enum CubicNurbsError { + /// Provided knot vector had an invalid length + #[error("Invalid knot vector length: expected {expected}, provided {provided}")] + InvalidKnotVectorLength { + /// Expected knot vector lengths + expected: usize, + /// Provided knot vector length + provided: usize, + }, + /// Knot vector has invalid values. Values of a knot vector must be nondescending, meaning the + /// next element must be greater than or equal to the previous one. + #[error("Invalid knot vector values: elements are not nondescending")] + InvalidKnotVectorValues, + /// Provided weights vector didn't have the same amoutn of values as teh control points vector + #[error("Invalid weights vector length: expected {expected}, provided {provided}")] + WeightsVectorMismatch { + /// Expected weights vector size + expected: usize, + /// Provided weights vector size + provided: usize, + }, +} + /// A Non-Uniform Rational B-Spline pub struct CubicNurbs { control_points: Vec

, @@ -312,7 +338,7 @@ impl CubicNurbs

{ control_points: impl Into>, weights: Option>>, knot_vector: Option>>, - ) -> Self { + ) -> Result { let mut control_points: Vec

= control_points.into(); let control_points_len = control_points.len(); @@ -328,11 +354,10 @@ impl CubicNurbs

{ // Check the knot vector length if knot_vector.len() != knot_vector_expected_length { - // TODO: change to result - panic!( - "Invalid knot vector length: {knot_vector_expected_length} expected, {} provided", - knot_vector.len() - ); + return Err(CubicNurbsError::InvalidKnotVectorLength { + expected: knot_vector_expected_length, + provided: knot_vector.len(), + }); } // Check the knot vector for being nondescending (previous elements is less than or equal @@ -347,18 +372,16 @@ impl CubicNurbs

{ } if !is_nondescending { - // TODO: change to result - panic!("Invalid knot vector, elements are not nondescending"); + return Err(CubicNurbsError::InvalidKnotVectorValues); } } // Check the weights vector length if weights.len() != control_points_len { - // TODO: change to result - panic!( - "Invalid weights vector length: {control_points_len} expected, {} provided", - weights.len() - ); + return Err(CubicNurbsError::WeightsVectorMismatch { + expected: control_points_len, + provided: weights.len(), + }); } Self::normalize_weights(&mut weights); @@ -368,10 +391,10 @@ impl CubicNurbs

{ .zip(weights) .for_each(|(p, w)| *p = *p * w); - Self { + Ok(Self { control_points, knot_vector, - } + }) } /// Generates a uniform knot vector that will generate the same curve as [`CubicBSpline`] From 42de3361cca3344483052f5de93b05aff3c83225 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 11:58:12 +0300 Subject: [PATCH 18/82] Improvbed wording in open uniform knot vector fn documentation --- crates/bevy_math/src/cubic_splines.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index a16fa10cb8e43..2b6e05b72ff8e 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -403,8 +403,8 @@ impl CubicNurbs

{ (0..length).map(|v| v as f32).collect() } - /// Generates an open uniform knot vector, which makes the ends of the curve meet the end and - /// start control points + /// Generates an open uniform knot vector, which makes the ends of the curve pass through the + /// start and end points pub fn open_uniform_knot_vector(control_points: usize) -> Vec { let last_knots_value = control_points - 3; std::iter::repeat(0.0) From e3f45e50be35f83ab17f974dea13f67597900ab9 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 12:00:06 +0300 Subject: [PATCH 19/82] Removed code that wasn't doing anything --- crates/bevy_math/src/cubic_splines.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 2b6e05b72ff8e..75a95b2b1a990 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -441,12 +441,6 @@ impl CubicNurbs

{ } fn normalize_weights(weights: &mut [f32]) { - let mut max = weights[0]; - let mut min = weights[0]; - weights.iter().for_each(|w| { - max = f32::max(max, *w); - min = f32::min(min, *w); - }); let g = weights.len() as f32; let weights_norm = weights.iter().fold(0.0, |sum, w| sum + w.powi(2)); let mul = g / weights_norm.sqrt(); From 754d3febd5516126ae039a5cb7df3b391c142ec9 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 2 Dec 2023 12:08:41 +0300 Subject: [PATCH 20/82] Made weight normalization use L0 norm --- crates/bevy_math/src/cubic_splines.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 75a95b2b1a990..9995ea9f427b2 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -440,10 +440,12 @@ impl CubicNurbs

{ ] } + /// Normalizes weights vector using L0 norm. + /// The resulting weight vector's values will add up to be equal the length of the vector fn normalize_weights(weights: &mut [f32]) { let g = weights.len() as f32; - let weights_norm = weights.iter().fold(0.0, |sum, w| sum + w.powi(2)); - let mul = g / weights_norm.sqrt(); + let weights_sum = weights.iter().fold(0.0, |sum, w| sum + w); + let mul = g / weights_sum; weights.iter_mut().for_each(|w| *w *= mul); } } From 3c3f34b80fde47efe51b30a13c25eaea8ca090de Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sun, 3 Dec 2023 15:56:00 +0300 Subject: [PATCH 21/82] Correct misunderstanding of `windows` method --- crates/bevy_math/src/cubic_splines.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 9995ea9f427b2..ec51ccec1aba8 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -457,7 +457,6 @@ impl CubicGenerator

for CubicNurbs

{ .windows(4) .enumerate() .map(|(i, points)| { - let i = i * 3; // Unwrap will not fail because the slice is of length 8 let knot_vector_segment = (&self.knot_vector[i..i + 8]).try_into().unwrap(); let matrix = Self::generate_matrix(knot_vector_segment); From 2d3cc3b01ca8b62f4a9a9c262f8e72d268789291 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 4 Dec 2023 12:30:18 +0300 Subject: [PATCH 22/82] Improved documentation of CubicNurbs --- crates/bevy_math/src/cubic_splines.rs | 83 +++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index ec51ccec1aba8..8a2624465c33f 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -320,9 +320,42 @@ pub enum CubicNurbsError { /// Provided weights vector size provided: usize, }, + /// The amoutn of points provided is less than 4 + #[error("Not enough control points, at least 4 are required, {provided} were provided")] + NotEnoughControlPoints { + /// The amount of control points provided + provided: usize, + }, } -/// A Non-Uniform Rational B-Spline +/// A spline interpolated continuously across the nearest four control points. The curve does not +/// pass through any of the control points unless there is a knot with multiplicity of 4. +/// +/// ### Interpolation +/// The curve does not pass through control points, unless the knot vector has 4 consecutive values +/// that are equal to each other +/// +/// ### Tangency +/// Automatically computed based on the position of control points. +/// +/// ### Continuity +/// C2 continuous! The acceleration continuity of this spline makes it useful for camera paths. +/// +/// ### Usage +/// +/// ``` +/// # use bevy_math::{*, prelude::*}; +/// let points = [ +/// vec2(-1.0, -20.0), +/// vec2(3.0, 2.0), +/// vec2(5.0, 3.0), +/// vec2(9.0, 8.0), +/// ]; +/// let weights = [1.0, 1.0, 2.0, 1.0]; +/// let knot_vector = [0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]; +/// let nurbs = CubicNurbs::new(points, Some(weights), Some(knot_vector)).to_curve(); +/// let positions: Vec<_> = nurbs.iter_positions(100).collect(); +/// ``` pub struct CubicNurbs { control_points: Vec

, knot_vector: Vec, @@ -342,13 +375,20 @@ impl CubicNurbs

{ let mut control_points: Vec

= control_points.into(); let control_points_len = control_points.len(); + if control_points_len < 4 { + return Err(CubicNurbsError::NotEnoughControlPoints { + provided: control_points_len, + }); + } + let mut weights = weights .map(Into::into) .unwrap_or_else(|| vec![1.0; control_points_len]); + // Unwrap is safe because the length was checked before let knot_vector: Vec = knot_vector .map(Into::into) - .unwrap_or_else(|| Self::open_uniform_knot_vector(control_points_len)); + .unwrap_or_else(|| Self::open_uniform_knot_vector(control_points_len).unwrap()); let knot_vector_expected_length = Self::knot_vector_length(control_points_len); @@ -397,21 +437,40 @@ impl CubicNurbs

{ }) } - /// Generates a uniform knot vector that will generate the same curve as [`CubicBSpline`] - pub fn uniform_knot_vector(control_points: usize) -> Vec { + /// Generates a uniform knot vector that will generate the same curve as [`CubicBSpline`]. + /// + /// "Uniform" means that teh difference between two knot values next to each other is the same + /// through teh entire knot vector. + /// + /// Will return `None` if there are less than 4 control points + pub fn uniform_knot_vector(control_points: usize) -> Option> { + if control_points < 4 { + return None; + } let length = control_points + 4; - (0..length).map(|v| v as f32).collect() + Some((0..length).map(|v| v as f32).collect()) } /// Generates an open uniform knot vector, which makes the ends of the curve pass through the - /// start and end points - pub fn open_uniform_knot_vector(control_points: usize) -> Vec { + /// start and end points. + /// + /// The knot vector will have a knot with multiplicity of 4 at the end and start and elements + /// in the middle will have a difference of 1. "Multiplicity" means taht there are N + /// consecutive elements that have the same value. + /// + /// Will return `None` if there are less than 4 control points + pub fn open_uniform_knot_vector(control_points: usize) -> Option> { + if control_points < 4 { + return None; + } let last_knots_value = control_points - 3; - std::iter::repeat(0.0) - .take(4) - .chain((1..last_knots_value).map(|v| v as f32)) - .chain(std::iter::repeat(last_knots_value as f32).take(4)) - .collect() + Some( + std::iter::repeat(0.0) + .take(4) + .chain((1..last_knots_value).map(|v| v as f32)) + .chain(std::iter::repeat(last_knots_value as f32).take(4)) + .collect(), + ) } #[inline(always)] From 4fc0fa88bbdda64aae650a391845ef5583b9795b Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 4 Dec 2023 12:31:15 +0300 Subject: [PATCH 23/82] Added 4 point minimum note to nurbs constructor --- crates/bevy_math/src/cubic_splines.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 8a2624465c33f..0e03e99f55d49 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -363,10 +363,13 @@ pub struct CubicNurbs { impl CubicNurbs

{ /// Generates a Non-Uniform Rational B-Spline. /// - /// If provided, weights vector must have the same amount of items as the control points vector + /// If provided, weights vector must have the same amount of items as the control points + /// vector. /// /// If provided, the knot vector must have n + 4 elements, where n is the amoutn of control - /// points + /// points. + /// + /// At least 4 points must be provided, otherwise an error will be returned. pub fn new( control_points: impl Into>, weights: Option>>, From 410dade3879a8f0b4700f109e829a911ddcfda52 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 4 Dec 2023 12:32:37 +0300 Subject: [PATCH 24/82] Fixed an error in CubicBSpline::new doc comment --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 0e03e99f55d49..8464ed4881edc 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -270,7 +270,7 @@ pub struct CubicBSpline { control_points: Vec

, } impl CubicBSpline

{ - /// Build a new Cardinal spline. + /// Build a new B-Spline. pub fn new(control_points: impl Into>) -> Self { Self { control_points: control_points.into(), From eff9f9837ba2b1516cb43df634cdeae63c2b6bc6 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 4 Dec 2023 12:34:41 +0300 Subject: [PATCH 25/82] Wording and typo fixes --- crates/bevy_math/src/cubic_splines.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 8464ed4881edc..48f10645dea7f 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -300,10 +300,10 @@ impl CubicGenerator

for CubicBSpline

{ /// Error during construction of [`CubicNurbs`] #[derive(Debug, Error)] pub enum CubicNurbsError { - /// Provided knot vector had an invalid length + /// Provided knot vector had an invalid length. #[error("Invalid knot vector length: expected {expected}, provided {provided}")] InvalidKnotVectorLength { - /// Expected knot vector lengths + /// Expected knot vector length expected: usize, /// Provided knot vector length provided: usize, @@ -312,15 +312,15 @@ pub enum CubicNurbsError { /// next element must be greater than or equal to the previous one. #[error("Invalid knot vector values: elements are not nondescending")] InvalidKnotVectorValues, - /// Provided weights vector didn't have the same amoutn of values as teh control points vector + /// Provided weights vector didn't have the same amount of values as the control points vector. #[error("Invalid weights vector length: expected {expected}, provided {provided}")] WeightsVectorMismatch { - /// Expected weights vector size + /// Expected weights vector length expected: usize, - /// Provided weights vector size + /// Provided weights vector length provided: usize, }, - /// The amoutn of points provided is less than 4 + /// The amount of control points provided is less than 4. #[error("Not enough control points, at least 4 are required, {provided} were provided")] NotEnoughControlPoints { /// The amount of control points provided From 26c0ea2cee18814de2f5016509e5fcc4bc79db32 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 4 Dec 2023 12:35:18 +0300 Subject: [PATCH 26/82] Wording fix for CubicNurbs::new --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 48f10645dea7f..59fc87c89a3dc 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -361,7 +361,7 @@ pub struct CubicNurbs { knot_vector: Vec, } impl CubicNurbs

{ - /// Generates a Non-Uniform Rational B-Spline. + /// Build a Non-Uniform Rational B-Spline. /// /// If provided, weights vector must have the same amount of items as the control points /// vector. From d5c2da5c10adca471fe0b385d8616e200929491c Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 4 Dec 2023 12:40:11 +0300 Subject: [PATCH 27/82] Import CubicNurbs and CubicNurbsError in prelude --- crates/bevy_math/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index 4d44d095c8fed..314beb58fd1cb 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -22,7 +22,7 @@ pub mod prelude { pub use crate::{ cubic_splines::{ CubicBSpline, CubicBezier, CubicCardinalSpline, CubicGenerator, CubicHermite, - CubicSegment, + CubicNurbs, CubicNurbsError, CubicSegment, }, primitives, BVec2, BVec3, BVec4, EulerRot, IRect, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4, Quat, Ray, Rect, URect, UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, From f5e85809fece08ec767ae50d04a784a06f3e07a6 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 4 Dec 2023 12:42:42 +0300 Subject: [PATCH 28/82] Fixed NURBS example --- crates/bevy_math/src/cubic_splines.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 59fc87c89a3dc..9253d0b509643 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -353,7 +353,9 @@ pub enum CubicNurbsError { /// ]; /// let weights = [1.0, 1.0, 2.0, 1.0]; /// let knot_vector = [0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]; -/// let nurbs = CubicNurbs::new(points, Some(weights), Some(knot_vector)).to_curve(); +/// let nurbs = CubicNurbs::new(points, Some(weights), Some(knot_vector)) +/// .expect("NURBS construction failed!") +/// .to_curve(); /// let positions: Vec<_> = nurbs.iter_positions(100).collect(); /// ``` pub struct CubicNurbs { From 68d88fa29d0e1e44d705f82b64bac0bcbc41c7d0 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Tue, 5 Dec 2023 13:19:25 +0300 Subject: [PATCH 29/82] Improved doc wording for normalize_weights --- crates/bevy_math/src/cubic_splines.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 9253d0b509643..ca5e4da65b6c0 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -505,7 +505,8 @@ impl CubicNurbs

{ } /// Normalizes weights vector using L0 norm. - /// The resulting weight vector's values will add up to be equal the length of the vector + /// The resulting weight vector's values will add up to be equal the amoutn of values in teh + /// weighst vector fn normalize_weights(weights: &mut [f32]) { let g = weights.len() as f32; let weights_sum = weights.iter().fold(0.0, |sum, w| sum + w); From d9c01879e06e3494686683c24e54635c9a8e2828 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Tue, 5 Dec 2023 13:19:54 +0300 Subject: [PATCH 30/82] Fixed typo --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index ca5e4da65b6c0..49fa260148619 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -506,7 +506,7 @@ impl CubicNurbs

{ /// Normalizes weights vector using L0 norm. /// The resulting weight vector's values will add up to be equal the amoutn of values in teh - /// weighst vector + /// weights vector fn normalize_weights(weights: &mut [f32]) { let g = weights.len() as f32; let weights_sum = weights.iter().fold(0.0, |sum, w| sum + w); From a1ba5e58836af80803dda0630b8862aa4d474f3d Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 18 Dec 2023 09:00:08 +0300 Subject: [PATCH 31/82] Remove redundant interpolation information from nurbs doc --- crates/bevy_math/src/cubic_splines.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 49fa260148619..5fd4b764468fe 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -328,8 +328,7 @@ pub enum CubicNurbsError { }, } -/// A spline interpolated continuously across the nearest four control points. The curve does not -/// pass through any of the control points unless there is a knot with multiplicity of 4. +/// A spline interpolated continuously across the nearest four control points. /// /// ### Interpolation /// The curve does not pass through control points, unless the knot vector has 4 consecutive values From 209091e2c15f0d6377f9e49e29a9d01020a54ecd Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 18 Dec 2023 09:02:00 +0300 Subject: [PATCH 32/82] Simpler check for knot vector values --- crates/bevy_math/src/cubic_splines.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 5fd4b764468fe..36d3661b44ca1 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -406,18 +406,8 @@ impl CubicNurbs

{ // Check the knot vector for being nondescending (previous elements is less than or equal // to the next) - { - let mut is_nondescending = true; - - for window_slice in knot_vector.windows(2) { - let prev = window_slice[0]; - let next = window_slice[1]; - is_nondescending = is_nondescending && (prev <= next); - } - - if !is_nondescending { - return Err(CubicNurbsError::InvalidKnotVectorValues); - } + if !knot_vector.windows(2).any(|win| win[0] > win[1]) { + return Err(CubicNurbsError::InvalidKnotVectorValues); } // Check the weights vector length From 7996678995648acb0eb5b531c249a185996f1ee0 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 18 Dec 2023 09:05:27 +0300 Subject: [PATCH 33/82] REplace unwrap with expect --- crates/bevy_math/src/cubic_splines.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 36d3661b44ca1..3ac74db51626d 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -389,10 +389,10 @@ impl CubicNurbs

{ .map(Into::into) .unwrap_or_else(|| vec![1.0; control_points_len]); - // Unwrap is safe because the length was checked before - let knot_vector: Vec = knot_vector - .map(Into::into) - .unwrap_or_else(|| Self::open_uniform_knot_vector(control_points_len).unwrap()); + let knot_vector: Vec = knot_vector.map(Into::into).unwrap_or_else(|| { + Self::open_uniform_knot_vector(control_points_len) + .expect("The amount of control points was checked") + }); let knot_vector_expected_length = Self::knot_vector_length(control_points_len); From 7dd0137b2f8e6337c742af8a373058b5108c7939 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 18 Dec 2023 09:05:55 +0300 Subject: [PATCH 34/82] Use Self::knot_vector_expected_length --- crates/bevy_math/src/cubic_splines.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 3ac74db51626d..be48342668aa4 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -441,8 +441,11 @@ impl CubicNurbs

{ if control_points < 4 { return None; } - let length = control_points + 4; - Some((0..length).map(|v| v as f32).collect()) + Some( + (0..Self::knot_vector_length(control_points)) + .map(|v| v as f32) + .collect(), + ) } /// Generates an open uniform knot vector, which makes the ends of the curve pass through the From ecb5f8e611bf0675e502542d9100314171c7e017 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 18 Dec 2023 09:06:45 +0300 Subject: [PATCH 35/82] Fix typos --- crates/bevy_math/src/cubic_splines.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index be48342668aa4..13956981ed992 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -433,8 +433,8 @@ impl CubicNurbs

{ /// Generates a uniform knot vector that will generate the same curve as [`CubicBSpline`]. /// - /// "Uniform" means that teh difference between two knot values next to each other is the same - /// through teh entire knot vector. + /// "Uniform" means that the difference between two knot values next to each other is the same + /// through the entire knot vector. /// /// Will return `None` if there are less than 4 control points pub fn uniform_knot_vector(control_points: usize) -> Option> { @@ -497,7 +497,7 @@ impl CubicNurbs

{ } /// Normalizes weights vector using L0 norm. - /// The resulting weight vector's values will add up to be equal the amoutn of values in teh + /// The resulting weight vector's values will add up to be equal the amoutn of values in the /// weights vector fn normalize_weights(weights: &mut [f32]) { let g = weights.len() as f32; From c10298e67fbb705b468892bec8e3eafe17c3ada1 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 18 Dec 2023 09:08:40 +0300 Subject: [PATCH 36/82] Inline `i` to use constant knot vector indexes instead --- crates/bevy_math/src/cubic_splines.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 13956981ed992..e84ad23d4e758 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -478,16 +478,12 @@ impl CubicNurbs

{ /// Based on fn generate_matrix(knot_vector_segment: &[f32; 8]) -> [[f32; 4]; 4] { let t = knot_vector_segment; - let i = 3; - let m00 = (t[i + 1] - t[i]).powi(2) / ((t[i + 1] - t[i - 1]) * (t[i + 1] - t[i - 2])); - let m02 = (t[i] - t[i - 1]).powi(2) / ((t[i + 2] - t[i - 1]) * (t[i + 1] - t[i - 1])); - let m12 = (3.0 * (t[i + 1] - t[i]) * (t[i] - t[i - 1])) - / ((t[i + 2] - t[i - 1]) * (t[i + 1] - t[i - 1])); - let m22 = 3.0 * (t[i + 1] - t[i]).powi(2) / ((t[i + 2] - t[i - 1]) * (t[i + 1] - t[i - 1])); - let m33 = (t[i + 1] - t[i]).powi(2) / ((t[i + 3] - t[i]) * (t[i + 2] - t[i])); - let m32 = -m22 / 3.0 - - m33 - - (t[i + 1] - t[i]).powi(2) / ((t[i + 2] - t[i]) * (t[i + 2] - t[i - 1])); + let m00 = (t[4] - t[3]).powi(2) / ((t[4] - t[2]) * (t[4] - t[1])); + let m02 = (t[3] - t[2]).powi(2) / ((t[5] - t[2]) * (t[4] - t[2])); + let m12 = (3.0 * (t[4] - t[3]) * (t[3] - t[2])) / ((t[5] - t[2]) * (t[4] - t[2])); + let m22 = 3.0 * (t[4] - t[3]).powi(2) / ((t[5] - t[2]) * (t[4] - t[2])); + let m33 = (t[4] - t[3]).powi(2) / ((t[3 + 3] - t[3]) * (t[5] - t[3])); + let m32 = -m22 / 3.0 - m33 - (t[4] - t[3]).powi(2) / ((t[5] - t[3]) * (t[5] - t[2])); [ [m00, 1.0 - m00 - m02, m02, 0.0], [-3.0 * m00, 3.0 * m00 - m12, m12, 0.0], From 77ba887080b1a904c52d5c21380e203482288d63 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 18 Dec 2023 09:11:08 +0300 Subject: [PATCH 37/82] Use sum instead of fold --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index e84ad23d4e758..465aa2a85ca81 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -497,7 +497,7 @@ impl CubicNurbs

{ /// weights vector fn normalize_weights(weights: &mut [f32]) { let g = weights.len() as f32; - let weights_sum = weights.iter().fold(0.0, |sum, w| sum + w); + let weights_sum: f32 = weights.iter().sum(); let mul = g / weights_sum; weights.iter_mut().for_each(|w| *w *= mul); } From 5cb413ec84fc1e8bdc7b26defb02cdbb8e7713dd Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 18 Dec 2023 09:15:38 +0300 Subject: [PATCH 38/82] Consume weights vector for normalization instad of mutable borrow --- crates/bevy_math/src/cubic_splines.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 465aa2a85ca81..8bb43a6ef9257 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -418,7 +418,7 @@ impl CubicNurbs

{ }); } - Self::normalize_weights(&mut weights); + weights = Self::normalize_weights(weights); control_points .iter_mut() @@ -495,11 +495,11 @@ impl CubicNurbs

{ /// Normalizes weights vector using L0 norm. /// The resulting weight vector's values will add up to be equal the amoutn of values in the /// weights vector - fn normalize_weights(weights: &mut [f32]) { + fn normalize_weights(weights: Vec) -> Vec { let g = weights.len() as f32; let weights_sum: f32 = weights.iter().sum(); let mul = g / weights_sum; - weights.iter_mut().for_each(|w| *w *= mul); + weights.into_iter().map(|w| w * mul).collect() } } impl CubicGenerator

for CubicNurbs

{ From 6ebd6b7893439eba07bb4c83b830e83f91f195f6 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 18 Dec 2023 09:25:43 +0300 Subject: [PATCH 39/82] zip iterators of windows instead of using enumerate --- crates/bevy_math/src/cubic_splines.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 8bb43a6ef9257..cfd56b317d3aa 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -508,13 +508,19 @@ impl CubicGenerator

for CubicNurbs

{ let segments = self .control_points .windows(4) - .enumerate() - .map(|(i, points)| { - // Unwrap will not fail because the slice is of length 8 - let knot_vector_segment = (&self.knot_vector[i..i + 8]).try_into().unwrap(); + .zip(self.knot_vector.windows(8)) + .map(|(points, knot_vector_segment)| { + let knot_vector_segment = knot_vector_segment + .try_into() + .expect("Knot vector windows are of length 8"); let matrix = Self::generate_matrix(knot_vector_segment); - // Unwrap will not fail because the windows slice is of length 4 - CubicSegment::coefficients(points.try_into().unwrap(), 1.0, matrix) + CubicSegment::coefficients( + points + .try_into() + .expect("Points vector windows are of length 4"), + 1.0, + matrix, + ) }) .collect(); CubicCurve { segments } From b4030fdeb00a57c184aa5d2eb9455001fef3cccd Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 18 Dec 2023 09:33:13 +0300 Subject: [PATCH 40/82] Fix knot vector values check --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index cfd56b317d3aa..4988c4aad7d58 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -406,7 +406,7 @@ impl CubicNurbs

{ // Check the knot vector for being nondescending (previous elements is less than or equal // to the next) - if !knot_vector.windows(2).any(|win| win[0] > win[1]) { + if knot_vector.windows(2).any(|win| win[0] > win[1]) { return Err(CubicNurbsError::InvalidKnotVectorValues); } From a8a84649aa74159bb070ddb5f8a366a3172dbc05 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Fri, 26 Jan 2024 22:53:28 +0300 Subject: [PATCH 41/82] Update glam version --- crates/bevy_math/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index 129e30473650a..1982e4a97ff85 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [dependencies] -glam = { version = "0.24.1", features = ["bytemuck"] } +glam = { version = "0.25", features = ["bytemuck"] } thiserror = "1.0" serde = { version = "1", features = ["derive"], optional = true } From 534092134116cfec5f434e511aada9673c498a76 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Fri, 26 Jan 2024 22:56:23 +0300 Subject: [PATCH 42/82] Use thiserror re-export from bevy_utils --- crates/bevy_math/Cargo.toml | 2 +- crates/bevy_math/src/cubic_splines.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index 1982e4a97ff85..d5237fc4d3546 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["bevy"] [dependencies] glam = { version = "0.25", features = ["bytemuck"] } -thiserror = "1.0" +bevy_utils = { path = "../bevy_utils", version = "0.12.0" } serde = { version = "1", features = ["derive"], optional = true } [features] diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 4988c4aad7d58..8fdd26b5688e0 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -6,8 +6,8 @@ use std::{ ops::{Add, Mul, Sub}, }; +use bevy_utils::{thiserror, thiserror::Error}; use glam::Vec2; -use thiserror::Error; /// A point in space of any dimension that supports the math ops needed for cubic spline /// interpolation. From 325816fae735bb270affb3618a57256906627a0f Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 26 Jan 2024 16:05:54 -0500 Subject: [PATCH 43/82] Typo fix Co-authored-by: IQuick 143 --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index bb14f573db85e..77ab944589089 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -367,7 +367,7 @@ impl CubicNurbs

{ /// If provided, weights vector must have the same amount of items as the control points /// vector. /// - /// If provided, the knot vector must have n + 4 elements, where n is the amoutn of control + /// If provided, the knot vector must have n + 4 elements, where n is the amount of control /// points. /// /// At least 4 points must be provided, otherwise an error will be returned. From 3501ef49f9776fb73ab108a3f2e797982270a122 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan <43478602+JohnTheCoolingFan@users.noreply.github.com> Date: Sat, 27 Jan 2024 12:56:28 +0300 Subject: [PATCH 44/82] Typo fix Co-authored-by: IQuick 143 --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 77ab944589089..a380d8d402ea1 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -493,7 +493,7 @@ impl CubicNurbs

{ } /// Normalizes weights vector using L0 norm. - /// The resulting weight vector's values will add up to be equal the amoutn of values in the + /// The resulting weight vector's values will add up to be equal the amount of values in the /// weights vector fn normalize_weights(weights: Vec) -> Vec { let g = weights.len() as f32; From c07b2e4c2b00373b474df847f08ec076daa29cd4 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan <43478602+JohnTheCoolingFan@users.noreply.github.com> Date: Sat, 27 Jan 2024 12:56:47 +0300 Subject: [PATCH 45/82] Typo fix Co-authored-by: IQuick 143 --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index a380d8d402ea1..199d29452d65e 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -452,7 +452,7 @@ impl CubicNurbs

{ /// start and end points. /// /// The knot vector will have a knot with multiplicity of 4 at the end and start and elements - /// in the middle will have a difference of 1. "Multiplicity" means taht there are N + /// in the middle will have a difference of 1. "Multiplicity" means that there are N /// consecutive elements that have the same value. /// /// Will return `None` if there are less than 4 control points From 7be7e692fb7b4c7ae20bfcce6249eb9a6549b4d3 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan <43478602+JohnTheCoolingFan@users.noreply.github.com> Date: Sat, 27 Jan 2024 12:58:17 +0300 Subject: [PATCH 46/82] Change knot vector index from expression to constant Co-authored-by: IQuick 143 --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 199d29452d65e..1366bde40b78f 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -482,7 +482,7 @@ impl CubicNurbs

{ let m02 = (t[3] - t[2]).powi(2) / ((t[5] - t[2]) * (t[4] - t[2])); let m12 = (3.0 * (t[4] - t[3]) * (t[3] - t[2])) / ((t[5] - t[2]) * (t[4] - t[2])); let m22 = 3.0 * (t[4] - t[3]).powi(2) / ((t[5] - t[2]) * (t[4] - t[2])); - let m33 = (t[4] - t[3]).powi(2) / ((t[3 + 3] - t[3]) * (t[5] - t[3])); + let m33 = (t[4] - t[3]).powi(2) / ((t[6] - t[3]) * (t[5] - t[3])); let m32 = -m22 / 3.0 - m33 - (t[4] - t[3]).powi(2) / ((t[5] - t[3]) * (t[5] - t[2])); [ [m00, 1.0 - m00 - m02, m02, 0.0], From 0bca0fdf75b58c6a629d3b380a37778cdba154ac Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 27 Jan 2024 13:07:18 +0300 Subject: [PATCH 47/82] Document wefault weights and knot vector for nurbs --- crates/bevy_math/src/cubic_splines.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 1366bde40b78f..7bac3e49c61a4 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -365,10 +365,10 @@ impl CubicNurbs

{ /// Build a Non-Uniform Rational B-Spline. /// /// If provided, weights vector must have the same amount of items as the control points - /// vector. + /// vector. Defaults to equal weights. /// /// If provided, the knot vector must have n + 4 elements, where n is the amount of control - /// points. + /// points. Defaults to open uniform knot vector: [`Self::open_uniform_knot_vector`]. /// /// At least 4 points must be provided, otherwise an error will be returned. pub fn new( From 80afd617d5365f145a68ed5225e73c4f4c338223 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 27 Jan 2024 13:14:40 +0300 Subject: [PATCH 48/82] Remove multiplier argument in CubicSegment::coefficients --- crates/bevy_math/src/cubic_splines.rs | 28 ++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 7bac3e49c61a4..b6072e2c46217 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -92,7 +92,7 @@ impl CubicGenerator

for CubicBezier

{ let segments = self .control_points .iter() - .map(|p| CubicSegment::coefficients(*p, 1.0, char_matrix)) + .map(|p| CubicSegment::coefficients(*p, char_matrix)) .collect(); CubicCurve { segments } @@ -163,7 +163,7 @@ impl CubicGenerator

for CubicHermite

{ .windows(2) .map(|p| { let (p0, v0, p1, v1) = (p[0].0, p[0].1, p[1].0, p[1].1); - CubicSegment::coefficients([p0, v0, p1, v1], 1.0, char_matrix) + CubicSegment::coefficients([p0, v0, p1, v1], char_matrix) }) .collect(); @@ -234,7 +234,7 @@ impl CubicGenerator

for CubicCardinalSpline

{ let segments = self .control_points .windows(4) - .map(|p| CubicSegment::coefficients([p[0], p[1], p[2], p[3]], 1.0, char_matrix)) + .map(|p| CubicSegment::coefficients([p[0], p[1], p[2], p[3]], char_matrix)) .collect(); CubicCurve { segments } @@ -280,17 +280,21 @@ impl CubicBSpline

{ impl CubicGenerator

for CubicBSpline

{ #[inline] fn to_curve(&self) -> CubicCurve

{ - let char_matrix = [ - [1., 4., 1., 0.], - [-3., 0., 3., 0.], - [3., -6., 3., 0.], - [-1., 3., -3., 1.], + let mut char_matrix = [ + [1.0, 4.0, 1.0, 0.0], + [-3.0, 0.0, 3.0, 0.0], + [3.0, -6.0, 3.0, 0.0], + [-1.0, 3.0, -3.0, 1.0], ]; + char_matrix + .iter_mut() + .for_each(|r| r.iter_mut().for_each(|c| *c = *c / 6.0)); + let segments = self .control_points .windows(4) - .map(|p| CubicSegment::coefficients([p[0], p[1], p[2], p[3]], 1.0 / 6.0, char_matrix)) + .map(|p| CubicSegment::coefficients([p[0], p[1], p[2], p[3]], char_matrix)) .collect(); CubicCurve { segments } @@ -518,7 +522,6 @@ impl CubicGenerator

for CubicNurbs

{ points .try_into() .expect("Points vector windows are of length 4"), - 1.0, matrix, ) }) @@ -594,17 +597,16 @@ impl CubicSegment

{ } #[inline] - fn coefficients(p: [P; 4], multiplier: f32, char_matrix: [[f32; 4]; 4]) -> Self { + fn coefficients(p: [P; 4], char_matrix: [[f32; 4]; 4]) -> Self { let [c0, c1, c2, c3] = char_matrix; // These are the polynomial coefficients, computed by multiplying the characteristic // matrix by the point matrix. - let mut coeff = [ + let coeff = [ p[0] * c0[0] + p[1] * c0[1] + p[2] * c0[2] + p[3] * c0[3], p[0] * c1[0] + p[1] * c1[1] + p[2] * c1[2] + p[3] * c1[3], p[0] * c2[0] + p[1] * c2[1] + p[2] * c2[2] + p[3] * c2[3], p[0] * c3[0] + p[1] * c3[1] + p[2] * c3[2] + p[3] * c3[3], ]; - coeff.iter_mut().for_each(|c| *c = *c * multiplier); Self { coeff } } } From 6ad1d151ea25954bb449f49dd7eecf23bfac85e2 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 27 Jan 2024 13:19:28 +0300 Subject: [PATCH 49/82] Use assign and divide operator --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index b6072e2c46217..f4054c5b10033 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -289,7 +289,7 @@ impl CubicGenerator

for CubicBSpline

{ char_matrix .iter_mut() - .for_each(|r| r.iter_mut().for_each(|c| *c = *c / 6.0)); + .for_each(|r| r.iter_mut().for_each(|c| *c /= 6.0)); let segments = self .control_points From 2b9063be24980107f407e7e78de21e8417bf1049 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sun, 28 Jan 2024 16:16:41 +0300 Subject: [PATCH 50/82] CubicNurbs documentation improvements --- crates/bevy_math/src/cubic_splines.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index f4054c5b10033..a8597b5cf871c 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -332,17 +332,22 @@ pub enum CubicNurbsError { }, } -/// A spline interpolated continuously across the nearest four control points. +/// A cubic non-uniform rational B-spline (NURBS). Generates a smooth curve from a +/// sequence of control points by interpolating between four points at a time. /// /// ### Interpolation -/// The curve does not pass through control points, unless the knot vector has 4 consecutive values -/// that are equal to each other +/// The knot vector is a non-decreasing sequence that controls which four +/// control points are assigned to each segment of the curve. It can be used to make +/// sharp corners. The curve will not pass through the control points unless the +/// knot vector has the same value four times in a row. /// -/// ### Tangency -/// Automatically computed based on the position of control points. -/// -/// ### Continuity -/// C2 continuous! The acceleration continuity of this spline makes it useful for camera paths. +/// ### Curvature +/// The tangents automatically calculated based on the position of the control +/// points. The curve is C2 continuous (meaning both the velocity and +/// acceleration are smooth), making it useful for camera paths and moving objects. +/// The continuity reduces if the curve's knot vector has repeating values, which is called knot +/// multiplicity. Knot multiplicity of 2 would reduce the continuity to C1, multiplicity of 3 would +/// reduce the continuity to C0. /// /// ### Usage /// From 7239dd289e2a2c61ed4a59f1c71f8c423a9e2efe Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sun, 28 Jan 2024 16:36:47 +0300 Subject: [PATCH 51/82] Make BSpline matrix an expression Assembly analysis shows that using an expression instead of applying the div 6.0 operation using an iterator produces smaller and probably more efficient code. The division by 6.0 was left in the matrix on all values to explicitly show that all values are divided by it. Manually simplifying some values doesn't result in any difference in the compiled code. Godbolt: https://godbolt.org/z/MMfMhnMsq --- crates/bevy_math/src/cubic_splines.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index a8597b5cf871c..a447b5484b540 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -280,17 +280,13 @@ impl CubicBSpline

{ impl CubicGenerator

for CubicBSpline

{ #[inline] fn to_curve(&self) -> CubicCurve

{ - let mut char_matrix = [ - [1.0, 4.0, 1.0, 0.0], - [-3.0, 0.0, 3.0, 0.0], - [3.0, -6.0, 3.0, 0.0], - [-1.0, 3.0, -3.0, 1.0], + let char_matrix = [ + [1.0 / 6.0, 4.0 / 6.0, 1.0 / 6.0, 0.0 / 6.0], + [-3.0 / 6.0, 0.0 / 6.0, 3.0 / 6.0, 0.0 / 6.0], + [3.0 / 6.0, -6.0 / 6.0, 3.0 / 6.0, 0.0 / 6.0], + [-1.0 / 6.0, 3.0 / 6.0, -3.0 / 6.0, 1.0 / 6.0], ]; - char_matrix - .iter_mut() - .for_each(|r| r.iter_mut().for_each(|c| *c /= 6.0)); - let segments = self .control_points .windows(4) From 579e70b2cff8211d3438edfecedf245150ab3e34 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan <43478602+JohnTheCoolingFan@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:16:35 +0300 Subject: [PATCH 52/82] Better wording for linear spline Co-authored-by: Alice Cecile --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index a447b5484b540..f34a927d0340f 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -531,7 +531,7 @@ impl CubicGenerator

for CubicNurbs

{ } } -/// A spline interpolated linearly across nearest 2 points. +/// A spline interpolated linearly between the nearest 2 points. pub struct LinearSpline { points: Vec

, } From 34105659414452fe2cf9d0ce481c94bccaad12d1 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Mon, 29 Jan 2024 19:19:58 +0300 Subject: [PATCH 53/82] Ducmentation for CubicSegment::coefficients --- crates/bevy_math/src/cubic_splines.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index f34a927d0340f..47682619040d0 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -597,6 +597,7 @@ impl CubicSegment

{ c * 2.0 + d * 6.0 * t } + /// Calculate polynomial coefficients for the cubic curve using a characteristic matrix. #[inline] fn coefficients(p: [P; 4], char_matrix: [[f32; 4]; 4]) -> Self { let [c0, c1, c2, c3] = char_matrix; From a4f5537e7ad8fa719f6d738fc05788a5f8d1d965 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Tue, 30 Jan 2024 22:57:30 +0300 Subject: [PATCH 54/82] Revert "Make BSpline matrix an expression" This reverts commit 7239dd289e2a2c61ed4a59f1c71f8c423a9e2efe. The reason is that in actuality, the assembly produced is identical at opt-level=3. Godbolt: https://godbolt.org/z/csqG1Y19P --- crates/bevy_math/src/cubic_splines.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 47682619040d0..e13adc670b587 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -280,13 +280,17 @@ impl CubicBSpline

{ impl CubicGenerator

for CubicBSpline

{ #[inline] fn to_curve(&self) -> CubicCurve

{ - let char_matrix = [ - [1.0 / 6.0, 4.0 / 6.0, 1.0 / 6.0, 0.0 / 6.0], - [-3.0 / 6.0, 0.0 / 6.0, 3.0 / 6.0, 0.0 / 6.0], - [3.0 / 6.0, -6.0 / 6.0, 3.0 / 6.0, 0.0 / 6.0], - [-1.0 / 6.0, 3.0 / 6.0, -3.0 / 6.0, 1.0 / 6.0], + let mut char_matrix = [ + [1.0, 4.0, 1.0, 0.0], + [-3.0, 0.0, 3.0, 0.0], + [3.0, -6.0, 3.0, 0.0], + [-1.0, 3.0, -3.0, 1.0], ]; + char_matrix + .iter_mut() + .for_each(|r| r.iter_mut().for_each(|c| *c /= 6.0)); + let segments = self .control_points .windows(4) From 23de0637705929aad4aaeb360e4965832c64bbb7 Mon Sep 17 00:00:00 2001 From: IQuick143 Date: Tue, 30 Jan 2024 22:46:10 +0100 Subject: [PATCH 55/82] Implement RationalCurve and RationalSegment for NURBS. --- crates/bevy_math/src/cubic_splines.rs | 290 +++++++++++++++++++++++--- crates/bevy_math/src/lib.rs | 2 +- 2 files changed, 265 insertions(+), 27 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 47682619040d0..7c39bf9285e6b 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -3,7 +3,7 @@ use std::{ fmt::Debug, iter::Sum, - ops::{Add, Mul, Sub}, + ops::{Add, Mul, Sub, Div}, }; use bevy_utils::{thiserror, thiserror::Error}; @@ -13,6 +13,7 @@ use glam::Vec2; /// interpolation. pub trait Point: Mul + + Div + Add + Sub + Add @@ -27,6 +28,7 @@ pub trait Point: impl Point for T where T: Mul + + Div + Add + Sub + Add @@ -364,6 +366,7 @@ pub enum CubicNurbsError { /// ``` pub struct CubicNurbs { control_points: Vec

, + weights: Vec, knot_vector: Vec, } impl CubicNurbs

{ @@ -427,11 +430,12 @@ impl CubicNurbs

{ control_points .iter_mut() - .zip(weights) - .for_each(|(p, w)| *p = *p * w); + .zip(weights.iter()) + .for_each(|(p, w)| *p = *p * *w); Ok(Self { control_points, + weights, knot_vector, }) } @@ -507,29 +511,6 @@ impl CubicNurbs

{ weights.into_iter().map(|w| w * mul).collect() } } -impl CubicGenerator

for CubicNurbs

{ - #[inline] - fn to_curve(&self) -> CubicCurve

{ - let segments = self - .control_points - .windows(4) - .zip(self.knot_vector.windows(8)) - .map(|(points, knot_vector_segment)| { - let knot_vector_segment = knot_vector_segment - .try_into() - .expect("Knot vector windows are of length 8"); - let matrix = Self::generate_matrix(knot_vector_segment); - CubicSegment::coefficients( - points - .try_into() - .expect("Points vector windows are of length 4"), - matrix, - ) - }) - .collect(); - CubicCurve { segments } - } -} /// A spline interpolated linearly between the nearest 2 points. pub struct LinearSpline { @@ -846,6 +827,263 @@ impl IntoIterator for CubicCurve

{ } } +impl RationalGenerator

for CubicNurbs

{ + #[inline] + fn to_curve(&self) -> RationalCurve

{ + let segments = self + .control_points.windows(4) + .zip(self.weights.windows(4)) + .zip(self.knot_vector.windows(8)) + .map(|((points, weights), knot_vector_segment)| { + let knot_vector_segment = knot_vector_segment + .try_into() + .expect("Knot vector windows are of length 8"); + let matrix = Self::generate_matrix(knot_vector_segment); + RationalSegment::coefficients( + points + .try_into() + .expect("Points vector windows are of length 4"), + weights + .try_into() + .expect("Weights vector windows are of length 4"), + matrix, + ) + }) + .collect(); + RationalCurve { segments } + } +} + +/// Implement this on cubic splines that can generate a curve from their spline parameters. +pub trait RationalGenerator { + /// Build a [`RationalCurve`] by computing the interpolation coefficients for each curve segment. + fn to_curve(&self) -> RationalCurve

; +} + +/// A segment of a rational cubic curve, used to hold precomputed coefficients for fast interpolation. +/// +/// Segments can be chained together to form a longer compound curve. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct RationalSegment { + coeff: [P; 4], + weight_coeff: [f32; 4], +} + +impl RationalSegment

{ + /// Instantaneous position of a point at parametric value `t`. + #[inline] + pub fn position(&self, t: f32) -> P { + let [a, b, c, d] = self.coeff; + let [x, y, z, w] = self.weight_coeff; + // Compute a cubic polynomial for the control points + let numerator = a + (b + (c + d * t) * t) * t; + // Compute a cubic polynomial for the weights + let denominator = x + (y + (z + w * t) * t) * t; + numerator / denominator + } + + /// Instantaneous velocity of a point at parametric value `t`. + #[inline] + pub fn velocity(&self, t: f32) -> P { + let [a, b, c, d] = self.coeff; + let [x, y, z, w] = self.weight_coeff; + // Compute a cubic polynomial for the control points + let numerator = a + (b + (c + d * t) * t) * t; + // Compute a cubic polynomial for the weights + let denominator = x + (y + (z + w * t) * t) * t; + + // Compute the derivative of the control point polynomial + let numerator_derivative = b + (c * 2.0 + d * 3.0 * t) * t; + // Compute the derivative of the weight polynomial + let denominator_derivative = y + (z * 2.0 + w * 3.0 * t) * t; + + // Velocity is the first derivative (wrt to the parameter `t`) + // Position = N/D therefore + // Velocity = (N/D)' = N'/D - N * D'/D^2 = (N' * D - N * D')/D^2 + numerator_derivative / denominator - numerator * (denominator_derivative / denominator.powi(2)) + } + + /// Instantaneous acceleration of a point at parametric value `t`. + #[inline] + pub fn acceleration(&self, t: f32) -> P { + let [a, b, c, d] = self.coeff; + let [x, y, z, w] = self.weight_coeff; + // Compute a cubic polynomial for the control points + let numerator = a + (b + (c + d * t) * t) * t; + // Compute a cubic polynomial for the weights + let denominator = x + (y + (z + w * t) * t) * t; + + // Compute the derivative of the control point polynomial + let numerator_derivative = b + (c * 2.0 + d * 3.0 * t) * t; + // Compute the derivative of the weight polynomial + let denominator_derivative = y + (z * 2.0 + w * 3.0 * t) * t; + + // Compute the second derivative of the control point polynomial + let numerator_second_derivative = c * 2.0 + d * 6.0 * t; + // Compute the second derivative of the weight polynomial + let denominator_second_derivative = z * 2.0 + w * 6.0 * t; + + // Velocity is the first derivative (wrt to the parameter `t`) + // Position = N/D therefore + // Velocity = (N/D)' = N'/D - N * D'/D^2 = (N' * D - N * D')/D^2 + // Acceleration = (N/D)'' = ((N' * D - N * D')/D^2)' = N''/D + N' * (-2D'/D^2) + N * (-D''/D^2 + 2D'^2/D^3) + numerator_second_derivative/denominator + + numerator_derivative * (-2.0 * denominator_derivative/denominator.powi(2)) + + numerator * (-denominator_second_derivative/denominator.powi(2) + 2.0 * denominator_derivative.powi(2)/denominator.powi(3)) + } + + /// Calculate polynomial coefficients for the cubic polynomials using a characteristic matrix. + #[inline] + fn coefficients(control_points: [P; 4], weights: [f32; 4], char_matrix: [[f32; 4]; 4]) -> Self { + let [c0, c1, c2, c3] = char_matrix; + let p = control_points; + let w = weights; + // These are the control point polynomial coefficients, computed by multiplying the characteristic + // matrix by the point matrix. + let coeff = [ + p[0] * c0[0] + p[1] * c0[1] + p[2] * c0[2] + p[3] * c0[3], + p[0] * c1[0] + p[1] * c1[1] + p[2] * c1[2] + p[3] * c1[3], + p[0] * c2[0] + p[1] * c2[1] + p[2] * c2[2] + p[3] * c2[3], + p[0] * c3[0] + p[1] * c3[1] + p[2] * c3[2] + p[3] * c3[3], + ]; + // These are the weight polynomial coefficients, computed by multiplying the characteristic + // matrix by the weight matrix. + let weight_coeff = [ + w[0] * c0[0] + w[1] * c0[1] + w[2] * c0[2] + w[3] * c0[3], + w[0] * c1[0] + w[1] * c1[1] + w[2] * c1[2] + w[3] * c1[3], + w[0] * c2[0] + w[1] * c2[1] + w[2] * c2[2] + w[3] * c2[3], + w[0] * c3[0] + w[1] * c3[1] + w[2] * c3[2] + w[3] * c3[3], + ]; + Self { coeff, weight_coeff } + } +} + +/// A collection of [`RationalSegment`]s chained into a curve. +/// +/// Use any struct that implements the [`RationalGenerator`] trait to create a new curve, such as +/// [`CubicNURBS`]. +#[derive(Clone, Debug, PartialEq)] +pub struct RationalCurve { + segments: Vec>, +} + +impl RationalCurve

{ + /// Compute the position of a point on the curve at the parametric value `t`. + /// + /// Note that `t` varies from `0..=(n_points - 3)`. + #[inline] + pub fn position(&self, t: f32) -> P { + let (segment, t) = self.segment(t); + segment.position(t) + } + + /// Compute the first derivative with respect to t at `t`. This is the instantaneous velocity of + /// a point on the curve at `t`. + /// + /// Note that `t` varies from `0..=(n_points - 3)`. + #[inline] + pub fn velocity(&self, t: f32) -> P { + let (segment, t) = self.segment(t); + segment.velocity(t) + } + + /// Compute the second derivative with respect to t at `t`. This is the instantaneous + /// acceleration of a point on the curve at `t`. + /// + /// Note that `t` varies from `0..=(n_points - 3)`. + #[inline] + pub fn acceleration(&self, t: f32) -> P { + let (segment, t) = self.segment(t); + segment.acceleration(t) + } + + /// A flexible iterator used to sample curves with arbitrary functions. + /// + /// This splits the curve into `subdivisions` of evenly spaced `t` values across the + /// length of the curve from start (t = 0) to end (t = n), where `n = self.segment_count()`, + /// returning an iterator evaluating the curve with the supplied `sample_function` at each `t`. + /// + /// For `subdivisions = 2`, this will split the curve into two lines, or three points, and + /// return an iterator with 3 items, the three points, one at the start, middle, and end. + #[inline] + pub fn iter_samples<'a, 'b: 'a>( + &'b self, + subdivisions: usize, + mut sample_function: impl FnMut(&Self, f32) -> P + 'a, + ) -> impl Iterator + 'a { + self.iter_uniformly(subdivisions) + .map(move |t| sample_function(self, t)) + } + + /// An iterator that returns values of `t` uniformly spaced over `0..=subdivisions`. + #[inline] + fn iter_uniformly(&self, subdivisions: usize) -> impl Iterator { + let segments = self.segments.len() as f32; + let step = segments / subdivisions as f32; + (0..=subdivisions).map(move |i| i as f32 * step) + } + + /// The list of segments contained in this `RationalCurve`. + /// + /// This spline's global `t` value is equal to how many segments it has. + /// + /// All method accepting `t` on `RationalCurve` depends on the global `t`. + /// When sampling over the entire curve, you should either use one of the + /// `iter_*` methods or account for the segment count using `curve.segments().len()`. + #[inline] + pub fn segments(&self) -> &[RationalSegment

] { + &self.segments + } + + /// Iterate over the curve split into `subdivisions`, sampling the position at each step. + pub fn iter_positions(&self, subdivisions: usize) -> impl Iterator + '_ { + self.iter_samples(subdivisions, Self::position) + } + + /// Iterate over the curve split into `subdivisions`, sampling the velocity at each step. + pub fn iter_velocities(&self, subdivisions: usize) -> impl Iterator + '_ { + self.iter_samples(subdivisions, Self::velocity) + } + + /// Iterate over the curve split into `subdivisions`, sampling the acceleration at each step. + pub fn iter_accelerations(&self, subdivisions: usize) -> impl Iterator + '_ { + self.iter_samples(subdivisions, Self::acceleration) + } + + #[inline] + /// Adds a segment to the curve + pub fn push_segment(&mut self, segment: RationalSegment

) { + self.segments.push(segment); + } + + /// Returns the [`RationalSegment`] and local `t` value given a spline's global `t` value. + #[inline] + fn segment(&self, t: f32) -> (&RationalSegment

, f32) { + if self.segments.len() == 1 { + (&self.segments[0], t) + } else { + let i = (t.floor() as usize).clamp(0, self.segments.len() - 1); + (&self.segments[i], t - i as f32) + } + } +} + +impl Extend> for RationalCurve

{ + fn extend>>(&mut self, iter: T) { + self.segments.extend(iter); + } +} + +impl IntoIterator for RationalCurve

{ + type IntoIter = > as IntoIterator>::IntoIter; + + type Item = RationalSegment

; + + fn into_iter(self) -> Self::IntoIter { + self.segments.into_iter() + } +} + #[cfg(test)] mod tests { use glam::{vec2, Vec2}; diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index faae8f720c957..85c64dc307245 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -25,7 +25,7 @@ pub mod prelude { pub use crate::{ cubic_splines::{ CubicBSpline, CubicBezier, CubicCardinalSpline, CubicGenerator, CubicHermite, - CubicNurbs, CubicNurbsError, CubicSegment, + CubicNurbs, CubicNurbsError, CubicCurve, CubicSegment, RationalGenerator, RationalCurve, RationalSegment }, primitives::*, BVec2, BVec3, BVec4, EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4, From 93a0d3f5d721380c3700c6939cfb32698f840641 Mon Sep 17 00:00:00 2001 From: IQuick143 Date: Tue, 30 Jan 2024 23:01:31 +0100 Subject: [PATCH 56/82] Change over polynomial evaluation scheme in CubicSegment --- crates/bevy_math/src/cubic_splines.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 7c39bf9285e6b..03ebb1c544a66 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -561,20 +561,23 @@ impl CubicSegment

{ #[inline] pub fn position(&self, t: f32) -> P { let [a, b, c, d] = self.coeff; - a + b * t + c * t.powi(2) + d * t.powi(3) + // Evaluate `a + bt + ct^2 + dt^3` + a + (b + (c + d * t) * t) * t } /// Instantaneous velocity of a point at parametric value `t`. #[inline] pub fn velocity(&self, t: f32) -> P { let [_, b, c, d] = self.coeff; - b + c * 2.0 * t + d * 3.0 * t.powi(2) + // Evaluate the derivative, which is `b + 2ct + 3dt^2` + b + (c * 2.0 + d * 3.0 * t) * t } /// Instantaneous acceleration of a point at parametric value `t`. #[inline] pub fn acceleration(&self, t: f32) -> P { let [_, _, c, d] = self.coeff; + // Evaluate the second derivative, which is `2c + 6dt` c * 2.0 + d * 6.0 * t } From 7a46d5418bd9070973270dade3ea0a791ec95b55 Mon Sep 17 00:00:00 2001 From: IQuick143 Date: Tue, 30 Jan 2024 23:34:18 +0100 Subject: [PATCH 57/82] Implement From for RationalCurve. Add test. --- crates/bevy_math/src/cubic_splines.rs | 59 ++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 03ebb1c544a66..4889fb0460487 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -1087,11 +1087,30 @@ impl IntoIterator for RationalCurve

{ } } +impl From> for RationalSegment

{ + fn from(value: CubicSegment

) -> Self { + Self { + coeff: value.coeff, + weight_coeff: [1.0, 0.0, 0.0, 0.0], + } + } +} + +impl From> for RationalCurve

{ + fn from(value: CubicCurve

) -> Self { + Self { + segments: value.segments.into_iter().map(Into::into).collect() + } + } +} + #[cfg(test)] mod tests { use glam::{vec2, Vec2}; - use crate::cubic_splines::{CubicBezier, CubicGenerator, CubicSegment}; + use crate::cubic_splines::{CubicBezier, CubicBSpline, CubicGenerator, CubicSegment}; + + use super::RationalCurve; /// How close two floats can be and still be considered equal const FLOAT_EQ: f32 = 1e-5; @@ -1154,4 +1173,42 @@ mod tests { assert!(bezier.ease(0.5) < -0.5); assert_eq!(bezier.ease(1.0), 1.0); } + + #[test] + fn cubic_to_rational() { + const EPSILON: f32 = 0.00001; + + let points = [ + vec2(0.0, 0.0), + vec2(1.0, 1.0), + vec2(1.0, 1.0), + vec2(2.0, -1.0), + vec2(3.0, 1.0), + vec2(0.0, 0.0), + ]; + + let b_spline = CubicBSpline::new(points).to_curve(); + let rational_b_spline = RationalCurve::from(b_spline.clone()); + + /// Tests if two vectors of points are approximately the same + fn compare_vectors(cubic_curve: Vec, rational_curve: Vec, name: &str) { + assert_eq!(cubic_curve.len(), rational_curve.len(), "{name} vector lengths mismatch"); + for (a,b) in cubic_curve.iter().zip(rational_curve.iter()) { + assert!(a.distance(*b) < EPSILON, "Mismatch between {name} values CubicCurve: {} Converted RationalCurve: {}", a, b); + } + } + + // Both curves should yield the same values + let cubic_positions: Vec<_> = b_spline.iter_positions(10).collect(); + let rational_positions: Vec<_> = rational_b_spline.iter_positions(10).collect(); + compare_vectors(cubic_positions, rational_positions, "position"); + + let cubic_velocities: Vec<_> = b_spline.iter_velocities(10).collect(); + let rational_velocities: Vec<_> = rational_b_spline.iter_velocities(10).collect(); + compare_vectors(cubic_velocities, rational_velocities, "position"); + + let cubic_accelerations: Vec<_> = b_spline.iter_accelerations(10).collect(); + let rational_accelerations: Vec<_> = rational_b_spline.iter_accelerations(10).collect(); + compare_vectors(cubic_accelerations, rational_accelerations, "position"); + } } From ed1d974d35bdde518314e6296cce4922d5946bd6 Mon Sep 17 00:00:00 2001 From: IQuick 143 Date: Wed, 31 Jan 2024 12:42:04 +0100 Subject: [PATCH 58/82] Fix incorrect test labels. Co-authored-by: JohnTheCoolingFan <43478602+JohnTheCoolingFan@users.noreply.github.com> --- crates/bevy_math/src/cubic_splines.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 4889fb0460487..0bd50202d5c4e 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -1205,10 +1205,10 @@ mod tests { let cubic_velocities: Vec<_> = b_spline.iter_velocities(10).collect(); let rational_velocities: Vec<_> = rational_b_spline.iter_velocities(10).collect(); - compare_vectors(cubic_velocities, rational_velocities, "position"); + compare_vectors(cubic_velocities, rational_velocities, "velocity"); let cubic_accelerations: Vec<_> = b_spline.iter_accelerations(10).collect(); let rational_accelerations: Vec<_> = rational_b_spline.iter_accelerations(10).collect(); - compare_vectors(cubic_accelerations, rational_accelerations, "position"); + compare_vectors(cubic_accelerations, rational_accelerations, "acceleration"); } } From adf9faf8e0d0eb7a1fd1358b7c01dcd3214231a7 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Wed, 31 Jan 2024 20:44:16 +0300 Subject: [PATCH 59/82] Formatting --- crates/bevy_math/src/cubic_splines.rs | 41 ++++++++++++++++++--------- crates/bevy_math/src/lib.rs | 5 ++-- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 9b842a8e55080..017ac0fe69d66 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -3,7 +3,7 @@ use std::{ fmt::Debug, iter::Sum, - ops::{Add, Mul, Sub, Div}, + ops::{Add, Div, Mul, Sub}, }; use bevy_utils::{thiserror, thiserror::Error}; @@ -838,7 +838,8 @@ impl RationalGenerator

for CubicNurbs

{ #[inline] fn to_curve(&self) -> RationalCurve

{ let segments = self - .control_points.windows(4) + .control_points + .windows(4) .zip(self.weights.windows(4)) .zip(self.knot_vector.windows(8)) .map(|((points, weights), knot_vector_segment)| { @@ -907,7 +908,8 @@ impl RationalSegment

{ // Velocity is the first derivative (wrt to the parameter `t`) // Position = N/D therefore // Velocity = (N/D)' = N'/D - N * D'/D^2 = (N' * D - N * D')/D^2 - numerator_derivative / denominator - numerator * (denominator_derivative / denominator.powi(2)) + numerator_derivative / denominator + - numerator * (denominator_derivative / denominator.powi(2)) } /// Instantaneous acceleration of a point at parametric value `t`. @@ -934,9 +936,11 @@ impl RationalSegment

{ // Position = N/D therefore // Velocity = (N/D)' = N'/D - N * D'/D^2 = (N' * D - N * D')/D^2 // Acceleration = (N/D)'' = ((N' * D - N * D')/D^2)' = N''/D + N' * (-2D'/D^2) + N * (-D''/D^2 + 2D'^2/D^3) - numerator_second_derivative/denominator + - numerator_derivative * (-2.0 * denominator_derivative/denominator.powi(2)) + - numerator * (-denominator_second_derivative/denominator.powi(2) + 2.0 * denominator_derivative.powi(2)/denominator.powi(3)) + numerator_second_derivative / denominator + + numerator_derivative * (-2.0 * denominator_derivative / denominator.powi(2)) + + numerator + * (-denominator_second_derivative / denominator.powi(2) + + 2.0 * denominator_derivative.powi(2) / denominator.powi(3)) } /// Calculate polynomial coefficients for the cubic polynomials using a characteristic matrix. @@ -961,7 +965,10 @@ impl RationalSegment

{ w[0] * c2[0] + w[1] * c2[1] + w[2] * c2[2] + w[3] * c2[3], w[0] * c3[0] + w[1] * c3[1] + w[2] * c3[2] + w[3] * c3[3], ]; - Self { coeff, weight_coeff } + Self { + coeff, + weight_coeff, + } } } @@ -1103,7 +1110,7 @@ impl From> for RationalSegment

{ impl From> for RationalCurve

{ fn from(value: CubicCurve

) -> Self { Self { - segments: value.segments.into_iter().map(Into::into).collect() + segments: value.segments.into_iter().map(Into::into).collect(), } } } @@ -1112,9 +1119,8 @@ impl From> for RationalCurve

{ mod tests { use glam::{vec2, Vec2}; - use crate::cubic_splines::{CubicBezier, CubicBSpline, CubicGenerator, CubicSegment}; - use super::RationalCurve; + use crate::cubic_splines::{CubicBSpline, CubicBezier, CubicGenerator, CubicSegment}; /// How close two floats can be and still be considered equal const FLOAT_EQ: f32 = 1e-5; @@ -1196,9 +1202,18 @@ mod tests { /// Tests if two vectors of points are approximately the same fn compare_vectors(cubic_curve: Vec, rational_curve: Vec, name: &str) { - assert_eq!(cubic_curve.len(), rational_curve.len(), "{name} vector lengths mismatch"); - for (a,b) in cubic_curve.iter().zip(rational_curve.iter()) { - assert!(a.distance(*b) < EPSILON, "Mismatch between {name} values CubicCurve: {} Converted RationalCurve: {}", a, b); + assert_eq!( + cubic_curve.len(), + rational_curve.len(), + "{name} vector lengths mismatch" + ); + for (a, b) in cubic_curve.iter().zip(rational_curve.iter()) { + assert!( + a.distance(*b) < EPSILON, + "Mismatch between {name} values CubicCurve: {} Converted RationalCurve: {}", + a, + b + ); } } diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index 85c64dc307245..913ff5f9c42b1 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -24,8 +24,9 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ cubic_splines::{ - CubicBSpline, CubicBezier, CubicCardinalSpline, CubicGenerator, CubicHermite, - CubicNurbs, CubicNurbsError, CubicCurve, CubicSegment, RationalGenerator, RationalCurve, RationalSegment + CubicBSpline, CubicBezier, CubicCardinalSpline, CubicCurve, CubicGenerator, + CubicHermite, CubicNurbs, CubicNurbsError, CubicSegment, RationalCurve, + RationalGenerator, RationalSegment, }, primitives::*, BVec2, BVec3, BVec4, EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4, From ecbc0c4a5ad9027a1cac38835e91ebcc8b39e415 Mon Sep 17 00:00:00 2001 From: Miles Silberling-Cook Date: Wed, 31 Jan 2024 19:57:40 -0500 Subject: [PATCH 60/82] Improved spline documentation --- crates/bevy_math/src/cubic_splines.rs | 185 +++++++++++++++++--------- 1 file changed, 122 insertions(+), 63 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 017ac0fe69d66..08ae0763c78f8 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -47,14 +47,20 @@ impl Point for T where /// [`CubicSegment::new_bezier`] for use in easing. /// /// ### Interpolation -/// The curve only passes through the first and last control point in each set of four points. +/// The curve only passes through the first and last control point in each set of four points. The curve +/// is divided into "segments" by every fourth control point. /// /// ### Tangency -/// Manually defined by the two intermediate control points within each set of four points. +/// Tangents are manually defined by the two intermediate control points within each set of four points. +/// You can think of the control points the curve passes through as "anchors", and as the intermediate +/// control points as the anchors displaced along their tangent vectors /// /// ### Continuity -/// At minimum C0 continuous, up to C2. Continuity greater than C0 can result in a loss of local -/// control over the spline due to the curvature constraints. +/// A Bezier curve is at minimum C0 continuous, meaning it has no holes or jumps. Each curve segment is +/// C2, meaning the tangent vector changes smoothly between each set of four control points, but this +/// doesn't hold at the control points between segments. Making the whole curve C1 or C2 requires moving +/// the intermediate control points to align the tangent vectors between segments, and can result in a +/// loss of local control. /// /// ### Usage /// @@ -84,6 +90,9 @@ impl CubicBezier

{ impl CubicGenerator

for CubicBezier

{ #[inline] fn to_curve(&self) -> CubicCurve

{ + // A derivation for this matrix can be found in "General Matrix Representations for B-splines" by Kaihuai Qin. + // + // See section 4.2 and equation 11. let char_matrix = [ [1., 0., 0., 0.], [-3., 3., 0., 0.], @@ -112,10 +121,11 @@ impl CubicGenerator

for CubicBezier

{ /// The curve passes through every control point. /// /// ### Tangency -/// Explicitly defined at each control point. +/// Tangents are explicitly defined at each control point. /// /// ### Continuity -/// At minimum C0 continuous, up to C1. +/// The curve is at minimum C0 continuous, meaning it has no holes or jumps. It is also C1, meaning the +/// tangent vector has no sudden jumps. /// /// ### Usage /// @@ -182,10 +192,11 @@ impl CubicGenerator

for CubicHermite

{ /// The curve passes through every control point. /// /// ### Tangency -/// Automatically defined at each control point. +/// Tangents are automatically computed based on the position of control points. /// /// ### Continuity -/// C1 continuous. +/// The curve is at minimum C0 continuous, meaning it has no holes or jumps. It is also C1, meaning the +/// tangent vector has no sudden jumps. /// /// ### Usage /// @@ -250,10 +261,11 @@ impl CubicGenerator

for CubicCardinalSpline

{ /// The curve does not pass through control points. /// /// ### Tangency -/// Automatically computed based on the position of control points. +/// Tangents are automatically computed based on the position of control points. /// /// ### Continuity -/// C2 continuous! The acceleration continuity of this spline makes it useful for camera paths. +/// The curve is C2 continuous, meaning it has no holes or jumps, and the tangent vector changes smoothly along +/// the entire curve length. The acceleration continuity of this spline makes it useful for camera paths. /// /// ### Usage /// @@ -282,6 +294,9 @@ impl CubicBSpline

{ impl CubicGenerator

for CubicBSpline

{ #[inline] fn to_curve(&self) -> CubicCurve

{ + // A derivation for this matrix can be found in "General Matrix Representations for B-splines" by Kaihuai Qin. + // + // See section 4.1 and equations 7 and 8. let mut char_matrix = [ [1.0, 4.0, 1.0, 0.0], [-3.0, 0.0, 3.0, 0.0], @@ -334,22 +349,33 @@ pub enum CubicNurbsError { }, } -/// A cubic non-uniform rational B-spline (NURBS). Generates a smooth curve from a -/// sequence of control points by interpolating between four points at a time. +/// Non-uniform Rational B-Splines (NURBS) are powerful generalization of the [`CubicBSpline`] which can +/// represent a much more diverse class of curves (like perfect circles and ellipses). +/// +/// ### Non-uniformity +/// The 'NU' part of NURBS stands for "Non-Uniform". This has to do with a parameter called the Knot +/// Vector. This is a non-decreasing sequence, where each successive pair of knots corresponds to +/// one segment of the final curve. The difference between each pair of knots roughly corresponds to the +/// length of each segment. Multiple repeated knot values are called "Knot multiplicity". Each repeated +/// value causes a "zero-length" segment, which is ignored. +/// +/// ### Rationality +/// The 'R' part of NURBS stands for "Rational". This has to do with NURBS allowing each control point to +/// be assigned a weighting, which controls how much it effects the curve recitative to the other points. /// /// ### Interpolation -/// The knot vector is a non-decreasing sequence that controls which four -/// control points are assigned to each segment of the curve. It can be used to make -/// sharp corners. The curve will not pass through the control points unless the -/// knot vector has the same value four times in a row. +/// The curve will not pass through the control points unless the knot vector has the same value four times +/// in a row. /// -/// ### Curvature -/// The tangents automatically calculated based on the position of the control -/// points. The curve is C2 continuous (meaning both the velocity and -/// acceleration are smooth), making it useful for camera paths and moving objects. -/// The continuity reduces if the curve's knot vector has repeating values, which is called knot -/// multiplicity. Knot multiplicity of 2 would reduce the continuity to C1, multiplicity of 3 would -/// reduce the continuity to C0. +/// ### Tangency +/// Tangents are automatically computed based on the position of control points. +/// +/// ### Continuity +/// When there is no Knot Multiplicity, the curve is C2 continuous, meaning it has no holes or jumps, and the +/// tangent vector changes smoothly along the entire curve length. Like the [`CubicBSpline`], the acceleration +/// continuity makes it useful for camera paths. Knot multiplicity of 2 reduces the continuity to C2, and Knot +/// multiplicity of 3 reduces the continuity to C0. The curve is always at-least C0, meaning it has no jumps +/// or holes. /// /// ### Usage /// @@ -380,7 +406,8 @@ impl CubicNurbs

{ /// vector. Defaults to equal weights. /// /// If provided, the knot vector must have n + 4 elements, where n is the amount of control - /// points. Defaults to open uniform knot vector: [`Self::open_uniform_knot_vector`]. + /// points. Defaults to open uniform knot vector: [`Self::open_uniform_knot_vector`]. A constant + /// knot vector is invalid. /// /// At least 4 points must be provided, otherwise an error will be returned. pub fn new( @@ -488,8 +515,10 @@ impl CubicNurbs

{ control_points_len + 4 } - /// Based on fn generate_matrix(knot_vector_segment: &[f32; 8]) -> [[f32; 4]; 4] { + // A derivation for this matrix can be found in "General Matrix Representations for B-splines" by Kaihuai Qin. + // + // See section 3.1. let t = knot_vector_segment; let m00 = (t[4] - t[3]).powi(2) / ((t[4] - t[2]) * (t[4] - t[1])); let m02 = (t[3] - t[2]).powi(2) / ((t[5] - t[2]) * (t[4] - t[2])); @@ -515,8 +544,44 @@ impl CubicNurbs

{ weights.into_iter().map(|w| w * mul).collect() } } +impl RationalGenerator

for CubicNurbs

{ + #[inline] + fn to_curve(&self) -> RationalCurve

{ + let segments = self + .control_points + .windows(4) + .zip(self.weights.windows(4)) + .zip(self.knot_vector.windows(8)) + .map(|((points, weights), knot_vector_segment)| { + let knot_vector_segment = knot_vector_segment + .try_into() + .expect("Knot vector windows are of length 8"); + let matrix = Self::generate_matrix(knot_vector_segment); + RationalSegment::coefficients( + points + .try_into() + .expect("Points vector windows are of length 4"), + weights + .try_into() + .expect("Weights vector windows are of length 4"), + matrix, + ) + }) + .collect(); + RationalCurve { segments } + } +} /// A spline interpolated linearly between the nearest 2 points. +/// +/// ### Interpolation +/// The curve passes through every control point. +/// +/// ### Tangency +/// The curve is not generally differentiable at control points. +/// +/// ### Continuity +/// The curve is C0 continuous, meaning it has no holes or jumps. pub struct LinearSpline { points: Vec

, } @@ -546,13 +611,14 @@ impl CubicGenerator

for LinearSpline

{ } } -/// Implement this on cubic splines that can generate a curve from their spline parameters. +/// Implement this on cubic splines that can generate a cubic curve from their spline parameters. pub trait CubicGenerator { /// Build a [`CubicCurve`] by computing the interpolation coefficients for each curve segment. fn to_curve(&self) -> CubicCurve

; } /// A segment of a cubic curve, used to hold precomputed coefficients for fast interpolation. +/// Can be evaluated as a parametric curve over the domain `[0, 1)`. /// /// Segments can be chained together to form a longer compound curve. #[derive(Clone, Debug, Default, PartialEq)] @@ -565,7 +631,7 @@ impl CubicSegment

{ #[inline] pub fn position(&self, t: f32) -> P { let [a, b, c, d] = self.coeff; - // Evaluate `a + bt + ct^2 + dt^3` + // Evaluate `a + bt + ct^2 + dt^3`, avoiding exponentiation a + (b + (c + d * t) * t) * t } @@ -573,7 +639,7 @@ impl CubicSegment

{ #[inline] pub fn velocity(&self, t: f32) -> P { let [_, b, c, d] = self.coeff; - // Evaluate the derivative, which is `b + 2ct + 3dt^2` + // Evaluate the derivative, which is `b + 2ct + 3dt^2`, avoiding exponentiation b + (c * 2.0 + d * 3.0 * t) * t } @@ -708,7 +774,8 @@ impl CubicSegment { } } -/// A collection of [`CubicSegment`]s chained into a curve. +/// A collection of [`CubicSegment`]s chained into a single parametric curve. Has domain `[0, N)` +/// where `N` is the number of attached segments. /// /// Use any struct that implements the [`CubicGenerator`] trait to create a new curve, such as /// [`CubicBezier`]. @@ -834,51 +901,28 @@ impl IntoIterator for CubicCurve

{ } } -impl RationalGenerator

for CubicNurbs

{ - #[inline] - fn to_curve(&self) -> RationalCurve

{ - let segments = self - .control_points - .windows(4) - .zip(self.weights.windows(4)) - .zip(self.knot_vector.windows(8)) - .map(|((points, weights), knot_vector_segment)| { - let knot_vector_segment = knot_vector_segment - .try_into() - .expect("Knot vector windows are of length 8"); - let matrix = Self::generate_matrix(knot_vector_segment); - RationalSegment::coefficients( - points - .try_into() - .expect("Points vector windows are of length 4"), - weights - .try_into() - .expect("Weights vector windows are of length 4"), - matrix, - ) - }) - .collect(); - RationalCurve { segments } - } -} - -/// Implement this on cubic splines that can generate a curve from their spline parameters. +/// Implement this on cubic splines that can generate a rational cubic curve from their spline parameters. pub trait RationalGenerator { /// Build a [`RationalCurve`] by computing the interpolation coefficients for each curve segment. fn to_curve(&self) -> RationalCurve

; } /// A segment of a rational cubic curve, used to hold precomputed coefficients for fast interpolation. +/// Can be evaluted as a parametric curve over the domain `[0, knot_span)`. /// /// Segments can be chained together to form a longer compound curve. #[derive(Clone, Debug, Default, PartialEq)] pub struct RationalSegment { + /// The coefficents matrix of the cubic curve. coeff: [P; 4], + /// The homogenious weight coefficients. weight_coeff: [f32; 4], + /// The width of the domain of this segment. + knot_span: f32, } impl RationalSegment

{ - /// Instantaneous position of a point at parametric value `t`. + /// Instantaneous position of a point at parametric value `t` in `[0, knot_span)`. #[inline] pub fn position(&self, t: f32) -> P { let [a, b, c, d] = self.coeff; @@ -890,9 +934,12 @@ impl RationalSegment

{ numerator / denominator } - /// Instantaneous velocity of a point at parametric value `t`. + /// Instantaneous velocity of a point at parametric value `t` in `[0, knot_span)`. #[inline] pub fn velocity(&self, t: f32) -> P { + // A derivation for the following equations can be found in "Matrix representation for NURBS + // curves and surfaces" by Choi et al. See equation 19. + let [a, b, c, d] = self.coeff; let [x, y, z, w] = self.weight_coeff; // Compute a cubic polynomial for the control points @@ -912,9 +959,15 @@ impl RationalSegment

{ - numerator * (denominator_derivative / denominator.powi(2)) } - /// Instantaneous acceleration of a point at parametric value `t`. + /// Instantaneous acceleration of a point at parametric value `t` in `[0, knot_span)`. #[inline] pub fn acceleration(&self, t: f32) -> P { + // A derivation for the following equations can be found in "Matrix representation for NURBS + // curves and surfaces" by Choi et al. See equation 20. Note: In come copies of this paper, equation 20 + // is printed with the following two errors: + // + The first term has incorrect sign. + // + The second term should uses R when it should use the first dericative. + let [a, b, c, d] = self.coeff; let [x, y, z, w] = self.weight_coeff; // Compute a cubic polynomial for the control points @@ -946,6 +999,9 @@ impl RationalSegment

{ /// Calculate polynomial coefficients for the cubic polynomials using a characteristic matrix. #[inline] fn coefficients(control_points: [P; 4], weights: [f32; 4], char_matrix: [[f32; 4]; 4]) -> Self { + // An explanation of this use can be found in "Matrix representation for NURBS curves and surfaces" + // by Choi et al. See section "Evaluation of NURB Curves and Surfaces", and equation 16. + let [c0, c1, c2, c3] = char_matrix; let p = control_points; let w = weights; @@ -968,14 +1024,16 @@ impl RationalSegment

{ Self { coeff, weight_coeff, + knot_span: 0.0, // TODO: Actually calculate the correct knot span here. } } } -/// A collection of [`RationalSegment`]s chained into a curve. +/// A collection of [`RationalSegment`]s chained into a single parametric curve. Has domain `[0, N)` where +/// `N` is at-least the number of segments. /// /// Use any struct that implements the [`RationalGenerator`] trait to create a new curve, such as -/// [`CubicNURBS`]. +/// [`CubicNURBS`], or convert [`CubicCurve`] using `into/from`. #[derive(Clone, Debug, PartialEq)] pub struct RationalCurve { segments: Vec>, @@ -1103,6 +1161,7 @@ impl From> for RationalSegment

{ Self { coeff: value.coeff, weight_coeff: [1.0, 0.0, 0.0, 0.0], + knot_span: 1.0, // Cubic curves are uniform, so every segment has domain [0, 1). } } } From 4a4c4183322340c7d749086b2b538bfaabd7b729 Mon Sep 17 00:00:00 2001 From: Miles Silberling-Cook Date: Thu, 1 Feb 2024 15:45:22 -0500 Subject: [PATCH 61/82] Fix nonlinearity and clean up language --- crates/bevy_math/src/cubic_splines.rs | 288 ++++++++++++++++---------- 1 file changed, 182 insertions(+), 106 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 08ae0763c78f8..b204d7a2c9dfb 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -321,30 +321,33 @@ impl CubicGenerator

for CubicBSpline

{ /// Error during construction of [`CubicNurbs`] #[derive(Debug, Error)] pub enum CubicNurbsError { - /// Provided knot vector had an invalid length. - #[error("Invalid knot vector length: expected {expected}, provided {provided}")] - InvalidKnotVectorLength { - /// Expected knot vector length + /// Provided the wrong number of knots. + #[error("Wrong number of knots: expected {expected}, provided {provided}")] + KnotsMismatch { + /// Expected number of knots expected: usize, - /// Provided knot vector length + /// Provided number of knots provided: usize, }, - /// Knot vector has invalid values. Values of a knot vector must be nondescending, meaning the - /// next element must be greater than or equal to the previous one. - #[error("Invalid knot vector values: elements are not nondescending")] - InvalidKnotVectorValues, - /// Provided weights vector didn't have the same amount of values as the control points vector. - #[error("Invalid weights vector length: expected {expected}, provided {provided}")] - WeightsVectorMismatch { - /// Expected weights vector length + /// The provided knots had a descending knot pair. Subsequent knots must + /// either increase or stay the same. + #[error("Invalid knots: contains descending knot pair")] + DescendingKnots, + /// The provided knots were all equal. Knots must contain at-least one increasing pair. + #[error("Invalid knots: all knots are equal")] + ConstantKnots, + /// Provided a different number of weights and control points. + #[error("Incorect number of weights: expected {expected}, provided {provided}")] + WeightsMismatch { + /// Expected number of weights expected: usize, - /// Provided weights vector length + /// Provided number of weights provided: usize, }, - /// The amount of control points provided is less than 4. + /// The number of control points provided is less than 4. #[error("Not enough control points, at least 4 are required, {provided} were provided")] NotEnoughControlPoints { - /// The amount of control points provided + /// The number of control points provided provided: usize, }, } @@ -353,29 +356,29 @@ pub enum CubicNurbsError { /// represent a much more diverse class of curves (like perfect circles and ellipses). /// /// ### Non-uniformity -/// The 'NU' part of NURBS stands for "Non-Uniform". This has to do with a parameter called the Knot -/// Vector. This is a non-decreasing sequence, where each successive pair of knots corresponds to -/// one segment of the final curve. The difference between each pair of knots roughly corresponds to the -/// length of each segment. Multiple repeated knot values are called "Knot multiplicity". Each repeated -/// value causes a "zero-length" segment, which is ignored. +/// The 'NU' part of NURBS stands for "Non-Uniform". This has to do with a parameter called 'knots'. +/// The knots are a non-decreasing sequence of floating point numbers. The first and last three pairs of +/// knots controll the behavior of the curve as it approaches it's endpoints. The intermediate pairs +/// each control the length of one segment of the curve. Multiple repeated knot values are called +/// "knot multiplicity". Knot multiplicity in the intermediate knots causes a "zero-length" segments, +/// and can create sharp corners. /// /// ### Rationality /// The 'R' part of NURBS stands for "Rational". This has to do with NURBS allowing each control point to /// be assigned a weighting, which controls how much it effects the curve recitative to the other points. /// /// ### Interpolation -/// The curve will not pass through the control points unless the knot vector has the same value four times -/// in a row. +/// The curve will not pass through the control points except where a knot has multiplicity four. /// /// ### Tangency /// Tangents are automatically computed based on the position of control points. /// /// ### Continuity -/// When there is no Knot Multiplicity, the curve is C2 continuous, meaning it has no holes or jumps, and the +/// When there is no knot multiplicity, the curve is C2 continuous, meaning it has no holes or jumps and the /// tangent vector changes smoothly along the entire curve length. Like the [`CubicBSpline`], the acceleration -/// continuity makes it useful for camera paths. Knot multiplicity of 2 reduces the continuity to C2, and Knot -/// multiplicity of 3 reduces the continuity to C0. The curve is always at-least C0, meaning it has no jumps -/// or holes. +/// continuity makes it useful for camera paths. Knot multiplicity of 2 in intermediate knots reduces the +/// continuity to C2, and Knot multiplicity of 3 reduces the continuity to C0. The curve is always at-least +/// C0, meaning it has no jumps or holes. /// /// ### Usage /// @@ -388,8 +391,8 @@ pub enum CubicNurbsError { /// vec2(9.0, 8.0), /// ]; /// let weights = [1.0, 1.0, 2.0, 1.0]; -/// let knot_vector = [0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]; -/// let nurbs = CubicNurbs::new(points, Some(weights), Some(knot_vector)) +/// let knots = [0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]; +/// let nurbs = CubicNurbs::new(points, Some(weights), Some(knots)) /// .expect("NURBS construction failed!") /// .to_curve(); /// let positions: Vec<_> = nurbs.iter_positions(100).collect(); @@ -397,23 +400,22 @@ pub enum CubicNurbsError { pub struct CubicNurbs { control_points: Vec

, weights: Vec, - knot_vector: Vec, + knots: Vec, } impl CubicNurbs

{ /// Build a Non-Uniform Rational B-Spline. /// - /// If provided, weights vector must have the same amount of items as the control points - /// vector. Defaults to equal weights. + /// If provided, weights must be the same length as the control points. Defaults to equal weights. /// - /// If provided, the knot vector must have n + 4 elements, where n is the amount of control - /// points. Defaults to open uniform knot vector: [`Self::open_uniform_knot_vector`]. A constant - /// knot vector is invalid. + /// If provided, the number of knots must be n + 4 elements, where n is the amount of control + /// points. Defaults to open uniform knots: [`Self::open_uniform_knots`]. Knots cannot + /// all be equal. /// /// At least 4 points must be provided, otherwise an error will be returned. pub fn new( control_points: impl Into>, weights: Option>>, - knot_vector: Option>>, + knots: Option>>, ) -> Result { let mut control_points: Vec

= control_points.into(); let control_points_len = control_points.len(); @@ -424,40 +426,55 @@ impl CubicNurbs

{ }); } - let mut weights = weights + let weights = weights .map(Into::into) .unwrap_or_else(|| vec![1.0; control_points_len]); - let knot_vector: Vec = knot_vector.map(Into::into).unwrap_or_else(|| { - Self::open_uniform_knot_vector(control_points_len) + let mut knots: Vec = knots.map(Into::into).unwrap_or_else(|| { + Self::open_uniform_knots(control_points_len) .expect("The amount of control points was checked") }); - let knot_vector_expected_length = Self::knot_vector_length(control_points_len); + let expected_knots_len = Self::knots_len(control_points_len); - // Check the knot vector length - if knot_vector.len() != knot_vector_expected_length { - return Err(CubicNurbsError::InvalidKnotVectorLength { - expected: knot_vector_expected_length, - provided: knot_vector.len(), + // Check the number of knots is correct + if knots.len() != expected_knots_len { + return Err(CubicNurbsError::KnotsMismatch { + expected: expected_knots_len, + provided: knots.len(), }); } - // Check the knot vector for being nondescending (previous elements is less than or equal + // Ensure the knots are non-descending (previous elements is less than or equal // to the next) - if knot_vector.windows(2).any(|win| win[0] > win[1]) { - return Err(CubicNurbsError::InvalidKnotVectorValues); + if knots.windows(2).any(|win| win[0] > win[1]) { + return Err(CubicNurbsError::DescendingKnots); } - // Check the weights vector length + // Ensure the knots are non-constant + if knots.windows(2).all(|win| win[0] == win[1]) { + return Err(CubicNurbsError::ConstantKnots); + } + + // Check that the number of weights equals the number of control points if weights.len() != control_points_len { - return Err(CubicNurbsError::WeightsVectorMismatch { + return Err(CubicNurbsError::WeightsMismatch { expected: control_points_len, provided: weights.len(), }); } - weights = Self::normalize_weights(weights); + // To align the evaluation behavior of nurbs with the other splines, + // make the intervals between knots form an exact cover of [0, N], where N is + // the number of segments of the final curve. + let curve_length = (control_points.len() - 3) as f32; + let min = knots.first().unwrap().clone(); + let max = knots.last().unwrap().clone(); + knots = knots + .into_iter() + .map(|k| k - min) + .map(|k| k * curve_length / max) + .collect(); control_points .iter_mut() @@ -467,36 +484,34 @@ impl CubicNurbs

{ Ok(Self { control_points, weights, - knot_vector, + knots, }) } - /// Generates a uniform knot vector that will generate the same curve as [`CubicBSpline`]. + /// Generates uniform knots that will generate the same curve as [`CubicBSpline`]. /// - /// "Uniform" means that the difference between two knot values next to each other is the same - /// through the entire knot vector. + /// "Uniform" means that the difference between two subsequent knots is the same. /// - /// Will return `None` if there are less than 4 control points - pub fn uniform_knot_vector(control_points: usize) -> Option> { + /// Will return `None` if there are less than 4 control points. + pub fn uniform_knots(control_points: usize) -> Option> { if control_points < 4 { return None; } Some( - (0..Self::knot_vector_length(control_points)) + (0..Self::knots_len(control_points)) .map(|v| v as f32) .collect(), ) } - /// Generates an open uniform knot vector, which makes the ends of the curve pass through the + /// Generates open uniform knots, which makes the ends of the curve pass through the /// start and end points. /// - /// The knot vector will have a knot with multiplicity of 4 at the end and start and elements - /// in the middle will have a difference of 1. "Multiplicity" means that there are N - /// consecutive elements that have the same value. + /// The start and end knots have multiplicity 4, and intermediate knots have multiplicity 0 and + /// difference of 1. "Multiplicity" means that there are N consecutive elements that have the same value. /// /// Will return `None` if there are less than 4 control points - pub fn open_uniform_knot_vector(control_points: usize) -> Option> { + pub fn open_uniform_knots(control_points: usize) -> Option> { if control_points < 4 { return None; } @@ -511,15 +526,27 @@ impl CubicNurbs

{ } #[inline(always)] - const fn knot_vector_length(control_points_len: usize) -> usize { + const fn knots_len(control_points_len: usize) -> usize { control_points_len + 4 } - fn generate_matrix(knot_vector_segment: &[f32; 8]) -> [[f32; 4]; 4] { + /// Generates a nonuniform B-spline charictartistic matrix from a sequence of six knots. Each six + /// knots describe the relationship between four successive controll points. For padding reasons, + /// this takes a vector of 8 knots, but only six are actually used. + fn generate_matrix(knots: &[f32; 8]) -> [[f32; 4]; 4] { // A derivation for this matrix can be found in "General Matrix Representations for B-splines" by Kaihuai Qin. // // See section 3.1. - let t = knot_vector_segment; + + let t = knots; + // In the notation of the paper: + // t[1] := t_i-2 + // t[2] := t_i-1 + // t[3] := t_i (the lower extent of the current knot span) + // t[4] := t_i+1 (the upper extent of the current knot span) + // t[5] := t_i+2 + // t[6] := t_i+3 + let m00 = (t[4] - t[3]).powi(2) / ((t[4] - t[2]) * (t[4] - t[1])); let m02 = (t[3] - t[2]).powi(2) / ((t[5] - t[2]) * (t[4] - t[2])); let m12 = (3.0 * (t[4] - t[3]) * (t[3] - t[2])) / ((t[5] - t[2]) * (t[4] - t[2])); @@ -533,16 +560,6 @@ impl CubicNurbs

{ [-m00, m00 - m32 - m33, m32, m33], ] } - - /// Normalizes weights vector using L0 norm. - /// The resulting weight vector's values will add up to be equal the amount of values in the - /// weights vector - fn normalize_weights(weights: Vec) -> Vec { - let g = weights.len() as f32; - let weights_sum: f32 = weights.iter().sum(); - let mul = g / weights_sum; - weights.into_iter().map(|w| w * mul).collect() - } } impl RationalGenerator

for CubicNurbs

{ #[inline] @@ -551,19 +568,20 @@ impl RationalGenerator

for CubicNurbs

{ .control_points .windows(4) .zip(self.weights.windows(4)) - .zip(self.knot_vector.windows(8)) - .map(|((points, weights), knot_vector_segment)| { - let knot_vector_segment = knot_vector_segment - .try_into() - .expect("Knot vector windows are of length 8"); - let matrix = Self::generate_matrix(knot_vector_segment); + .zip(self.knots.windows(8)) + .filter(|(_, knots)| knots[4] - knots[3] > 0.0) + .map(|((points, weights), knots)| { + // This is curve segment i. It uses control points P_i, P_i+2, P_i+2 and P_i+3, + // It is associated with knot span i+3 (which is the interval between knots i+3 + // and i+4) and it's charictartistic matrix uses knots i+1 through i+6 (because + // those define the two knot spans on either side). + let span = knots[4] - knots[3]; + let coefficent_knots = knots.try_into().expect("Knot windows are of length 6"); + let matrix = Self::generate_matrix(coefficent_knots); RationalSegment::coefficients( - points - .try_into() - .expect("Points vector windows are of length 4"), - weights - .try_into() - .expect("Weights vector windows are of length 4"), + points.try_into().expect("Point windows are of length 4"), + weights.try_into().expect("Weight windows are of length 4"), + span, matrix, ) }) @@ -908,14 +926,14 @@ pub trait RationalGenerator { } /// A segment of a rational cubic curve, used to hold precomputed coefficients for fast interpolation. -/// Can be evaluted as a parametric curve over the domain `[0, knot_span)`. +/// Can be evaluated as a parametric curve over the domain `[0, knot_span)`. /// /// Segments can be chained together to form a longer compound curve. #[derive(Clone, Debug, Default, PartialEq)] pub struct RationalSegment { - /// The coefficents matrix of the cubic curve. + /// The coefficients matrix of the cubic curve. coeff: [P; 4], - /// The homogenious weight coefficients. + /// The homogeneous weight coefficients. weight_coeff: [f32; 4], /// The width of the domain of this segment. knot_span: f32, @@ -966,7 +984,7 @@ impl RationalSegment

{ // curves and surfaces" by Choi et al. See equation 20. Note: In come copies of this paper, equation 20 // is printed with the following two errors: // + The first term has incorrect sign. - // + The second term should uses R when it should use the first dericative. + // + The second term should uses R when it should use the first derivative. let [a, b, c, d] = self.coeff; let [x, y, z, w] = self.weight_coeff; @@ -998,7 +1016,12 @@ impl RationalSegment

{ /// Calculate polynomial coefficients for the cubic polynomials using a characteristic matrix. #[inline] - fn coefficients(control_points: [P; 4], weights: [f32; 4], char_matrix: [[f32; 4]; 4]) -> Self { + fn coefficients( + control_points: [P; 4], + weights: [f32; 4], + knot_span: f32, + char_matrix: [[f32; 4]; 4], + ) -> Self { // An explanation of this use can be found in "Matrix representation for NURBS curves and surfaces" // by Choi et al. See section "Evaluation of NURB Curves and Surfaces", and equation 16. @@ -1024,7 +1047,7 @@ impl RationalSegment

{ Self { coeff, weight_coeff, - knot_span: 0.0, // TODO: Actually calculate the correct knot span here. + knot_span, } } } @@ -1090,8 +1113,8 @@ impl RationalCurve

{ /// An iterator that returns values of `t` uniformly spaced over `0..=subdivisions`. #[inline] fn iter_uniformly(&self, subdivisions: usize) -> impl Iterator { - let segments = self.segments.len() as f32; - let step = segments / subdivisions as f32; + let domain = self.domain(); + let step = domain / subdivisions as f32; (0..=subdivisions).map(move |i| i as f32 * step) } @@ -1122,22 +1145,42 @@ impl RationalCurve

{ self.iter_samples(subdivisions, Self::acceleration) } + /// Adds a segment to the curve. #[inline] - /// Adds a segment to the curve pub fn push_segment(&mut self, segment: RationalSegment

) { self.segments.push(segment); } /// Returns the [`RationalSegment`] and local `t` value given a spline's global `t` value. + /// Input `t` will be clamped to the domain of the curve. Returned value will be in `[0, 1]`. #[inline] - fn segment(&self, t: f32) -> (&RationalSegment

, f32) { - if self.segments.len() == 1 { - (&self.segments[0], t) + fn segment(&self, mut t: f32) -> (&RationalSegment

, f32) { + if t <= 0.0 { + (&self.segments[0], 0.0) + } else if self.segments.len() == 1 { + (&self.segments[0], t / self.segments[0].knot_span) } else { - let i = (t.floor() as usize).clamp(0, self.segments.len() - 1); - (&self.segments[i], t - i as f32) + // Try to fit t into each segment domain + for segment in self.segments.iter() { + if t < segment.knot_span { + // The division here makes t a normalized parameter in [0, 1] that can be properly + // evaluated against a cubic curve segment. See equations 6 & 16 from "Matrix representation + // of NURBS curves and surfaces" by Choi et al. or equation 3 from "General Matrix + // Representations for B-Splines" by Qin. + return (segment, t / segment.knot_span); + } else { + t = t - segment.knot_span + } + } + return (self.segments.last().unwrap(), 1.0); } } + + /// Returns the length of of the domain of the parametric curve. + #[inline] + fn domain(&self) -> f32 { + self.segments.iter().map(|segment| segment.knot_span).sum() + } } impl Extend> for RationalCurve

{ @@ -1178,8 +1221,10 @@ impl From> for RationalCurve

{ mod tests { use glam::{vec2, Vec2}; - use super::RationalCurve; - use crate::cubic_splines::{CubicBSpline, CubicBezier, CubicGenerator, CubicSegment}; + use crate::cubic_splines::{ + CubicBSpline, CubicBezier, CubicGenerator, CubicNurbs, CubicSegment, RationalCurve, + RationalGenerator, + }; /// How close two floats can be and still be considered equal const FLOAT_EQ: f32 = 1e-5; @@ -1243,6 +1288,8 @@ mod tests { assert_eq!(bezier.ease(1.0), 1.0); } + /// Test that RationalCurve properly generalizes CubicCurve. A Cubic upgraded to a rational + /// should produce pretty much the same output. #[test] fn cubic_to_rational() { const EPSILON: f32 = 0.00001; @@ -1266,10 +1313,10 @@ mod tests { rational_curve.len(), "{name} vector lengths mismatch" ); - for (a, b) in cubic_curve.iter().zip(rational_curve.iter()) { + for (i, (a, b)) in cubic_curve.iter().zip(rational_curve.iter()).enumerate() { assert!( a.distance(*b) < EPSILON, - "Mismatch between {name} values CubicCurve: {} Converted RationalCurve: {}", + "Mismatch at {name} value {i}. CubicCurve: {} Converted RationalCurve: {}", a, b ); @@ -1289,4 +1336,33 @@ mod tests { let rational_accelerations: Vec<_> = rational_b_spline.iter_accelerations(10).collect(); compare_vectors(cubic_accelerations, rational_accelerations, "acceleration"); } + + /// Test that a curbs curve can approximate a portion of a circle. + #[test] + fn nurbs_circular_arc() { + use std::f32::consts::SQRT_2; + const EPSILON: f32 = 0.1; + // It's not an especially precise approximation because all our nurbs are cubic. + // A quadratic or quintic curve rational would be exact. it just needs to be better + // than a raw B-spline. + + let points = [ + vec2(1.0, 0.0), + vec2(1.0, 1.0), + vec2(0.0, 1.0), + vec2(-1.0, 1.0), + vec2(-1.0, 0.0), + ]; + + let weights = vec![1., SQRT_2 / 2., 1., SQRT_2 / 2., 1.]; + let spline = CubicNurbs::new(points, Some(weights), None as Option>).unwrap(); + let curve = spline.to_curve(); + for (i, point) in curve.iter_positions(10).enumerate() { + assert!( + point.length() - 1.0 < EPSILON, + "Point {i} is not on the unit circle: {point:?} has length {}", + point.length() + ) + } + } } From f38e62a3ab1f4b9134bd063ef0e8abd77b6bef5e Mon Sep 17 00:00:00 2001 From: Miles Silberling-Cook Date: Fri, 2 Feb 2024 15:10:45 -0500 Subject: [PATCH 62/82] Fix typoes and CI --- crates/bevy_math/src/cubic_splines.rs | 33 +++++++++++++-------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index b204d7a2c9dfb..daa68f0eb5688 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -192,7 +192,7 @@ impl CubicGenerator

for CubicHermite

{ /// The curve passes through every control point. /// /// ### Tangency -/// Tangents are automatically computed based on the position of control points. +/// Tangents are automatically computed based on the positions of control points. /// /// ### Continuity /// The curve is at minimum C0 continuous, meaning it has no holes or jumps. It is also C1, meaning the @@ -323,7 +323,7 @@ impl CubicGenerator

for CubicBSpline

{ pub enum CubicNurbsError { /// Provided the wrong number of knots. #[error("Wrong number of knots: expected {expected}, provided {provided}")] - KnotsMismatch { + KnotsNumberMismatch { /// Expected number of knots expected: usize, /// Provided number of knots @@ -333,12 +333,12 @@ pub enum CubicNurbsError { /// either increase or stay the same. #[error("Invalid knots: contains descending knot pair")] DescendingKnots, - /// The provided knots were all equal. Knots must contain at-least one increasing pair. + /// The provided knots were all equal. Knots must contain at least one increasing pair. #[error("Invalid knots: all knots are equal")] ConstantKnots, /// Provided a different number of weights and control points. #[error("Incorect number of weights: expected {expected}, provided {provided}")] - WeightsMismatch { + WeightsNumberMismatch { /// Expected number of weights expected: usize, /// Provided number of weights @@ -358,14 +358,14 @@ pub enum CubicNurbsError { /// ### Non-uniformity /// The 'NU' part of NURBS stands for "Non-Uniform". This has to do with a parameter called 'knots'. /// The knots are a non-decreasing sequence of floating point numbers. The first and last three pairs of -/// knots controll the behavior of the curve as it approaches it's endpoints. The intermediate pairs +/// knots control the behavior of the curve as it approaches it's endpoints. The intermediate pairs /// each control the length of one segment of the curve. Multiple repeated knot values are called /// "knot multiplicity". Knot multiplicity in the intermediate knots causes a "zero-length" segments, /// and can create sharp corners. /// /// ### Rationality /// The 'R' part of NURBS stands for "Rational". This has to do with NURBS allowing each control point to -/// be assigned a weighting, which controls how much it effects the curve recitative to the other points. +/// be assigned a weighting, which controls how much it effects the curve compared to the other points. /// /// ### Interpolation /// The curve will not pass through the control points except where a knot has multiplicity four. @@ -377,7 +377,7 @@ pub enum CubicNurbsError { /// When there is no knot multiplicity, the curve is C2 continuous, meaning it has no holes or jumps and the /// tangent vector changes smoothly along the entire curve length. Like the [`CubicBSpline`], the acceleration /// continuity makes it useful for camera paths. Knot multiplicity of 2 in intermediate knots reduces the -/// continuity to C2, and Knot multiplicity of 3 reduces the continuity to C0. The curve is always at-least +/// continuity to C2, and Knot multiplicity of 3 reduces the continuity to C0. The curve is always at least /// C0, meaning it has no jumps or holes. /// /// ### Usage @@ -439,7 +439,7 @@ impl CubicNurbs

{ // Check the number of knots is correct if knots.len() != expected_knots_len { - return Err(CubicNurbsError::KnotsMismatch { + return Err(CubicNurbsError::KnotsNumberMismatch { expected: expected_knots_len, provided: knots.len(), }); @@ -458,7 +458,7 @@ impl CubicNurbs

{ // Check that the number of weights equals the number of control points if weights.len() != control_points_len { - return Err(CubicNurbsError::WeightsMismatch { + return Err(CubicNurbsError::WeightsNumberMismatch { expected: control_points_len, provided: weights.len(), }); @@ -468,8 +468,8 @@ impl CubicNurbs

{ // make the intervals between knots form an exact cover of [0, N], where N is // the number of segments of the final curve. let curve_length = (control_points.len() - 3) as f32; - let min = knots.first().unwrap().clone(); - let max = knots.last().unwrap().clone(); + let min = *knots.first().unwrap(); + let max = *knots.last().unwrap(); knots = knots .into_iter() .map(|k| k - min) @@ -508,7 +508,7 @@ impl CubicNurbs

{ /// start and end points. /// /// The start and end knots have multiplicity 4, and intermediate knots have multiplicity 0 and - /// difference of 1. "Multiplicity" means that there are N consecutive elements that have the same value. + /// difference of 1. /// /// Will return `None` if there are less than 4 control points pub fn open_uniform_knots(control_points: usize) -> Option> { @@ -984,7 +984,7 @@ impl RationalSegment

{ // curves and surfaces" by Choi et al. See equation 20. Note: In come copies of this paper, equation 20 // is printed with the following two errors: // + The first term has incorrect sign. - // + The second term should uses R when it should use the first derivative. + // + The second term uses R when it should use the first derivative. let [a, b, c, d] = self.coeff; let [x, y, z, w] = self.weight_coeff; @@ -1052,8 +1052,7 @@ impl RationalSegment

{ } } -/// A collection of [`RationalSegment`]s chained into a single parametric curve. Has domain `[0, N)` where -/// `N` is at-least the number of segments. +/// A collection of [`RationalSegment`]s chained into a single parametric curve. /// /// Use any struct that implements the [`RationalGenerator`] trait to create a new curve, such as /// [`CubicNURBS`], or convert [`CubicCurve`] using `into/from`. @@ -1169,7 +1168,7 @@ impl RationalCurve

{ // Representations for B-Splines" by Qin. return (segment, t / segment.knot_span); } else { - t = t - segment.knot_span + t -= segment.knot_span; } } return (self.segments.last().unwrap(), 1.0); @@ -1178,7 +1177,7 @@ impl RationalCurve

{ /// Returns the length of of the domain of the parametric curve. #[inline] - fn domain(&self) -> f32 { + pub fn domain(&self) -> f32 { self.segments.iter().map(|segment| segment.knot_span).sum() } } From 3c295fda15cbaf948434dd4c76c63036d362db9c Mon Sep 17 00:00:00 2001 From: Miles Silberling-Cook Date: Fri, 2 Feb 2024 16:28:02 -0500 Subject: [PATCH 63/82] Remove circular arc test case --- crates/bevy_math/src/cubic_splines.rs | 32 +-------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index daa68f0eb5688..04910810b89e8 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -1221,8 +1221,7 @@ mod tests { use glam::{vec2, Vec2}; use crate::cubic_splines::{ - CubicBSpline, CubicBezier, CubicGenerator, CubicNurbs, CubicSegment, RationalCurve, - RationalGenerator, + CubicBSpline, CubicBezier, CubicGenerator, CubicSegment, RationalCurve, }; /// How close two floats can be and still be considered equal @@ -1335,33 +1334,4 @@ mod tests { let rational_accelerations: Vec<_> = rational_b_spline.iter_accelerations(10).collect(); compare_vectors(cubic_accelerations, rational_accelerations, "acceleration"); } - - /// Test that a curbs curve can approximate a portion of a circle. - #[test] - fn nurbs_circular_arc() { - use std::f32::consts::SQRT_2; - const EPSILON: f32 = 0.1; - // It's not an especially precise approximation because all our nurbs are cubic. - // A quadratic or quintic curve rational would be exact. it just needs to be better - // than a raw B-spline. - - let points = [ - vec2(1.0, 0.0), - vec2(1.0, 1.0), - vec2(0.0, 1.0), - vec2(-1.0, 1.0), - vec2(-1.0, 0.0), - ]; - - let weights = vec![1., SQRT_2 / 2., 1., SQRT_2 / 2., 1.]; - let spline = CubicNurbs::new(points, Some(weights), None as Option>).unwrap(); - let curve = spline.to_curve(); - for (i, point) in curve.iter_positions(10).enumerate() { - assert!( - point.length() - 1.0 < EPSILON, - "Point {i} is not on the unit circle: {point:?} has length {}", - point.length() - ) - } - } } From 48fbb7d03c7fe8cd8d1995acad7adff16d51eb07 Mon Sep 17 00:00:00 2001 From: Miles Silberling-Cook Date: Fri, 2 Feb 2024 22:35:03 -0500 Subject: [PATCH 64/82] Re-add corrected circular arc test --- crates/bevy_math/src/cubic_splines.rs | 37 ++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 04910810b89e8..81ac7a0000f88 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -1221,7 +1221,8 @@ mod tests { use glam::{vec2, Vec2}; use crate::cubic_splines::{ - CubicBSpline, CubicBezier, CubicGenerator, CubicSegment, RationalCurve, + CubicBSpline, CubicBezier, CubicGenerator, CubicNurbs, CubicSegment, RationalCurve, + RationalGenerator, }; /// How close two floats can be and still be considered equal @@ -1334,4 +1335,38 @@ mod tests { let rational_accelerations: Vec<_> = rational_b_spline.iter_accelerations(10).collect(); compare_vectors(cubic_accelerations, rational_accelerations, "acceleration"); } + + /// Test that a curbs curve can approximate a portion of a circle. + #[test] + fn nurbs_circular_arc() { + use std::f32::consts::FRAC_PI_2; + const EPSILON: f32 = 0.0000001; + + // The following NURBS parameters were determined by constraining the first two + // points to the line y=1, the second two points to the line x=1, and the distance + // between each pair of points to be equal. One can solve the weights by assuming the + // first and last weights to be one, the intermediate weights to be equal, and + // subjecting ones self to a lot of tedious matrix algebra. + + let alpha = FRAC_PI_2; + let leg = 2.0 * f32::sin(alpha / 2.0) / (1.0 + 2.0 * f32::cos(alpha / 2.0)); + let weight = (1.0 + 2.0 * f32::cos(alpha / 2.0)) / 3.0; + let points = [ + vec2(1.0, 0.0), + vec2(1.0, leg), + vec2(leg, 1.0), + vec2(0.0, 1.0), + ]; + let weights = [1.0, weight, weight, 1.0]; + let knots = [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]; + let spline = CubicNurbs::new(points, Some(weights), Some(knots)).unwrap(); + let curve = spline.to_curve(); + for (i, point) in curve.iter_positions(10).enumerate() { + assert!( + f32::abs(point.length() - 1.0) < EPSILON, + "Point {i} is not on the unit circle: {point:?} has length {}", + point.length() + ) + } + } } From db764b61f08911ad949befc6b112cffa49b167c8 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan <43478602+JohnTheCoolingFan@users.noreply.github.com> Date: Sat, 3 Feb 2024 15:06:09 +0300 Subject: [PATCH 65/82] Fix code style --- crates/bevy_math/src/cubic_splines.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 81ac7a0000f88..796cdbf705941 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -1167,9 +1167,8 @@ impl RationalCurve

{ // of NURBS curves and surfaces" by Choi et al. or equation 3 from "General Matrix // Representations for B-Splines" by Qin. return (segment, t / segment.knot_span); - } else { - t -= segment.knot_span; } + t -= segment.knot_span; } return (self.segments.last().unwrap(), 1.0); } From dda207ee4f946de6f039f6342498785c7e7a61d2 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan <43478602+JohnTheCoolingFan@users.noreply.github.com> Date: Sat, 3 Feb 2024 15:13:49 +0300 Subject: [PATCH 66/82] CI fix --- crates/bevy_math/src/cubic_splines.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 796cdbf705941..d18a0f2e4eeee 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -1286,7 +1286,7 @@ mod tests { assert_eq!(bezier.ease(1.0), 1.0); } - /// Test that RationalCurve properly generalizes CubicCurve. A Cubic upgraded to a rational + /// Test that [`RationalCurve`] properly generalizes [`CubicCurve`]. A Cubic upgraded to a rational /// should produce pretty much the same output. #[test] fn cubic_to_rational() { @@ -1365,7 +1365,7 @@ mod tests { f32::abs(point.length() - 1.0) < EPSILON, "Point {i} is not on the unit circle: {point:?} has length {}", point.length() - ) + ); } } } From ae4bb38c54fb4a35c84d95f44f2088b447e2391d Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan <43478602+JohnTheCoolingFan@users.noreply.github.com> Date: Sat, 3 Feb 2024 15:25:37 +0300 Subject: [PATCH 67/82] Fix type name in doc comment --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index d18a0f2e4eeee..9d50a6ca5a5b5 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -1055,7 +1055,7 @@ impl RationalSegment

{ /// A collection of [`RationalSegment`]s chained into a single parametric curve. /// /// Use any struct that implements the [`RationalGenerator`] trait to create a new curve, such as -/// [`CubicNURBS`], or convert [`CubicCurve`] using `into/from`. +/// [`CubicNurbs`], or convert [`CubicCurve`] using `into/from`. #[derive(Clone, Debug, PartialEq)] pub struct RationalCurve { segments: Vec>, From 8100a70f5e36e0c26c72d2df1781791e4b6b1065 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan <43478602+JohnTheCoolingFan@users.noreply.github.com> Date: Sat, 3 Feb 2024 19:52:33 +0300 Subject: [PATCH 68/82] Typo fixes Co-authored-by: IQuick 143 --- crates/bevy_math/src/cubic_splines.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 9d50a6ca5a5b5..79b9a0cb344d1 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -573,7 +573,7 @@ impl RationalGenerator

for CubicNurbs

{ .map(|((points, weights), knots)| { // This is curve segment i. It uses control points P_i, P_i+2, P_i+2 and P_i+3, // It is associated with knot span i+3 (which is the interval between knots i+3 - // and i+4) and it's charictartistic matrix uses knots i+1 through i+6 (because + // and i+4) and it's characteristic matrix uses knots i+1 through i+6 (because // those define the two knot spans on either side). let span = knots[4] - knots[3]; let coefficent_knots = knots.try_into().expect("Knot windows are of length 6"); @@ -1335,7 +1335,7 @@ mod tests { compare_vectors(cubic_accelerations, rational_accelerations, "acceleration"); } - /// Test that a curbs curve can approximate a portion of a circle. + /// Test that a nurbs curve can approximate a portion of a circle. #[test] fn nurbs_circular_arc() { use std::f32::consts::FRAC_PI_2; From 3b6075189a8c22aadc4db6cc1efd4971fb0b2b01 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 3 Feb 2024 20:28:00 +0300 Subject: [PATCH 69/82] Divide by `max - min` instead of `max` --- crates/bevy_math/src/cubic_splines.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 79b9a0cb344d1..24f49ec14b90b 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -470,10 +470,11 @@ impl CubicNurbs

{ let curve_length = (control_points.len() - 3) as f32; let min = *knots.first().unwrap(); let max = *knots.last().unwrap(); + let knot_delta = max - min; knots = knots .into_iter() .map(|k| k - min) - .map(|k| k * curve_length / max) + .map(|k| k * curve_length / knot_delta) .collect(); control_points From b03fbb996f33c8e6ea25e025200c083c031b2333 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 24 Feb 2024 05:19:07 +0300 Subject: [PATCH 70/82] Revert "Use thiserror re-export from bevy_utils" This reverts commit 534092134116cfec5f434e511aada9673c498a76. --- crates/bevy_math/Cargo.toml | 2 +- crates/bevy_math/src/cubic_splines.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index 33b477fd0f9f6..944c9394f9b25 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["bevy"] [dependencies] glam = { version = "0.25", features = ["bytemuck"] } -bevy_utils = { path = "../bevy_utils", version = "0.12.0" } +thiserror = "1.0" serde = { version = "1", features = ["derive"], optional = true } approx = { version = "0.5", optional = true } diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 24f49ec14b90b..8deaa4e677b7a 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -6,8 +6,8 @@ use std::{ ops::{Add, Div, Mul, Sub}, }; -use bevy_utils::{thiserror, thiserror::Error}; use glam::Vec2; +use thiserror::Error; /// A point in space of any dimension that supports the math ops needed for cubic spline /// interpolation. From d3445667faa0965130f219a67b59e971c27a769c Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 24 Feb 2024 05:20:43 +0300 Subject: [PATCH 71/82] Revert "Make Point trait impl a blanket impl" This reverts commit ae6319639fe3730b79652b5ece239aa4b52d1c3c. --- crates/bevy_math/src/cubic_splines.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 8deaa4e677b7a..bda82b5c7b3d4 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -26,20 +26,10 @@ pub trait Point: { } -impl Point for T where - T: Mul - + Div - + Add - + Sub - + Add - + Sum - + Default - + Debug - + Clone - + PartialEq - + Copy -{ -} +impl Point for Vec3 {} +impl Point for Vec3A {} +impl Point for Vec2 {} +impl Point for f32 {} /// A spline composed of a single cubic Bezier curve. /// From 2269a929c805c48cc7522ccb083f25ce871c2582 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 24 Feb 2024 05:25:47 +0300 Subject: [PATCH 72/82] Import Vec3 and Vec3A --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index bda82b5c7b3d4..9d28bd9d7d6b8 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -6,7 +6,7 @@ use std::{ ops::{Add, Div, Mul, Sub}, }; -use glam::Vec2; +use glam::{Vec2, Vec3, Vec3A}; use thiserror::Error; /// A point in space of any dimension that supports the math ops needed for cubic spline From 5cc66be0c9fc364d4eedecc94a385f7b798b39b1 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 24 Feb 2024 10:31:21 +0300 Subject: [PATCH 73/82] Revert "Import Vec3 and Vec3A" This reverts commit 2269a929c805c48cc7522ccb083f25ce871c2582. --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 9d28bd9d7d6b8..bda82b5c7b3d4 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -6,7 +6,7 @@ use std::{ ops::{Add, Div, Mul, Sub}, }; -use glam::{Vec2, Vec3, Vec3A}; +use glam::Vec2; use thiserror::Error; /// A point in space of any dimension that supports the math ops needed for cubic spline From 5aea5cc70e8c344424c0e1cad919bce5afa0bc41 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Sat, 24 Feb 2024 10:31:26 +0300 Subject: [PATCH 74/82] Reapply "Make Point trait impl a blanket impl" This reverts commit d3445667faa0965130f219a67b59e971c27a769c. --- crates/bevy_math/src/cubic_splines.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index bda82b5c7b3d4..8deaa4e677b7a 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -26,10 +26,20 @@ pub trait Point: { } -impl Point for Vec3 {} -impl Point for Vec3A {} -impl Point for Vec2 {} -impl Point for f32 {} +impl Point for T where + T: Mul + + Div + + Add + + Sub + + Add + + Sum + + Default + + Debug + + Clone + + PartialEq + + Copy +{ +} /// A spline composed of a single cubic Bezier curve. /// From 1d093131956ae97343e0a3fd35b6e46b06c7ede0 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Wed, 28 Feb 2024 18:02:00 +0300 Subject: [PATCH 75/82] Revert "Reapply "Make Point trait impl a blanket impl"" This reverts commit 5aea5cc70e8c344424c0e1cad919bce5afa0bc41. --- crates/bevy_math/src/cubic_splines.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 8deaa4e677b7a..bda82b5c7b3d4 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -26,20 +26,10 @@ pub trait Point: { } -impl Point for T where - T: Mul - + Div - + Add - + Sub - + Add - + Sum - + Default - + Debug - + Clone - + PartialEq - + Copy -{ -} +impl Point for Vec3 {} +impl Point for Vec3A {} +impl Point for Vec2 {} +impl Point for f32 {} /// A spline composed of a single cubic Bezier curve. /// From 039ba811308271a3b6e905d0b4d643fcb9edc6b8 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Wed, 28 Feb 2024 18:03:37 +0300 Subject: [PATCH 76/82] Import required types --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index bda82b5c7b3d4..9d28bd9d7d6b8 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -6,7 +6,7 @@ use std::{ ops::{Add, Div, Mul, Sub}, }; -use glam::Vec2; +use glam::{Vec2, Vec3, Vec3A}; use thiserror::Error; /// A point in space of any dimension that supports the math ops needed for cubic spline From 70e67f17d6fa958bfaf09ad24966f1ebdfdf3fac Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Wed, 28 Feb 2024 18:05:26 +0300 Subject: [PATCH 77/82] Implement Point for Vec4 --- crates/bevy_math/src/cubic_splines.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 9d28bd9d7d6b8..849d1657c1138 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -6,7 +6,7 @@ use std::{ ops::{Add, Div, Mul, Sub}, }; -use glam::{Vec2, Vec3, Vec3A}; +use glam::{Vec2, Vec3, Vec3A, Vec4}; use thiserror::Error; /// A point in space of any dimension that supports the math ops needed for cubic spline @@ -26,6 +26,7 @@ pub trait Point: { } +impl Point for Vec4 {} impl Point for Vec3 {} impl Point for Vec3A {} impl Point for Vec2 {} From 983749ad2ec75307ab0a67894f223569b7042399 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Wed, 28 Feb 2024 18:12:23 +0300 Subject: [PATCH 78/82] Relax dependencies of Point trait --- crates/bevy_math/src/cubic_splines.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 849d1657c1138..40511451939fd 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -2,7 +2,6 @@ use std::{ fmt::Debug, - iter::Sum, ops::{Add, Div, Mul, Sub}, }; @@ -16,12 +15,9 @@ pub trait Point: + Div + Add + Sub - + Add - + Sum + Default + Debug + Clone - + PartialEq + Copy { } From c22e971dca7a47e05ff7a5750d3cbd019fce5f65 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan Date: Wed, 28 Feb 2024 18:13:26 +0300 Subject: [PATCH 79/82] Implement Point for Quat --- crates/bevy_math/src/cubic_splines.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 40511451939fd..c61a1740fb80e 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -5,7 +5,7 @@ use std::{ ops::{Add, Div, Mul, Sub}, }; -use glam::{Vec2, Vec3, Vec3A, Vec4}; +use glam::{Quat, Vec2, Vec3, Vec3A, Vec4}; use thiserror::Error; /// A point in space of any dimension that supports the math ops needed for cubic spline @@ -22,6 +22,7 @@ pub trait Point: { } +impl Point for Quat {} impl Point for Vec4 {} impl Point for Vec3 {} impl Point for Vec3A {} From 4b4322ec6d3d5b62c17adbe815729a345df7a7e9 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 28 Feb 2024 11:10:21 -0500 Subject: [PATCH 80/82] Fix typo --- crates/bevy_math/src/cubic_splines.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index c61a1740fb80e..379190d526b1a 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -325,7 +325,7 @@ pub enum CubicNurbsError { #[error("Invalid knots: all knots are equal")] ConstantKnots, /// Provided a different number of weights and control points. - #[error("Incorect number of weights: expected {expected}, provided {provided}")] + #[error("Incorrect number of weights: expected {expected}, provided {provided}")] WeightsNumberMismatch { /// Expected number of weights expected: usize, From f6d8d1b769ef5680353139e1f627fbc7d69e668a Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 28 Feb 2024 11:20:44 -0500 Subject: [PATCH 81/82] Fix typos --- crates/bevy_math/src/cubic_splines.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 379190d526b1a..5c394e9044096 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -520,7 +520,7 @@ impl CubicNurbs

{ } /// Generates a nonuniform B-spline charictartistic matrix from a sequence of six knots. Each six - /// knots describe the relationship between four successive controll points. For padding reasons, + /// knots describe the relationship between four successive control points. For padding reasons, /// this takes a vector of 8 knots, but only six are actually used. fn generate_matrix(knots: &[f32; 8]) -> [[f32; 4]; 4] { // A derivation for this matrix can be found in "General Matrix Representations for B-splines" by Kaihuai Qin. @@ -565,8 +565,8 @@ impl RationalGenerator

for CubicNurbs

{ // and i+4) and it's characteristic matrix uses knots i+1 through i+6 (because // those define the two knot spans on either side). let span = knots[4] - knots[3]; - let coefficent_knots = knots.try_into().expect("Knot windows are of length 6"); - let matrix = Self::generate_matrix(coefficent_knots); + let coefficient_knots = knots.try_into().expect("Knot windows are of length 6"); + let matrix = Self::generate_matrix(coefficient_knots); RationalSegment::coefficients( points.try_into().expect("Point windows are of length 4"), weights.try_into().expect("Weight windows are of length 4"), From 32c35fcc58258dfaa3c1e651c6209382e6362546 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan <43478602+JohnTheCoolingFan@users.noreply.github.com> Date: Wed, 28 Feb 2024 20:10:23 +0300 Subject: [PATCH 82/82] Typos and wording fixes Co-authored-by: Joona Aalto --- crates/bevy_math/src/cubic_splines.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index 5c394e9044096..422d5767e2b6a 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -340,20 +340,20 @@ pub enum CubicNurbsError { }, } -/// Non-uniform Rational B-Splines (NURBS) are powerful generalization of the [`CubicBSpline`] which can +/// Non-uniform Rational B-Splines (NURBS) are a powerful generalization of the [`CubicBSpline`] which can /// represent a much more diverse class of curves (like perfect circles and ellipses). /// /// ### Non-uniformity /// The 'NU' part of NURBS stands for "Non-Uniform". This has to do with a parameter called 'knots'. /// The knots are a non-decreasing sequence of floating point numbers. The first and last three pairs of -/// knots control the behavior of the curve as it approaches it's endpoints. The intermediate pairs +/// knots control the behavior of the curve as it approaches its endpoints. The intermediate pairs /// each control the length of one segment of the curve. Multiple repeated knot values are called -/// "knot multiplicity". Knot multiplicity in the intermediate knots causes a "zero-length" segments, +/// "knot multiplicity". Knot multiplicity in the intermediate knots causes a "zero-length" segment, /// and can create sharp corners. /// /// ### Rationality /// The 'R' part of NURBS stands for "Rational". This has to do with NURBS allowing each control point to -/// be assigned a weighting, which controls how much it effects the curve compared to the other points. +/// be assigned a weighting, which controls how much it affects the curve compared to the other points. /// /// ### Interpolation /// The curve will not pass through the control points except where a knot has multiplicity four. @@ -365,7 +365,7 @@ pub enum CubicNurbsError { /// When there is no knot multiplicity, the curve is C2 continuous, meaning it has no holes or jumps and the /// tangent vector changes smoothly along the entire curve length. Like the [`CubicBSpline`], the acceleration /// continuity makes it useful for camera paths. Knot multiplicity of 2 in intermediate knots reduces the -/// continuity to C2, and Knot multiplicity of 3 reduces the continuity to C0. The curve is always at least +/// continuity to C2, and knot multiplicity of 3 reduces the continuity to C0. The curve is always at least /// C0, meaning it has no jumps or holes. /// /// ### Usage @@ -433,7 +433,7 @@ impl CubicNurbs

{ }); } - // Ensure the knots are non-descending (previous elements is less than or equal + // Ensure the knots are non-descending (previous element is less than or equal // to the next) if knots.windows(2).any(|win| win[0] > win[1]) { return Err(CubicNurbsError::DescendingKnots); @@ -499,7 +499,7 @@ impl CubicNurbs

{ /// The start and end knots have multiplicity 4, and intermediate knots have multiplicity 0 and /// difference of 1. /// - /// Will return `None` if there are less than 4 control points + /// Will return `None` if there are less than 4 control points. pub fn open_uniform_knots(control_points: usize) -> Option> { if control_points < 4 { return None; @@ -519,7 +519,7 @@ impl CubicNurbs

{ control_points_len + 4 } - /// Generates a nonuniform B-spline charictartistic matrix from a sequence of six knots. Each six + /// Generates a non-uniform B-spline characteristic matrix from a sequence of six knots. Each six /// knots describe the relationship between four successive control points. For padding reasons, /// this takes a vector of 8 knots, but only six are actually used. fn generate_matrix(knots: &[f32; 8]) -> [[f32; 4]; 4] {