Skip to content

Commit

Permalink
Add Vec3::slerp (#583)
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-blackbird authored Dec 21, 2024
1 parent 4f59317 commit 77c55be
Show file tree
Hide file tree
Showing 13 changed files with 424 additions and 10 deletions.
10 changes: 10 additions & 0 deletions benches/vec3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,15 @@ bench_select!(
from => random_vec3
);

bench_trinop!(
vec3_slerp,
"vec3 slerp",
op => slerp,
from1 => random_vec3,
from2 => random_vec3,
from3 => random_f32
);

criterion_group!(
benches,
vec3_angle_between,
Expand All @@ -203,6 +212,7 @@ criterion_group!(
vec3_to_array_into,
vec3_to_rgb,
vec3_to_tuple_into,
vec3_slerp,
);

criterion_main!(benches);
10 changes: 10 additions & 0 deletions benches/vec3a.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,15 @@ bench_select!(
from => random_vec3a
);

bench_trinop!(
vec3a_slerp,
"vec3a slerp",
op => slerp,
from1 => random_vec3a,
from2 => random_vec3a,
from3 => random_f32
);

criterion_group!(
benches,
vec3a_normalize_bench,
Expand All @@ -171,6 +180,7 @@ criterion_group!(
vec3a_to_rgb,
vec3a_to_tuple_into,
vec3a_to_vec3,
vec3a_slerp,
);

criterion_main!(benches);
51 changes: 50 additions & 1 deletion codegen/templates/vec.rs.tera
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@
{% set vec3_t = "Vec3" %}
{% set vec3a_t = "Vec3A" %}
{% set vec4_t = "Vec4" %}
{% set quat_t = "Quat" %}
{% elif scalar_t == "f64" %}
{% set self_t = "DVec" ~ dim %}
{% set vec2_t = "DVec2" %}
{% set vec3_t = "DVec3" %}
{% set vec4_t = "DVec4" %}
{% set from_types = ["Vec" ~ dim, "IVec" ~ dim, "UVec" ~ dim] %}
{% set quat_t = "DQuat" %}
{% endif %}
{% elif scalar_t == "i8" %}
{% set is_signed = true %}
Expand Down Expand Up @@ -199,6 +201,10 @@
{% endif %}
{% if is_float %}
{{ scalar_t }}::math,
{% if dim == 3 %}
FloatExt,
{{ quat_t }},
{% endif %}
{% endif %}
{% if from_types %}
{% for ty in from_types %}
Expand Down Expand Up @@ -2163,6 +2169,49 @@ impl {{ self_t }} {
Self::new(b, sign + self.y * self.y * a, -self.y),
)
}

/// Performs a spherical linear interpolation between `self` and `rhs` based on the value `s`.
///
/// When `s` is `0.0`, the result will be equal to `self`. When `s` is `1.0`, the result
/// will be equal to `rhs`. When `s` is outside of range `[0, 1]`, the result is linearly
/// extrapolated.
#[inline]
#[must_use]
pub fn slerp(self, rhs: Self, s: {{ scalar_t }}) -> Self {
let self_length = self.length();
let rhs_length = rhs.length();
// Cosine of the angle between the vectors [-1, 1], or NaN if either vector has a zero length
let dot = self.dot(rhs) / (self_length * rhs_length);
// If dot is close to 1 or -1, or is NaN the calculations for t1 and t2 break down
if math::abs(dot) < 1.0 - 3e-7 {
// Angle between the vectors [0, +π]
let theta = math::acos_approx(dot);
// Sine of the angle between vectors [0, 1]
let sin_theta = math::sin(theta);
let t1 = math::sin(theta * (1. - s));
let t2 = math::sin(theta * s);

// Interpolate vector lengths
let result_length = self_length.lerp(rhs_length, s);
// Scale the vectors to the target length and interpolate them
return (self * (result_length / self_length) * t1
+ rhs * (result_length / rhs_length) * t2)
* sin_theta.recip();
}
if dot < 0.0 {
// Vectors are almost parallel in opposing directions

// Create a rotation from self to rhs along some axis
let axis = self.any_orthogonal_vector().normalize(){% if is_align %}.into(){% endif %};
let rotation = {{ quat_t }}::from_axis_angle(axis, core::{{ scalar_t }}::consts::PI * s);
// Interpolate vector lengths
let result_length = self_length.lerp(rhs_length, s);
rotation * self * (result_length / self_length)
} else {
// Vectors are almost parallel in the same direction, or dot was NaN
self.lerp(rhs, s)
}
}
{% endif %}
{% endif %}

Expand Down Expand Up @@ -2205,7 +2254,7 @@ impl {{ self_t }} {
/// Rotates towards `rhs` up to `max_angle` (in radians).
///
/// When `max_angle` is `0.0`, the result will be equal to `self`. When `max_angle` is equal to
/// `self.angle_between(rhs)`, the result will be equal to `rhs`. If `max_angle` is negative,
/// `self.angle_between(rhs)`, the result will be parallel to `rhs`. If `max_angle` is negative,
/// rotates towards the exact opposite of `rhs`. Will not go past the target.
#[inline]
#[must_use]
Expand Down
45 changes: 44 additions & 1 deletion src/f32/coresimd/vec3a.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated from vec.rs.tera template. Edit the template, not the generated file.

use crate::{coresimd::*, f32::math, BVec3, BVec3A, Vec2, Vec3, Vec4};
use crate::{coresimd::*, f32::math, BVec3, BVec3A, FloatExt, Quat, Vec2, Vec3, Vec4};

use core::fmt;
use core::iter::{Product, Sum};
Expand Down Expand Up @@ -939,6 +939,49 @@ impl Vec3A {
)
}

/// Performs a spherical linear interpolation between `self` and `rhs` based on the value `s`.
///
/// When `s` is `0.0`, the result will be equal to `self`. When `s` is `1.0`, the result
/// will be equal to `rhs`. When `s` is outside of range `[0, 1]`, the result is linearly
/// extrapolated.
#[inline]
#[must_use]
pub fn slerp(self, rhs: Self, s: f32) -> Self {
let self_length = self.length();
let rhs_length = rhs.length();
// Cosine of the angle between the vectors [-1, 1], or NaN if either vector has a zero length
let dot = self.dot(rhs) / (self_length * rhs_length);
// If dot is close to 1 or -1, or is NaN the calculations for t1 and t2 break down
if math::abs(dot) < 1.0 - 3e-7 {
// Angle between the vectors [0, +π]
let theta = math::acos_approx(dot);
// Sine of the angle between vectors [0, 1]
let sin_theta = math::sin(theta);
let t1 = math::sin(theta * (1. - s));
let t2 = math::sin(theta * s);

// Interpolate vector lengths
let result_length = self_length.lerp(rhs_length, s);
// Scale the vectors to the target length and interpolate them
return (self * (result_length / self_length) * t1
+ rhs * (result_length / rhs_length) * t2)
* sin_theta.recip();
}
if dot < 0.0 {
// Vectors are almost parallel in opposing directions

// Create a rotation from self to rhs along some axis
let axis = self.any_orthogonal_vector().normalize().into();
let rotation = Quat::from_axis_angle(axis, core::f32::consts::PI * s);
// Interpolate vector lengths
let result_length = self_length.lerp(rhs_length, s);
rotation * self * (result_length / self_length)
} else {
// Vectors are almost parallel in the same direction, or dot was NaN
self.lerp(rhs, s)
}
}

/// Casts all elements of `self` to `f64`.
#[inline]
#[must_use]
Expand Down
45 changes: 44 additions & 1 deletion src/f32/neon/vec3a.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated from vec.rs.tera template. Edit the template, not the generated file.

use crate::{f32::math, neon::*, BVec3, BVec3A, Vec2, Vec3, Vec4};
use crate::{f32::math, neon::*, BVec3, BVec3A, FloatExt, Quat, Vec2, Vec3, Vec4};

use core::fmt;
use core::iter::{Product, Sum};
Expand Down Expand Up @@ -983,6 +983,49 @@ impl Vec3A {
)
}

/// Performs a spherical linear interpolation between `self` and `rhs` based on the value `s`.
///
/// When `s` is `0.0`, the result will be equal to `self`. When `s` is `1.0`, the result
/// will be equal to `rhs`. When `s` is outside of range `[0, 1]`, the result is linearly
/// extrapolated.
#[inline]
#[must_use]
pub fn slerp(self, rhs: Self, s: f32) -> Self {
let self_length = self.length();
let rhs_length = rhs.length();
// Cosine of the angle between the vectors [-1, 1], or NaN if either vector has a zero length
let dot = self.dot(rhs) / (self_length * rhs_length);
// If dot is close to 1 or -1, or is NaN the calculations for t1 and t2 break down
if math::abs(dot) < 1.0 - 3e-7 {
// Angle between the vectors [0, +π]
let theta = math::acos_approx(dot);
// Sine of the angle between vectors [0, 1]
let sin_theta = math::sin(theta);
let t1 = math::sin(theta * (1. - s));
let t2 = math::sin(theta * s);

// Interpolate vector lengths
let result_length = self_length.lerp(rhs_length, s);
// Scale the vectors to the target length and interpolate them
return (self * (result_length / self_length) * t1
+ rhs * (result_length / rhs_length) * t2)
* sin_theta.recip();
}
if dot < 0.0 {
// Vectors are almost parallel in opposing directions

// Create a rotation from self to rhs along some axis
let axis = self.any_orthogonal_vector().normalize().into();
let rotation = Quat::from_axis_angle(axis, core::f32::consts::PI * s);
// Interpolate vector lengths
let result_length = self_length.lerp(rhs_length, s);
rotation * self * (result_length / self_length)
} else {
// Vectors are almost parallel in the same direction, or dot was NaN
self.lerp(rhs, s)
}
}

/// Casts all elements of `self` to `f64`.
#[inline]
#[must_use]
Expand Down
45 changes: 44 additions & 1 deletion src/f32/scalar/vec3a.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated from vec.rs.tera template. Edit the template, not the generated file.

use crate::{f32::math, BVec3, BVec3A, Vec2, Vec3, Vec4};
use crate::{f32::math, BVec3, BVec3A, FloatExt, Quat, Vec2, Vec3, Vec4};

use core::fmt;
use core::iter::{Product, Sum};
Expand Down Expand Up @@ -986,6 +986,49 @@ impl Vec3A {
)
}

