Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Vec3::slerp #583

Merged
merged 2 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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