From 724918cb9b3a305d5463b675a6cda72170bca552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 28 Jan 2026 20:51:54 +0100 Subject: [PATCH 1/7] feat: implement bytemuck traits for poses --- Cargo.toml | 2 +- src/eigen3.rs | 5 ++++- src/lib.rs | 2 -- src/pose2.rs | 4 +++- src/pose3.rs | 28 +++++++++++++++++++++++++--- 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2eedb52..18e8f46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Sébastien Crozet "] description = "Extensions for glam: Pose2, Pose3, Rot2, and matrix utilities." documentation = "https://docs.rs/glamx" homepage = "https://dimforge.com" -repository = "https://github.com/dimforge/parry" +repository = "https://github.com/dimforge/glamx" readme = "README.md" keywords = ["math", "geometry", "rotation", "pose", "glam"] categories = ["mathematics", "game-development", "no-std"] diff --git a/src/eigen3.rs b/src/eigen3.rs index 8dfb535..fd75bd0 100644 --- a/src/eigen3.rs +++ b/src/eigen3.rs @@ -63,7 +63,10 @@ macro_rules! impl_symmetric_eigen3 { if p1 == 0.0 { // The matrix is diagonal. let mut eigenvalues = [mat.x_axis.x, mat.y_axis.y, mat.z_axis.z]; - eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)); + // Simple 3-element sorting network (no alloc needed) + if eigenvalues[0] > eigenvalues[1] { eigenvalues.swap(0, 1); } + if eigenvalues[1] > eigenvalues[2] { eigenvalues.swap(1, 2); } + if eigenvalues[0] > eigenvalues[1] { eigenvalues.swap(0, 1); } <$Vec3>::from_array(eigenvalues) } else { let q = (mat.x_axis.x + mat.y_axis.y + mat.z_axis.z) / 3.0; diff --git a/src/lib.rs b/src/lib.rs index c2813c3..63090b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,8 +36,6 @@ #[cfg(feature = "std")] extern crate std; -extern crate alloc; - // Re-export approx for convenience #[cfg(feature = "approx")] pub use approx; diff --git a/src/pose2.rs b/src/pose2.rs index 4f3d131..7fa86fa 100644 --- a/src/pose2.rs +++ b/src/pose2.rs @@ -10,6 +10,7 @@ macro_rules! impl_pose2 { #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] + #[repr(C)] $(#[$attr])* pub struct $Pose2 { /// The rotational part of the pose. @@ -352,7 +353,8 @@ impl_pose2!( glam::Vec2, glam::Vec3, glam::Mat2, - glam::Mat3 + glam::Mat3, + #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] ); impl_pose2!( DPose2, diff --git a/src/pose3.rs b/src/pose3.rs index 49cb222..4dafeb1 100644 --- a/src/pose3.rs +++ b/src/pose3.rs @@ -5,17 +5,19 @@ use core::ops::{Mul, MulAssign}; /// Macro to generate a 3D pose type for a specific scalar type. macro_rules! impl_pose3 { - ($Pose3:ident, $Rot3:ident, $Real:ty, $Vec3:ty, $Mat4: ty $(, #[$attr:meta])*) => { + ($Pose3:ident, $Rot3:ident, $Real:ty, $Vec3:ty, $Mat4: ty $(, $Padding: ty, $bytemuck: ident)?) => { #[doc = concat!("A 3D pose (rotation + translation), representing a rigid body transformation (", stringify!($Real), " precision).")] #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] - $(#[$attr])* + $(#[cfg_attr(feature = "bytemuck", derive($bytemuck::Pod, $bytemuck::Zeroable))])* + #[repr(C)] pub struct $Pose3 { /// The rotational part of the pose. pub rotation: $Rot3, /// The translational part of the pose. pub translation: $Vec3, + $(pub padding: $Padding,)* } impl $Pose3 { @@ -23,6 +25,7 @@ macro_rules! impl_pose3 { pub const IDENTITY: Self = Self { rotation: $Rot3::IDENTITY, translation: <$Vec3>::ZERO, + $(padding: 0 as $Padding,)* }; /// Creates the identity pose. @@ -37,6 +40,7 @@ macro_rules! impl_pose3 { Self { rotation: $Rot3::IDENTITY, translation, + $(padding: 0 as $Padding,)* } } @@ -52,6 +56,7 @@ macro_rules! impl_pose3 { Self { rotation, translation: <$Vec3>::ZERO, + $(padding: 0 as $Padding,)* } } @@ -61,6 +66,7 @@ macro_rules! impl_pose3 { Self { rotation, translation, + $(padding: 0 as $Padding,)* } } @@ -71,6 +77,7 @@ macro_rules! impl_pose3 { Self { rotation, translation, + $(padding: 0 as $Padding,)* } } @@ -80,6 +87,7 @@ macro_rules! impl_pose3 { Self { rotation: $Rot3::from_scaled_axis(axisangle.into()), translation: <$Vec3>::ZERO, + $(padding: 0 as $Padding,)* } } @@ -89,6 +97,7 @@ macro_rules! impl_pose3 { Self { rotation: self.rotation, translation: self.translation + self.rotation * translation, + $(padding: 0 as $Padding,)* } } @@ -98,6 +107,7 @@ macro_rules! impl_pose3 { Self { rotation: self.rotation, translation: self.translation + translation, + $(padding: 0 as $Padding,)* } } @@ -108,6 +118,7 @@ macro_rules! impl_pose3 { Self { rotation: inv_rot, translation: inv_rot * (-self.translation), + $(padding: 0 as $Padding,)* } } @@ -119,6 +130,7 @@ macro_rules! impl_pose3 { $Pose3 { translation: inv_rot1 * tr_12, rotation: inv_rot1 * rhs.rotation, + $(padding: 0 as $Padding,)* } } @@ -158,6 +170,7 @@ macro_rules! impl_pose3 { Self { rotation: self.rotation.slerp(other.rotation, t), translation: self.translation.lerp(other.translation, t), + $(padding: 0 as $Padding,)* } } @@ -189,6 +202,7 @@ macro_rules! impl_pose3 { Self { rotation, translation: translation.into(), + $(padding: 0 as $Padding,)* } } @@ -210,6 +224,7 @@ macro_rules! impl_pose3 { $Pose3 { rotation, translation, + $(padding: 0 as $Padding,)* } } @@ -228,6 +243,7 @@ macro_rules! impl_pose3 { $Pose3 { rotation: <$Rot3>::look_at_lh(eye.into(), target.into(), up.into()).inverse(), translation: eye, + $(padding: 0 as $Padding,)* } } } @@ -241,6 +257,7 @@ macro_rules! impl_pose3 { Self { rotation: self.rotation * rhs.rotation, translation: self.translation + self.rotation * rhs.translation, + $(padding: 0 as $Padding,)* } } } @@ -324,6 +341,7 @@ macro_rules! impl_pose3 { $Pose3 { translation: self * rhs.translation, rotation: self * rhs.rotation, + $(padding: 0 as $Padding,)* } } } @@ -336,6 +354,7 @@ macro_rules! impl_pose3 { $Pose3 { translation: self.translation, rotation: self.rotation * rhs, + $(padding: 0 as $Padding,)* } } } @@ -387,7 +406,7 @@ macro_rules! impl_pose3 { }; } -impl_pose3!(Pose3, Rot3, f32, glam::Vec3, glam::Mat4); +impl_pose3!(Pose3, Rot3, f32, glam::Vec3, glam::Mat4, u32, bytemuck); impl_pose3!(Pose3A, Rot3, f32, glam::Vec3A, glam::Mat4); impl_pose3!(DPose3, DRot3, f64, glam::DVec3, glam::DMat4); @@ -408,6 +427,7 @@ impl From for Pose3 { Self { rotation: p.rotation.as_quat(), translation: p.translation.as_vec3(), + padding: 0 } } } @@ -449,6 +469,7 @@ impl From for Pose3 { Self { rotation: p.rotation, translation: p.translation.into(), + padding: 0 } } } @@ -481,6 +502,7 @@ mod nalgebra_conv { iso.translation.y, iso.translation.z, ), + padding: 0 } } } From 18fc632e78bdd2398b024e6bd9d5d8fd2bdb54b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Wed, 11 Feb 2026 15:29:12 +0100 Subject: [PATCH 2/7] =?UTF-8?q?feat:=E2=80=AFmake=20eigen3=20compatible=20?= =?UTF-8?q?with=20spirv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/eigen3.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/eigen3.rs b/src/eigen3.rs index fd75bd0..4f1805b 100644 --- a/src/eigen3.rs +++ b/src/eigen3.rs @@ -63,10 +63,12 @@ macro_rules! impl_symmetric_eigen3 { if p1 == 0.0 { // The matrix is diagonal. let mut eigenvalues = [mat.x_axis.x, mat.y_axis.y, mat.z_axis.z]; - // Simple 3-element sorting network (no alloc needed) - if eigenvalues[0] > eigenvalues[1] { eigenvalues.swap(0, 1); } - if eigenvalues[1] > eigenvalues[2] { eigenvalues.swap(1, 2); } - if eigenvalues[0] > eigenvalues[1] { eigenvalues.swap(0, 1); } + // Simple 3-element sorting network (no alloc needed). + // NOTE: manual swaps instead of .swap() to avoid ptr::swap which + // generates memcpy unsupported in SPIR-V. + if eigenvalues[0] > eigenvalues[1] { let t = eigenvalues[0]; eigenvalues[0] = eigenvalues[1]; eigenvalues[1] = t; } + if eigenvalues[1] > eigenvalues[2] { let t = eigenvalues[1]; eigenvalues[1] = eigenvalues[2]; eigenvalues[2] = t; } + if eigenvalues[0] > eigenvalues[1] { let t = eigenvalues[0]; eigenvalues[0] = eigenvalues[1]; eigenvalues[1] = t; } <$Vec3>::from_array(eigenvalues) } else { let q = (mat.x_axis.x + mat.y_axis.y + mat.z_axis.z) / 3.0; From f1de5c0a77bde32680da4dd234d22538bb512633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sun, 15 Feb 2026 23:31:19 +0100 Subject: [PATCH 3/7] fix Nan in eigendecomposition due to ftz on some platforms --- src/eigen3.rs | 12 ++++++++++-- src/pose3.rs | 6 +++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/eigen3.rs b/src/eigen3.rs index 4f1805b..0ee5df8 100644 --- a/src/eigen3.rs +++ b/src/eigen3.rs @@ -76,9 +76,17 @@ macro_rules! impl_symmetric_eigen3 { + (mat.y_axis.y - q).powi(2) + (mat.z_axis.z - q).powi(2) + 2.0 * p1; + let p = (p2 / 6.0).sqrt(); - let mat_b = 1.0 / p * (mat - q * <$Mat3>::IDENTITY); - let r = mat_b.determinant() / 2.0; + + // NOTE: `p` shouldn’t be zero, but in some platforms (nvidia gpus apparently), + // denormals can be flushed resulting in a zero. + let r = if p != 0.0 { + let mat_b = 1.0 / p * (mat - q * <$Mat3>::IDENTITY); + mat_b.determinant() / 2.0 + } else { + 1.0 // Will result in phy == 0.0 + }; // r should be in the [-1, 1] range for a symmetric matrix, // but computation error can leave it slightly outside this range. diff --git a/src/pose3.rs b/src/pose3.rs index 4dafeb1..6deaa26 100644 --- a/src/pose3.rs +++ b/src/pose3.rs @@ -427,7 +427,7 @@ impl From for Pose3 { Self { rotation: p.rotation.as_quat(), translation: p.translation.as_vec3(), - padding: 0 + padding: 0, } } } @@ -469,7 +469,7 @@ impl From for Pose3 { Self { rotation: p.rotation, translation: p.translation.into(), - padding: 0 + padding: 0, } } } @@ -502,7 +502,7 @@ mod nalgebra_conv { iso.translation.y, iso.translation.z, ), - padding: 0 + padding: 0, } } } From 4f3d44cadf518608cf42f43833cc5b67f030414b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sun, 15 Mar 2026 17:12:20 +0100 Subject: [PATCH 4/7] chore: add missing comment --- src/pose3.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pose3.rs b/src/pose3.rs index 6deaa26..89ba212 100644 --- a/src/pose3.rs +++ b/src/pose3.rs @@ -17,7 +17,12 @@ macro_rules! impl_pose3 { pub rotation: $Rot3, /// The translational part of the pose. pub translation: $Vec3, - $(pub padding: $Padding,)* + $( + /// Explicit padding for compatibility with bytemuck and targets like spirv. + /// + /// Can have any value as it is never read from or written to. + pub padding: $Padding, + )* } impl $Pose3 { From c2d9524f6eb21dfff1ffe89dca7d6a98ac44b251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Thu, 2 Apr 2026 15:14:23 +0200 Subject: [PATCH 5/7] Update to glam 0.32 --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 18e8f46..0205b14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,15 +61,15 @@ rand = ["glam/rand"] encase = ["glam/encase"] [dependencies] -glam = { version = "0.30", default-features = false } +glam = { version = "0.32", default-features = false } simba = { version = "0.9", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["libm"] } approx = { version = "0.5", default-features = false, optional = true } serde = { version = "1.0", optional = true, features = ["derive"] } bytemuck = { version = "1", features = ["derive"], optional = true } -nalgebra = { version = "0.34", default-features = false, features = ["convert-glam030"], optional = true } +nalgebra = { version = "0.34", default-features = false, features = ["convert-glam032"], optional = true } rkyv = { version = "0.8", optional = true, default-features = false } [dev-dependencies] approx = "0.5" -glam = { version = "0.30", features = ["approx"] } +glam = { version = "0.32", features = ["approx"] } From 2ec9e6b13e021fa5ab705fe98536c1f9bc4ac04c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Sun, 15 Mar 2026 17:12:29 +0100 Subject: [PATCH 6/7] chore: update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b38659c..16bee14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# Unreleased + +- Add `padding` field to `Pose3` for better compatibility with `bytemuck` and `spirv`. +- Fix various internal implementation details that are incompatible with `spirv`/`naga`. +- Update to `glam 0.32`. + # v0.1.3 - Add `From`/`Into` conversions between f32 and f64 type variants. From bf34fec7fc5d79f73c70a489b36143f5624fa5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Thu, 2 Apr 2026 15:27:30 +0200 Subject: [PATCH 7/7] Clippy fix --- src/eigen3.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/eigen3.rs b/src/eigen3.rs index 0ee5df8..0467ed0 100644 --- a/src/eigen3.rs +++ b/src/eigen3.rs @@ -6,6 +6,8 @@ //! //! Adapted from +#![allow(clippy::manual_swap)] // Needed for spirv compatibility. + use glam::Vec3Swizzles; use num_traits::float::FloatConst;