Skip to content

Commit

Permalink
add support for packed_simd under nightly feature for 8-wide types, a…
Browse files Browse the repository at this point in the history
…dd slerp, start scaffolding for f64 types
  • Loading branch information
fu5ha committed Aug 20, 2020
1 parent d6d2996 commit 75106a7
Show file tree
Hide file tree
Showing 14 changed files with 1,835 additions and 937 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target
**/*.rs.bk
Cargo.lock
/.vscode
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
## 0.6
- Upgrade `wide` to 0.5.x
- Rename `[WideType]::merge()` to `[WideType]::blend()`
- Add `wgpu`-specfic notes to `projection` module (adds `_wgpu` to some function names)
- Add `wgpu`-specfic notes to `projection` module (adds `_wgpu` to some function names)
- Add support for `packed_simd` under "nightly" feature flag (required nightly Rust compiler)
- Under nightly, add support for 256-bit wide vectors
- Add support for f64/double precision floats
- Add spherical linear interpolation and better docs around interpolation
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ license = "MIT OR Apache-2.0 OR Zlib"

[dependencies]
# wide = { path = "../wide" }
wide = { version = "~0.5.1" }
wide = { version = "~0.5.1", optional = true }
bytemuck = { version = "~1.3.1" }
serde = { version = "1.0", features = [], optional = true }
# packed_simd = { version = "~0.3.3", optional = true }
packed_simd = { path = "../packed_simd", optional = true }

[features]
default = [ "wide" ]
nightly = [ "packed_simd" ]

[dev-dependencies]
serde_test = "1.0"
35 changes: 25 additions & 10 deletions src/bivec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@
//! three components, each of which represents the *projected area* of that bivector onto one of the three
//! basis bivectors. This is analogous to how vector components represent the *projected length* of that vector
//! onto each unit vector.
use wide::f32x4;
use crate::*;

use crate::util::*;
use crate::vec::*;

use std::ops::*;

Expand Down Expand Up @@ -64,12 +63,12 @@ macro_rules! bivec2s {

#[inline]
pub fn zero() -> Self {
Self::new($t::from(0.0))
Self::new($t::splat(0.0))
}

#[inline]
pub fn unit_xy() -> Self {
Self::new($t::from(1.0))
Self::new($t::splat(1.0))
}

#[inline]
Expand All @@ -95,6 +94,11 @@ macro_rules! bivec2s {
r
}

#[inline]
pub fn dot(&self, rhs: Self) -> $t {
self.xy * rhs.xy
}

#[inline]
pub fn layout() -> alloc::alloc::Layout {
alloc::alloc::Layout::from_size_align(std::mem::size_of::<Self>(), std::mem::align_of::<$t>()).unwrap()
Expand Down Expand Up @@ -285,7 +289,10 @@ macro_rules! bivec2s {
}
}

bivec2s!((Bivec2) => f32, (WBivec2) => f32x4);
bivec2s!(
(Bivec2) => f32,
(Bivec2x4) => f32x4,
(Bivec2x8) => f32x8);

macro_rules! bivec3s {
($($bn:ident => ($vt:ident, $t:ident)),+) => {
Expand Down Expand Up @@ -324,7 +331,7 @@ macro_rules! bivec3s {

#[inline]
pub fn zero() -> Self {
Self::new($t::from(0.0), $t::from(0.0), $t::from(0.0))
Self::new($t::splat(0.0), $t::splat(0.0), $t::splat(0.0))
}

/// Create the bivector which represents the same plane of rotation as a given
Expand All @@ -336,17 +343,17 @@ macro_rules! bivec3s {

#[inline]
pub fn unit_xy() -> Self {
Self::new($t::from(1.0), $t::from(0.0), $t::from(0.0))
Self::new($t::splat(1.0), $t::splat(0.0), $t::splat(0.0))
}

#[inline]
pub fn unit_xz() -> Self {
Self::new($t::from(0.0), $t::from(1.0), $t::from(0.0))
Self::new($t::splat(0.0), $t::splat(1.0), $t::splat(0.0))
}

#[inline]
pub fn unit_yz() -> Self {
Self::new($t::from(0.0), $t::from(0.0), $t::from(1.0))
Self::new($t::splat(0.0), $t::splat(0.0), $t::splat(1.0))
}

#[inline]
Expand Down Expand Up @@ -374,6 +381,11 @@ macro_rules! bivec3s {
r
}

#[inline]
pub fn dot(&self, rhs: Self) -> $t {
self.xy.mul_add(rhs.xy, self.xz.mul_add(rhs.xz, self.yz * rhs.yz))
}

#[inline]
pub fn layout() -> alloc::alloc::Layout {
alloc::alloc::Layout::from_size_align(std::mem::size_of::<Self>(), std::mem::align_of::<$t>()).unwrap()
Expand Down Expand Up @@ -572,4 +584,7 @@ macro_rules! bivec3s {
}
}

bivec3s!(Bivec3 => (Vec3, f32), WBivec3 => (Wec3, f32x4));
bivec3s!(
Bivec3 => (Vec3, f32),
Bivec3x4 => (Vec3x4, f32x4),
Bivec3x8 => (Vec3x8, f32x8));
188 changes: 188 additions & 0 deletions src/interp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
//! Interpolation on types for which it makes sense.
use crate::*;

/// Pure linear interpolation, i.e. `(1.0 - t) * self + (t) * end`.
///
/// For interpolating `Rotor`s with linear interpolation, you almost certainly
/// want to normalize the returned `Rotor`. For example,
/// ```rs
/// let interpolated_rotor = rotor1.lerp(rotor2, 0.5).normalized();
/// ```
/// For most cases (especially where perfomrance is the primary concern, like in
/// animation interpolation for games, this 'normalized lerp' or 'nlerp' is probably
/// what you want to use. However, there are situations in which you really want
/// the interpolation between two `Rotor`s to be of constant angular velocity. In this
/// case, check out `Slerp`.
pub trait Lerp<T> {
fn lerp(&self, end: Self, t: T) -> Self;
}

macro_rules! impl_lerp {
($($tt:ident => ($($vt:ident),+)),+) => {
$($(impl Lerp<$tt> for $vt {
/// Linearly interpolate between `self` and `end` by `t` between 0.0 and 1.0.
/// i.e. `(1.0 - t) * self + (t) * end`.
///
/// For interpolating `Rotor`s with linear interpolation, you almost certainly
/// want to normalize the returned `Rotor`. For example,
/// ```rs
/// let interpolated_rotor = rotor1.lerp(rotor2, 0.5).normalized();
/// ```
/// For most cases (especially where perfomrance is the primary concern, like in
/// animation interpolation for games, this 'normalized lerp' or 'nlerp' is probably
/// what you want to use. However, there are situations in which you really want
/// the interpolation between two `Rotor`s to be of constant angular velocity. In this
/// case, check out `Slerp`.
#[inline]
fn lerp(&self, end: Self, t: $tt) -> Self {
*self * ($tt::splat(1.0) - t) + end * t
}
})+)+
};
}

impl_lerp!(
f32 => (Vec2, Vec3, Vec4, Bivec2, Bivec3, Rotor2, Rotor3),
f32x4 => (Vec2x4, Vec3x4, Vec4x4, Bivec2x4, Bivec3x4, Rotor2x4, Rotor3x4));

/// Spherical-linear interpolation.
///
/// Basically, interpolation that maintains a constant angular velocity
/// from one orientation on a unit hypersphere to another. This is sorta the "high quality" interpolation
/// for `Rotor`s, and it can also be used to interpolate other things, one example being interpolation of
/// 3d normal vectors.
///
/// Note that you should often normalize the result returned by this operation, when working with `Rotor`s, etc!
pub trait Slerp<T> {
fn slerp(&self, end: Self, t: T) -> Self;
}

macro_rules! impl_slerp_rotor3 {
($($tt:ident => ($($vt:ident),+)),+) => {
$($(impl Slerp<$tt> for $vt {
/// Spherical-linear interpolation between `self` and `end` based on `t` from 0.0 to 1.0.
///
/// `self` and `end` should both be normalized or something bad will happen!
///
/// Basically, interpolation that maintains a constant angular velocity
/// from one orientation on a unit hypersphere to another. This is sorta the "high quality" interpolation
/// for `Rotor`s, and it can also be used to interpolate other things, one example being interpolation of
/// 3d normal vectors.
///
/// Note that you should often normalize the result returned by this operation, when working with `Rotor`s, etc!
#[inline]
fn slerp(&self, end: Self, t: $tt) -> Self {
let dot = self.dot(end);

if dot > 0.9995 {
return self.lerp(end, t);
}

let dot = dot.min(1.0).max(-1.0);

let theta_0 = dot.acos(); // angle between inputs
let theta = theta_0 * t; // amount of said angle to travel

let v2 = (end - (*self * dot)).normalized(); // create orthonormal basis between self and `v2`

let (s, c) = theta.sin_cos();

let mut n = *self;

n.s = c.mul_add(self.s, s * v2.s);
n.bv.xy = c.mul_add(self.bv.xy, s * v2.bv.xy);
n.bv.xz = c.mul_add(self.bv.xz, s * v2.bv.xz);
n.bv.yz = c.mul_add(self.bv.yz, s * v2.bv.yz);

n
}
})+)+
};
}

impl_slerp_rotor3!(
f32 => (Rotor3));

macro_rules! impl_slerp_rotor3_wide {
($($tt:ident => ($($vt:ident),+)),+) => {
$($(impl Slerp<$tt> for $vt {
/// Spherical-linear interpolation between `self` and `end` based on `t` from 0.0 to 1.0.
///
/// `self` and `end` should both be normalized or something bad will happen!
///
/// The implementation for SIMD types also requires that the two things being interpolated between
/// are not exactly aligned, or else the result is undefined.
///
/// Basically, interpolation that maintains a constant angular velocity
/// from one orientation on a unit hypersphere to another. This is sorta the "high quality" interpolation
/// for `Rotor`s, and it can also be used to interpolate other things, one example being interpolation of
/// 3d normal vectors.
///
/// Note that you should often normalize the result returned by this operation, when working with `Rotor`s, etc!
#[inline]
fn slerp(&self, end: Self, t: $tt) -> Self {
let dot = self.dot(end);

let dot = dot.min($tt::splat(1.0)).max($tt::splat(-1.0));

let theta_0 = dot.acos(); // angle between inputs
let theta = theta_0 * t; // amount of said angle to travel

let v2 = (end - (*self * dot)).normalized(); // create orthonormal basis between self and `v2`

let (s, c) = theta.sin_cos();

let mut n = *self;

n.s = c.mul_add(self.s, s * v2.s);
n.bv.xy = c.mul_add(self.bv.xy, s * v2.bv.xy);
n.bv.xz = c.mul_add(self.bv.xz, s * v2.bv.xz);
n.bv.yz = c.mul_add(self.bv.yz, s * v2.bv.yz);

n
}
})+)+
};
}

impl_slerp_rotor3_wide!(
f32x4 => (Rotor3x4));

macro_rules! impl_slerp_gen {
($($tt:ident => ($($vt:ident),+)),+) => {
$($(impl Slerp<$tt> for $vt {
/// Spherical-linear interpolation between `self` and `end` based on `t` from 0.0 to 1.0.
///
/// `self` and `end` should both be normalized or something bad will happen!
///
/// The implementation for SIMD types also requires that the two things being interpolated between
/// are not exactly aligned, or else the result is undefined.
///
/// Basically, interpolation that maintains a constant angular velocity
/// from one orientation on a unit hypersphere to another. This is sorta the "high quality" interpolation
/// for `Rotor`s, and it can also be used to interpolate other things, one example being interpolation of
/// 3d normal vectors.
///
/// Note that you should often normalize the result returned by this operation, when working with `Rotor`s, etc!
#[inline]
fn slerp(&self, end: Self, t: $tt) -> Self {
let dot = self.dot(end);

let dot = dot.min($tt::splat(1.0)).max($tt::splat(-1.0));

let theta_0 = dot.acos(); // angle between inputs
let theta = theta_0 * t; // amount of said angle to travel

let v2 = (end - (*self * dot)).normalized(); // create orthonormal basis between self and `v2`

let (s, c) = theta.sin_cos();

*self * c + v2 * s
}
})+)+
};
}

impl_slerp_gen!(
f32 => (Vec2, Vec3, Vec4, Bivec2, Bivec3, Rotor2),
f32x4 => (Vec2x4, Vec3x4, Vec4x4, Bivec2x4, Bivec3x4, Rotor2x4));
25 changes: 0 additions & 25 deletions src/lerp.rs

This file was deleted.

Loading

0 comments on commit 75106a7

Please sign in to comment.