/// Performs a spherical linear interpolation between `self` and `rhs` based on the value `s`.
///
/// When `s` is `0.0`, the result will be equal to `self`. When `s` is `1.0`, the result
/// will be equal to `rhs`. When `s` is outside of range `[0, 1]`, the result is linearly
/// extrapolated.
#[inline]
#[must_use]
pub fn slerp(self, rhs: Self, s: f32) -> Self {
let self_length = self.length();
let rhs_length = rhs.length();
// Cosine of the angle between the vectors [-1, 1], or NaN if either vector has a zero length
let dot = self.dot(rhs) / (self_length * rhs_length);
// If dot is close to 1 or -1, or is NaN the calculations for t1 and t2 break down
if math::abs(dot) < 1.0 - 3e-7 {
// Angle between the vectors [0, +π]
let theta = math::acos_approx(dot);
// Sine of the angle between vectors [0, 1]
let sin_theta = math::sin(theta);
let t1 = math::sin(theta * (1. - s));
let t2 = math::sin(theta * s);

// Interpolate vector lengths
let result_length = self_length.lerp(rhs_length, s);
// Scale the vectors to the target length and interpolate them
return (self * (result_length / self_length) * t1
+ rhs * (result_length / rhs_length) * t2)
* sin_theta.recip();
}
if dot < 0.0 {
// Vectors are almost parallel in opposing directions

// Create a rotation from self to rhs along some axis
let axis = self.any_orthogonal_vector().normalize().into();
let rotation = Quat::from_axis_angle(axis, core::f32::consts::PI * s);
// Interpolate vector lengths
let result_length = self_length.lerp(rhs_length, s);
rotation * self * (result_length / self_length)
} else {
// Vectors are almost parallel in the same direction, or dot was NaN
self.lerp(rhs, s)
}
}

