Path: blob/main/crates/bevy_math/src/curve/derivatives/adaptor_impls.rs
6598 views
//! Implementations of derivatives on curve adaptors. These allow1//! compositionality for derivatives.23use super::{SampleDerivative, SampleTwoDerivatives};4use crate::common_traits::{HasTangent, Sum, VectorSpace, WithDerivative, WithTwoDerivatives};5use crate::curve::{6adaptors::{7ChainCurve, ConstantCurve, ContinuationCurve, CurveReparamCurve, ForeverCurve, GraphCurve,8LinearReparamCurve, PingPongCurve, RepeatCurve, ReverseCurve, ZipCurve,9},10Curve,11};1213// -- ConstantCurve1415impl<T> SampleDerivative<T> for ConstantCurve<T>16where17T: HasTangent + Clone,18{19fn sample_with_derivative_unchecked(&self, _t: f32) -> WithDerivative<T> {20WithDerivative {21value: self.value.clone(),22derivative: VectorSpace::ZERO,23}24}25}2627impl<T> SampleTwoDerivatives<T> for ConstantCurve<T>28where29T: HasTangent + Clone,30{31fn sample_with_two_derivatives_unchecked(&self, _t: f32) -> WithTwoDerivatives<T> {32WithTwoDerivatives {33value: self.value.clone(),34derivative: VectorSpace::ZERO,35second_derivative: VectorSpace::ZERO,36}37}38}3940// -- ChainCurve4142impl<T, C, D> SampleDerivative<T> for ChainCurve<T, C, D>43where44T: HasTangent,45C: SampleDerivative<T>,46D: SampleDerivative<T>,47{48fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {49if t > self.first.domain().end() {50self.second.sample_with_derivative_unchecked(51// `t - first.domain.end` computes the offset into the domain of the second.52t - self.first.domain().end() + self.second.domain().start(),53)54} else {55self.first.sample_with_derivative_unchecked(t)56}57}58}5960impl<T, C, D> SampleTwoDerivatives<T> for ChainCurve<T, C, D>61where62T: HasTangent,63C: SampleTwoDerivatives<T>,64D: SampleTwoDerivatives<T>,65{66fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {67if t > self.first.domain().end() {68self.second.sample_with_two_derivatives_unchecked(69// `t - first.domain.end` computes the offset into the domain of the second.70t - self.first.domain().end() + self.second.domain().start(),71)72} else {73self.first.sample_with_two_derivatives_unchecked(t)74}75}76}7778// -- ContinuationCurve7980impl<T, C, D> SampleDerivative<T> for ContinuationCurve<T, C, D>81where82T: VectorSpace,83C: SampleDerivative<T>,84D: SampleDerivative<T>,85{86fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {87if t > self.first.domain().end() {88let mut output = self.second.sample_with_derivative_unchecked(89// `t - first.domain.end` computes the offset into the domain of the second.90t - self.first.domain().end() + self.second.domain().start(),91);92output.value = output.value + self.offset;93output94} else {95self.first.sample_with_derivative_unchecked(t)96}97}98}99100impl<T, C, D> SampleTwoDerivatives<T> for ContinuationCurve<T, C, D>101where102T: VectorSpace,103C: SampleTwoDerivatives<T>,104D: SampleTwoDerivatives<T>,105{106fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {107if t > self.first.domain().end() {108let mut output = self.second.sample_with_two_derivatives_unchecked(109// `t - first.domain.end` computes the offset into the domain of the second.110t - self.first.domain().end() + self.second.domain().start(),111);112output.value = output.value + self.offset;113output114} else {115self.first.sample_with_two_derivatives_unchecked(t)116}117}118}119120// -- RepeatCurve121122impl<T, C> SampleDerivative<T> for RepeatCurve<T, C>123where124T: HasTangent,125C: SampleDerivative<T>,126{127fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {128let t = self.base_curve_sample_time(t);129self.curve.sample_with_derivative_unchecked(t)130}131}132133impl<T, C> SampleTwoDerivatives<T> for RepeatCurve<T, C>134where135T: HasTangent,136C: SampleTwoDerivatives<T>,137{138fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {139let t = self.base_curve_sample_time(t);140self.curve.sample_with_two_derivatives_unchecked(t)141}142}143144// -- ForeverCurve145146impl<T, C> SampleDerivative<T> for ForeverCurve<T, C>147where148T: HasTangent,149C: SampleDerivative<T>,150{151fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {152let t = self.base_curve_sample_time(t);153self.curve.sample_with_derivative_unchecked(t)154}155}156157impl<T, C> SampleTwoDerivatives<T> for ForeverCurve<T, C>158where159T: HasTangent,160C: SampleTwoDerivatives<T>,161{162fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {163let t = self.base_curve_sample_time(t);164self.curve.sample_with_two_derivatives_unchecked(t)165}166}167168// -- PingPongCurve169170impl<T, C> SampleDerivative<T> for PingPongCurve<T, C>171where172T: HasTangent,173C: SampleDerivative<T>,174{175fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {176if t > self.curve.domain().end() {177let t = self.curve.domain().end() * 2.0 - t;178// The derivative of the preceding expression is -1, so the chain179// rule implies the derivative should be negated.180let mut output = self.curve.sample_with_derivative_unchecked(t);181output.derivative = -output.derivative;182output183} else {184self.curve.sample_with_derivative_unchecked(t)185}186}187}188189impl<T, C> SampleTwoDerivatives<T> for PingPongCurve<T, C>190where191T: HasTangent,192C: SampleTwoDerivatives<T>,193{194fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {195if t > self.curve.domain().end() {196let t = self.curve.domain().end() * 2.0 - t;197// See the implementation on `ReverseCurve` for an explanation of198// why this is correct.199let mut output = self.curve.sample_with_two_derivatives_unchecked(t);200output.derivative = -output.derivative;201output202} else {203self.curve.sample_with_two_derivatives_unchecked(t)204}205}206}207208// -- ZipCurve209210impl<U, V, S, T, C, D> SampleDerivative<(S, T)> for ZipCurve<S, T, C, D>211where212U: VectorSpace<Scalar = f32>,213V: VectorSpace<Scalar = f32>,214S: HasTangent<Tangent = U>,215T: HasTangent<Tangent = V>,216C: SampleDerivative<S>,217D: SampleDerivative<T>,218{219fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<(S, T)> {220let first_output = self.first.sample_with_derivative_unchecked(t);221let second_output = self.second.sample_with_derivative_unchecked(t);222WithDerivative {223value: (first_output.value, second_output.value),224derivative: Sum(first_output.derivative, second_output.derivative),225}226}227}228229impl<U, V, S, T, C, D> SampleTwoDerivatives<(S, T)> for ZipCurve<S, T, C, D>230where231U: VectorSpace<Scalar = f32>,232V: VectorSpace<Scalar = f32>,233S: HasTangent<Tangent = U>,234T: HasTangent<Tangent = V>,235C: SampleTwoDerivatives<S>,236D: SampleTwoDerivatives<T>,237{238fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<(S, T)> {239let first_output = self.first.sample_with_two_derivatives_unchecked(t);240let second_output = self.second.sample_with_two_derivatives_unchecked(t);241WithTwoDerivatives {242value: (first_output.value, second_output.value),243derivative: Sum(first_output.derivative, second_output.derivative),244second_derivative: Sum(245first_output.second_derivative,246second_output.second_derivative,247),248}249}250}251252// -- GraphCurve253254impl<V, T, C> SampleDerivative<(f32, T)> for GraphCurve<T, C>255where256V: VectorSpace<Scalar = f32>,257T: HasTangent<Tangent = V>,258C: SampleDerivative<T>,259{260fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<(f32, T)> {261let output = self.base.sample_with_derivative_unchecked(t);262WithDerivative {263value: (t, output.value),264derivative: Sum(1.0, output.derivative),265}266}267}268269impl<V, T, C> SampleTwoDerivatives<(f32, T)> for GraphCurve<T, C>270where271V: VectorSpace<Scalar = f32>,272T: HasTangent<Tangent = V>,273C: SampleTwoDerivatives<T>,274{275fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<(f32, T)> {276let output = self.base.sample_with_two_derivatives_unchecked(t);277WithTwoDerivatives {278value: (t, output.value),279derivative: Sum(1.0, output.derivative),280second_derivative: Sum(0.0, output.second_derivative),281}282}283}284285// -- ReverseCurve286287impl<T, C> SampleDerivative<T> for ReverseCurve<T, C>288where289T: HasTangent,290C: SampleDerivative<T>,291{292fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {293// This gets almost the correct value, but we haven't accounted for the294// reversal of orientation yet.295let mut output = self296.curve297.sample_with_derivative_unchecked(self.domain().end() - (t - self.domain().start()));298299output.derivative = -output.derivative;300301output302}303}304305impl<T, C> SampleTwoDerivatives<T> for ReverseCurve<T, C>306where307T: HasTangent,308C: SampleTwoDerivatives<T>,309{310fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {311// This gets almost the correct value, but we haven't accounted for the312// reversal of orientation yet.313let mut output = self.curve.sample_with_two_derivatives_unchecked(314self.domain().end() - (t - self.domain().start()),315);316317output.derivative = -output.derivative;318319// (Note that the reparametrization that reverses the curve satisfies320// g'(t)^2 = 1 and g''(t) = 0, so the second derivative is already321// correct.)322323output324}325}326327// -- CurveReparamCurve328329impl<V, T, C, D> SampleDerivative<T> for CurveReparamCurve<T, C, D>330where331V: VectorSpace<Scalar = f32>,332T: HasTangent<Tangent = V>,333C: SampleDerivative<T>,334D: SampleDerivative<f32>,335{336fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {337// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t)338// is `self.reparam_curve`.339340// Start by computing g(t) and g'(t).341let reparam_output = self.reparam_curve.sample_with_derivative_unchecked(t);342343// Compute:344// - value: f(g(t))345// - derivative: f'(g(t))346let mut output = self347.base348.sample_with_derivative_unchecked(reparam_output.value);349350// Do the multiplication part of the chain rule.351output.derivative = output.derivative * reparam_output.derivative;352353// value: f(g(t)), derivative: f'(g(t)) g'(t)354output355}356}357358impl<V, T, C, D> SampleTwoDerivatives<T> for CurveReparamCurve<T, C, D>359where360V: VectorSpace<Scalar = f32>,361T: HasTangent<Tangent = V>,362C: SampleTwoDerivatives<T>,363D: SampleTwoDerivatives<f32>,364{365fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {366// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t)367// is `self.reparam_curve`.368369// Start by computing g(t), g'(t), g''(t).370let reparam_output = self.reparam_curve.sample_with_two_derivatives_unchecked(t);371372// Compute:373// - value: f(g(t))374// - derivative: f'(g(t))375// - second derivative: f''(g(t))376let mut output = self377.base378.sample_with_two_derivatives_unchecked(reparam_output.value);379380// Set the second derivative according to the chain and product rules381// r''(t) = f''(g(t)) g'(t)^2 + f'(g(t)) g''(t)382output.second_derivative = (output.second_derivative383* (reparam_output.derivative * reparam_output.derivative))384+ (output.derivative * reparam_output.second_derivative);385386// Set the first derivative according to the chain rule387// r'(t) = f'(g(t)) g'(t)388output.derivative = output.derivative * reparam_output.derivative;389390output391}392}393394// -- LinearReparamCurve395396impl<V, T, C> SampleDerivative<T> for LinearReparamCurve<T, C>397where398V: VectorSpace<Scalar = f32>,399T: HasTangent<Tangent = V>,400C: SampleDerivative<T>,401{402fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {403// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t) is404// the linear map bijecting `self.new_domain` onto `self.base.domain()`.405406// The invariants imply this unwrap always succeeds.407let g = self.new_domain.linear_map_to(self.base.domain()).unwrap();408409// Compute g'(t) from the domain lengths.410let g_derivative = self.base.domain().length() / self.new_domain.length();411412// Compute:413// - value: f(g(t))414// - derivative: f'(g(t))415let mut output = self.base.sample_with_derivative_unchecked(g(t));416417// Adjust the derivative according to the chain rule.418output.derivative = output.derivative * g_derivative;419420output421}422}423424impl<V, T, C> SampleTwoDerivatives<T> for LinearReparamCurve<T, C>425where426V: VectorSpace<Scalar = f32>,427T: HasTangent<Tangent = V>,428C: SampleTwoDerivatives<T>,429{430fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {431// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t) is432// the linear map bijecting `self.new_domain` onto `self.base.domain()`.433434// The invariants imply this unwrap always succeeds.435let g = self.new_domain.linear_map_to(self.base.domain()).unwrap();436437// Compute g'(t) from the domain lengths.438let g_derivative = self.base.domain().length() / self.new_domain.length();439440// Compute:441// - value: f(g(t))442// - derivative: f'(g(t))443// - second derivative: f''(g(t))444let mut output = self.base.sample_with_two_derivatives_unchecked(g(t));445446// Set the second derivative according to the chain and product rules447// r''(t) = f''(g(t)) g'(t)^2 (g''(t) = 0)448output.second_derivative = output.second_derivative * (g_derivative * g_derivative);449450// Set the first derivative according to the chain rule451// r'(t) = f'(g(t)) g'(t)452output.derivative = output.derivative * g_derivative;453454output455}456}457458#[cfg(test)]459mod tests {460461use approx::assert_abs_diff_eq;462463use super::*;464use crate::cubic_splines::{CubicBezier, CubicCardinalSpline, CubicCurve, CubicGenerator};465use crate::curve::{Curve, CurveExt, Interval};466use crate::{vec2, Vec2, Vec3};467468fn test_curve() -> CubicCurve<Vec2> {469let control_pts = [[470vec2(0.0, 0.0),471vec2(1.0, 0.0),472vec2(0.0, 1.0),473vec2(1.0, 1.0),474]];475476CubicBezier::new(control_pts).to_curve().unwrap()477}478479fn other_test_curve() -> CubicCurve<Vec2> {480let control_pts = [481vec2(1.0, 1.0),482vec2(2.0, 1.0),483vec2(2.0, 0.0),484vec2(1.0, 0.0),485];486487CubicCardinalSpline::new(0.5, control_pts)488.to_curve()489.unwrap()490}491492fn reparam_curve() -> CubicCurve<f32> {493let control_pts = [[0.0, 0.25, 0.75, 1.0]];494495CubicBezier::new(control_pts).to_curve().unwrap()496}497498#[test]499fn constant_curve() {500let curve = ConstantCurve::new(Interval::UNIT, Vec3::new(0.2, 1.5, -2.6));501let jet = curve.sample_with_derivative(0.5).unwrap();502assert_abs_diff_eq!(jet.derivative, Vec3::ZERO);503}504505#[test]506fn chain_curve() {507let curve1 = test_curve();508let curve2 = other_test_curve();509let curve = curve1.by_ref().chain(&curve2).unwrap();510511let jet = curve.sample_with_derivative(0.65).unwrap();512let true_jet = curve1.sample_with_derivative(0.65).unwrap();513assert_abs_diff_eq!(jet.value, true_jet.value);514assert_abs_diff_eq!(jet.derivative, true_jet.derivative);515516let jet = curve.sample_with_derivative(1.1).unwrap();517let true_jet = curve2.sample_with_derivative(0.1).unwrap();518assert_abs_diff_eq!(jet.value, true_jet.value);519assert_abs_diff_eq!(jet.derivative, true_jet.derivative);520}521522#[test]523fn continuation_curve() {524let curve1 = test_curve();525let curve2 = other_test_curve();526let curve = curve1.by_ref().chain_continue(&curve2).unwrap();527528let jet = curve.sample_with_derivative(0.99).unwrap();529let true_jet = curve1.sample_with_derivative(0.99).unwrap();530assert_abs_diff_eq!(jet.value, true_jet.value);531assert_abs_diff_eq!(jet.derivative, true_jet.derivative);532533let jet = curve.sample_with_derivative(1.3).unwrap();534let true_jet = curve2.sample_with_derivative(0.3).unwrap();535assert_abs_diff_eq!(jet.value, true_jet.value);536assert_abs_diff_eq!(jet.derivative, true_jet.derivative);537}538539#[test]540fn repeat_curve() {541let curve1 = test_curve();542let curve = curve1.by_ref().repeat(3).unwrap();543544let jet = curve.sample_with_derivative(0.73).unwrap();545let true_jet = curve1.sample_with_derivative(0.73).unwrap();546assert_abs_diff_eq!(jet.value, true_jet.value);547assert_abs_diff_eq!(jet.derivative, true_jet.derivative);548549let jet = curve.sample_with_derivative(3.5).unwrap();550let true_jet = curve1.sample_with_derivative(0.5).unwrap();551assert_abs_diff_eq!(jet.value, true_jet.value);552assert_abs_diff_eq!(jet.derivative, true_jet.derivative);553}554555#[test]556fn forever_curve() {557let curve1 = test_curve();558let curve = curve1.by_ref().forever().unwrap();559560let jet = curve.sample_with_derivative(0.12).unwrap();561let true_jet = curve1.sample_with_derivative(0.12).unwrap();562assert_abs_diff_eq!(jet.value, true_jet.value);563assert_abs_diff_eq!(jet.derivative, true_jet.derivative);564565let jet = curve.sample_with_derivative(500.5).unwrap();566let true_jet = curve1.sample_with_derivative(0.5).unwrap();567assert_abs_diff_eq!(jet.value, true_jet.value);568assert_abs_diff_eq!(jet.derivative, true_jet.derivative);569}570571#[test]572fn ping_pong_curve() {573let curve1 = test_curve();574let curve = curve1.by_ref().ping_pong().unwrap();575576let jet = curve.sample_with_derivative(0.99).unwrap();577let comparison_jet = curve1.sample_with_derivative(0.99).unwrap();578assert_abs_diff_eq!(jet.value, comparison_jet.value);579assert_abs_diff_eq!(jet.derivative, comparison_jet.derivative);580581let jet = curve.sample_with_derivative(1.3).unwrap();582let comparison_jet = curve1.sample_with_derivative(0.7).unwrap();583assert_abs_diff_eq!(jet.value, comparison_jet.value);584assert_abs_diff_eq!(jet.derivative, -comparison_jet.derivative, epsilon = 1.0e-5);585}586587#[test]588fn zip_curve() {589let curve1 = test_curve();590let curve2 = other_test_curve();591let curve = curve1.by_ref().zip(&curve2).unwrap();592593let jet = curve.sample_with_derivative(0.7).unwrap();594let comparison_jet1 = curve1.sample_with_derivative(0.7).unwrap();595let comparison_jet2 = curve2.sample_with_derivative(0.7).unwrap();596assert_abs_diff_eq!(jet.value.0, comparison_jet1.value);597assert_abs_diff_eq!(jet.value.1, comparison_jet2.value);598let Sum(derivative1, derivative2) = jet.derivative;599assert_abs_diff_eq!(derivative1, comparison_jet1.derivative);600assert_abs_diff_eq!(derivative2, comparison_jet2.derivative);601}602603#[test]604fn graph_curve() {605let curve1 = test_curve();606let curve = curve1.by_ref().graph();607608let jet = curve.sample_with_derivative(0.25).unwrap();609let comparison_jet = curve1.sample_with_derivative(0.25).unwrap();610assert_abs_diff_eq!(jet.value.0, 0.25);611assert_abs_diff_eq!(jet.value.1, comparison_jet.value);612let Sum(one, derivative) = jet.derivative;613assert_abs_diff_eq!(one, 1.0);614assert_abs_diff_eq!(derivative, comparison_jet.derivative);615}616617#[test]618fn reverse_curve() {619let curve1 = test_curve();620let curve = curve1.by_ref().reverse().unwrap();621622let jet = curve.sample_with_derivative(0.23).unwrap();623let comparison_jet = curve1.sample_with_derivative(0.77).unwrap();624assert_abs_diff_eq!(jet.value, comparison_jet.value);625assert_abs_diff_eq!(jet.derivative, -comparison_jet.derivative);626}627628#[test]629fn curve_reparam_curve() {630let reparam_curve = reparam_curve();631let reparam_jet = reparam_curve.sample_with_derivative(0.6).unwrap();632633let curve1 = test_curve();634let curve = curve1.by_ref().reparametrize_by_curve(&reparam_curve);635636let jet = curve.sample_with_derivative(0.6).unwrap();637let base_jet = curve1638.sample_with_derivative(reparam_curve.sample(0.6).unwrap())639.unwrap();640assert_abs_diff_eq!(jet.value, base_jet.value);641assert_abs_diff_eq!(jet.derivative, base_jet.derivative * reparam_jet.derivative);642}643644#[test]645fn linear_reparam_curve() {646let curve1 = test_curve();647let curve = curve1648.by_ref()649.reparametrize_linear(Interval::new(0.0, 0.5).unwrap())650.unwrap();651652let jet = curve.sample_with_derivative(0.23).unwrap();653let comparison_jet = curve1.sample_with_derivative(0.46).unwrap();654assert_abs_diff_eq!(jet.value, comparison_jet.value);655assert_abs_diff_eq!(jet.derivative, comparison_jet.derivative * 2.0);656}657}658659660