/// Casts all elements of `self` to `f64`.
#[inline]
#[must_use]
Expand Down
45 changes: 44 additions & 1 deletion src/f32/sse2/vec3a.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated from vec.rs.tera template. Edit the template, not the generated file.

use crate::{f32::math, sse2::*, BVec3, BVec3A, Vec2, Vec3, Vec4};
use crate::{f32::math, sse2::*, BVec3, BVec3A, FloatExt, Quat, Vec2, Vec3, Vec4};

use core::fmt;
use core::iter::{Product, Sum};
Expand Down Expand Up @@ -991,6 +991,49 @@ impl Vec3A {
)
}

/// Performs a spherical linear interpolation between `self` and `rhs` based on the value `s`.
///
/// When `s` is `0.0`, the result will be equal to `self`. When `s` is `1.0`, the result
/// will be equal to `rhs`. When `s` is outside of range `[0, 1]`, the result is linearly
/// extrapolated.
#[inline]
#[must_use]
pub fn slerp(self, rhs: Self, s: f32) -> Self {
let self_length = self.length();
let rhs_length = rhs.length();
// Cosine of the angle between the vectors [-1, 1], or NaN if either vector has a zero length
let dot = self.dot(rhs) / (self_length * rhs_length);
// If dot is close to 1 or -1, or is NaN the calculations for t1 and t2 break down
if math::abs(dot) < 1.0 - 3e-7 {
// Angle between the vectors [0, +π]
let theta = math::acos_approx(dot);
// Sine of the angle between vectors [0, 1]
let sin_theta = math::sin(theta);
let t1 = math::sin(theta * (1. - s));
let t2 = math::sin(theta * s);

// Interpolate vector lengths
let result_length = self_length.lerp(rhs_length, s);
// Scale the vectors to the target length and interpolate them
return (self * (result_length / self_length) * t1
+ rhs * (result_length / rhs_length) * t2)
* sin_theta.recip();
}
if dot < 0.0 {
// Vectors are almost parallel in opposing directions

// Create a rotation from self to rhs along some axis
let axis = self.any_orthogonal_vector().normalize().into();
let rotation = Quat::from_axis_angle(axis, core::f32::consts::PI * s);
// Interpolate vector lengths
let result_length = self_length.lerp(rhs_length, s);
rotation * self * (result_length / self_length)
} else {
// Vectors are almost parallel in the same direction, or dot was NaN
self.lerp(rhs, s)
}
}

/// Casts all elements of `self` to `f64`.
#[inline]
#[must_use]
Expand Down
2 changes: 1 addition & 1 deletion src/f32/vec2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ impl Vec2 {
/// Rotates towards `rhs` up to `max_angle` (in radians).
///
/// When `max_angle` is `0.0`, the result will be equal to `self`. When `max_angle` is equal to
/// `self.angle_between(rhs)`, the result will be equal to `rhs`. If `max_angle` is negative,
/// `self.angle_between(rhs)`, the result will be parallel to `rhs`. If `max_angle` is negative,
/// rotates towards the exact opposite of `rhs`. Will not go past the target.
#[inline]
#[must_use]
Expand Down
Loading

0 comments on commit 77c55be

Please sign in to comment.