diff --git a/crates/build/re_types_builder/src/codegen/cpp/mod.rs b/crates/build/re_types_builder/src/codegen/cpp/mod.rs index 11ddfd846da6..105fc4ca250b 100644 --- a/crates/build/re_types_builder/src/codegen/cpp/mod.rs +++ b/crates/build/re_types_builder/src/codegen/cpp/mod.rs @@ -1298,7 +1298,8 @@ fn single_field_constructor_methods( // but ran into some issues when init archetypes with initializer lists. if let Type::Object(field_type_fqname) = &field.typ { let field_type_obj = &objects[field_type_fqname]; - if field_type_obj.fields.len() == 1 { + if field_type_obj.fields.len() == 1 && !field_type_obj.is_attr_set(ATTR_CPP_NO_FIELD_CTORS) + { methods.extend(add_copy_assignment_and_constructor( hpp_includes, &field_type_obj.fields[0], diff --git a/crates/store/re_types/definitions/rerun/archetypes/transform3d.fbs b/crates/store/re_types/definitions/rerun/archetypes/transform3d.fbs index 8ef8b109851b..1e9f44d78113 100644 --- a/crates/store/re_types/definitions/rerun/archetypes/transform3d.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes/transform3d.fbs @@ -27,11 +27,17 @@ table Transform3D ( /// Translation vectors. translation: [rerun.components.Translation3D] ("attr.rerun.component_optional", nullable, order: 1100); + /// Rotation via axis + angle. + rotation_axis_angle: [rerun.components.RotationAxisAngle] ("attr.rerun.component_optional", nullable, order: 1200); + + /// Rotation via quaternion. + quaternion: [rerun.components.RotationQuat] ("attr.rerun.component_optional", nullable, order: 1300); + /// Scaling factor. - scale: [rerun.components.Scale3D] ("attr.rerun.component_optional", nullable, order: 1200); + scale: [rerun.components.Scale3D] ("attr.rerun.component_optional", nullable, order: 1400); /// 3x3 transformation matrices. - mat3x3: [rerun.components.TransformMat3x3] ("attr.rerun.component_optional", nullable, order: 1300); + mat3x3: [rerun.components.TransformMat3x3] ("attr.rerun.component_optional", nullable, order: 1500); // --- visual representation diff --git a/crates/store/re_types/definitions/rerun/components.fbs b/crates/store/re_types/definitions/rerun/components.fbs index 75661d1f84db..f938da3c3c26 100644 --- a/crates/store/re_types/definitions/rerun/components.fbs +++ b/crates/store/re_types/definitions/rerun/components.fbs @@ -37,6 +37,8 @@ include "./components/range1d.fbs"; include "./components/resolution.fbs"; include "./components/resolution2d.fbs"; include "./components/rotation3d.fbs"; +include "./components/rotation_axis_angle.fbs"; +include "./components/rotation_quat.fbs"; include "./components/scalar.fbs"; include "./components/scale3d.fbs"; include "./components/stroke_width.fbs"; diff --git a/crates/store/re_types/definitions/rerun/components/rotation_axis_angle.fbs b/crates/store/re_types/definitions/rerun/components/rotation_axis_angle.fbs new file mode 100644 index 000000000000..b807a746dd86 --- /dev/null +++ b/crates/store/re_types/definitions/rerun/components/rotation_axis_angle.fbs @@ -0,0 +1,10 @@ +namespace rerun.components; + +/// 3D rotation represented by a rotation around a given axis. +table RotationAxisAngle ( + "attr.docs.unreleased", + "attr.rust.derive": "Default, Copy, PartialEq", + "attr.rust.repr": "transparent" +) { + rotation: rerun.datatypes.RotationAxisAngle (order: 100); +} diff --git a/crates/store/re_types/definitions/rerun/components/rotation_quat.fbs b/crates/store/re_types/definitions/rerun/components/rotation_quat.fbs new file mode 100644 index 000000000000..b88f69cce51f --- /dev/null +++ b/crates/store/re_types/definitions/rerun/components/rotation_quat.fbs @@ -0,0 +1,13 @@ +namespace rerun.components; + +/// A 3D rotation expressed as a quaternion. +/// +/// Note: although the x,y,z,w components of the quaternion will be passed through to the +/// datastore as provided, when used in the Viewer Quaternions will always be normalized. +struct RotationQuat ( + "attr.docs.unreleased", + "attr.rust.derive": "Default, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable", + "attr.rust.repr": "transparent" +) { + quaternion: rerun.datatypes.Quaternion (order: 100); +} diff --git a/crates/store/re_types/definitions/rerun/datatypes/quaternion.fbs b/crates/store/re_types/definitions/rerun/datatypes/quaternion.fbs index 99265d24c9f3..d5305cc696da 100644 --- a/crates/store/re_types/definitions/rerun/datatypes/quaternion.fbs +++ b/crates/store/re_types/definitions/rerun/datatypes/quaternion.fbs @@ -6,12 +6,11 @@ namespace rerun.datatypes; /// /// Note: although the x,y,z,w components of the quaternion will be passed through to the /// datastore as provided, when used in the Viewer Quaternions will always be normalized. -// -// Expectations: struct Quaternion ( "attr.arrow.transparent", - "attr.rust.derive": "Copy, PartialEq, PartialOrd", + "attr.rust.derive": "Copy, PartialEq, PartialOrd, bytemuck::Pod, bytemuck::Zeroable", "attr.rust.tuple_struct", + "attr.rust.repr": "C", "attr.cpp.no_field_ctors" // Always be explicit about the values of the fields. ) { xyzw: [float: 4] (order: 100); diff --git a/crates/store/re_types/src/archetypes/transform3d.rs b/crates/store/re_types/src/archetypes/transform3d.rs index c7bad74dca53..ac89a0825a55 100644 --- a/crates/store/re_types/src/archetypes/transform3d.rs +++ b/crates/store/re_types/src/archetypes/transform3d.rs @@ -171,6 +171,12 @@ pub struct Transform3D { /// Translation vectors. pub translation: Option>, + /// Rotation via axis + angle. + pub rotation_axis_angle: Option>, + + /// Rotation via quaternion. + pub quaternion: Option>, + /// Scaling factor. pub scale: Option>, @@ -189,6 +195,8 @@ impl ::re_types_core::SizeBytes for Transform3D { fn heap_size_bytes(&self) -> u64 { self.transform.heap_size_bytes() + self.translation.heap_size_bytes() + + self.rotation_axis_angle.heap_size_bytes() + + self.quaternion.heap_size_bytes() + self.scale.heap_size_bytes() + self.mat3x3.heap_size_bytes() + self.axis_length.heap_size_bytes() @@ -198,6 +206,8 @@ impl ::re_types_core::SizeBytes for Transform3D { fn is_pod() -> bool { ::is_pod() && >>::is_pod() + && >>::is_pod() + && >>::is_pod() && >>::is_pod() && >>::is_pod() && >::is_pod() @@ -210,23 +220,27 @@ static REQUIRED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 0usize]> = static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 1usize]> = once_cell::sync::Lazy::new(|| ["rerun.components.Transform3DIndicator".into()]); -static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 5usize]> = +static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 7usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.Transform3D".into(), "rerun.components.Translation3D".into(), + "rerun.components.RotationAxisAngle".into(), + "rerun.components.RotationQuat".into(), "rerun.components.Scale3D".into(), "rerun.components.TransformMat3x3".into(), "rerun.components.AxisLength".into(), ] }); -static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 6usize]> = +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 8usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.Transform3DIndicator".into(), "rerun.components.Transform3D".into(), "rerun.components.Translation3D".into(), + "rerun.components.RotationAxisAngle".into(), + "rerun.components.RotationQuat".into(), "rerun.components.Scale3D".into(), "rerun.components.TransformMat3x3".into(), "rerun.components.AxisLength".into(), @@ -234,8 +248,8 @@ static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 6usize]> = }); impl Transform3D { - /// The total number of components in the archetype: 0 required, 1 recommended, 5 optional - pub const NUM_COMPONENTS: usize = 6usize; + /// The total number of components in the archetype: 0 required, 1 recommended, 7 optional + pub const NUM_COMPONENTS: usize = 8usize; } /// Indicator component for the [`Transform3D`] [`::re_types_core::Archetype`] @@ -316,6 +330,31 @@ impl ::re_types_core::Archetype for Transform3D { } else { None }; + let rotation_axis_angle = + if let Some(array) = arrays_by_name.get("rerun.components.RotationAxisAngle") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Transform3D#rotation_axis_angle")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.Transform3D#rotation_axis_angle")? + }) + } else { + None + }; + let quaternion = if let Some(array) = arrays_by_name.get("rerun.components.RotationQuat") { + Some({ + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.Transform3D#quaternion")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .collect::>>() + .with_context("rerun.archetypes.Transform3D#quaternion")? + }) + } else { + None + }; let scale = if let Some(array) = arrays_by_name.get("rerun.components.Scale3D") { Some({ ::from_arrow_opt(&**array) @@ -352,6 +391,8 @@ impl ::re_types_core::Archetype for Transform3D { Ok(Self { transform, translation, + rotation_axis_angle, + quaternion, scale, mat3x3, axis_length, @@ -369,6 +410,12 @@ impl ::re_types_core::AsComponents for Transform3D { self.translation .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.rotation_axis_angle + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), + self.quaternion + .as_ref() + .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), self.scale .as_ref() .map(|comp_batch| (comp_batch as &dyn ComponentBatch).into()), @@ -394,6 +441,8 @@ impl Transform3D { Self { transform: transform.into(), translation: None, + rotation_axis_angle: None, + quaternion: None, scale: None, mat3x3: None, axis_length: None, @@ -410,6 +459,26 @@ impl Transform3D { self } + /// Rotation via axis + angle. + #[inline] + pub fn with_rotation_axis_angle( + mut self, + rotation_axis_angle: impl IntoIterator>, + ) -> Self { + self.rotation_axis_angle = Some(rotation_axis_angle.into_iter().map(Into::into).collect()); + self + } + + /// Rotation via quaternion. + #[inline] + pub fn with_quaternion( + mut self, + quaternion: impl IntoIterator>, + ) -> Self { + self.quaternion = Some(quaternion.into_iter().map(Into::into).collect()); + self + } + /// Scaling factor. #[inline] pub fn with_scale( diff --git a/crates/store/re_types/src/archetypes/transform3d_ext.rs b/crates/store/re_types/src/archetypes/transform3d_ext.rs index 6c3baa307342..72b3e67cabb7 100644 --- a/crates/store/re_types/src/archetypes/transform3d_ext.rs +++ b/crates/store/re_types/src/archetypes/transform3d_ext.rs @@ -1,11 +1,26 @@ use crate::{ components::{Scale3D, TransformMat3x3, Translation3D}, - datatypes::{Rotation3D, TranslationRotationScale3D}, + Rotation3D, }; use super::Transform3D; impl Transform3D { + /// Convenience method that takes any kind of (single) rotation representation and sets it on this transform. + #[inline] + pub fn with_rotation(self, rotation: impl Into) -> Self { + match rotation.into() { + Rotation3D::Quaternion(quaternion) => Self { + quaternion: Some(vec![quaternion]), + ..self + }, + Rotation3D::AxisAngle(rotation_axis_angle) => Self { + rotation_axis_angle: Some(vec![rotation_axis_angle]), + ..self + }, + } + } + /// From a translation. #[inline] pub fn from_translation(translation: impl Into) -> Self { @@ -27,10 +42,7 @@ impl Transform3D { /// From a rotation #[inline] pub fn from_rotation(rotation: impl Into) -> Self { - Self { - transform: TranslationRotationScale3D::from_rotation(rotation).into(), - ..Self::default() - } + Self::default().with_rotation(rotation) } /// From a scale @@ -49,10 +61,10 @@ impl Transform3D { rotation: impl Into, ) -> Self { Self { - transform: TranslationRotationScale3D::from_rotation(rotation).into(), translation: Some(vec![translation.into()]), ..Self::default() } + .with_rotation(rotation) } /// From a translation applied after a 3x3 matrix. @@ -89,21 +101,21 @@ impl Transform3D { scale: impl Into, ) -> Self { Self { - transform: TranslationRotationScale3D::from_rotation(rotation).into(), scale: Some(vec![scale.into()]), translation: Some(vec![translation.into()]), ..Self::default() } + .with_rotation(rotation) } /// From a rotation & scale #[inline] pub fn from_rotation_scale(rotation: impl Into, scale: impl Into) -> Self { Self { - transform: TranslationRotationScale3D::from_rotation(rotation).into(), scale: Some(vec![scale.into()]), ..Self::default() } + .with_rotation(rotation) } /// Indicate that this transform is from parent to child. diff --git a/crates/store/re_types/src/components/.gitattributes b/crates/store/re_types/src/components/.gitattributes index c3fb38da0562..a68885020fec 100644 --- a/crates/store/re_types/src/components/.gitattributes +++ b/crates/store/re_types/src/components/.gitattributes @@ -38,6 +38,8 @@ range1d.rs linguist-generated=true resolution.rs linguist-generated=true resolution2d.rs linguist-generated=true rotation3d.rs linguist-generated=true +rotation_axis_angle.rs linguist-generated=true +rotation_quat.rs linguist-generated=true scalar.rs linguist-generated=true scale3d.rs linguist-generated=true stroke_width.rs linguist-generated=true diff --git a/crates/store/re_types/src/components/mod.rs b/crates/store/re_types/src/components/mod.rs index 8b19349ae5f3..31cbd5bcd5a6 100644 --- a/crates/store/re_types/src/components/mod.rs +++ b/crates/store/re_types/src/components/mod.rs @@ -66,6 +66,10 @@ mod resolution2d_ext; mod resolution_ext; mod rotation3d; mod rotation3d_ext; +mod rotation_axis_angle; +mod rotation_axis_angle_ext; +mod rotation_quat; +mod rotation_quat_ext; mod scalar; mod scalar_ext; mod scale3d; @@ -134,6 +138,8 @@ pub use self::range1d::Range1D; pub use self::resolution::Resolution; pub use self::resolution2d::Resolution2D; pub use self::rotation3d::Rotation3D; +pub use self::rotation_axis_angle::RotationAxisAngle; +pub use self::rotation_quat::RotationQuat; pub use self::scalar::Scalar; pub use self::scale3d::Scale3D; pub use self::stroke_width::StrokeWidth; diff --git a/crates/store/re_types/src/components/rotation_axis_angle.rs b/crates/store/re_types/src/components/rotation_axis_angle.rs new file mode 100644 index 000000000000..a75035c69b46 --- /dev/null +++ b/crates/store/re_types/src/components/rotation_axis_angle.rs @@ -0,0 +1,105 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs +// Based on "crates/store/re_types/definitions/rerun/components/rotation_axis_angle.fbs". + +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::cloned_instead_of_copied)] +#![allow(clippy::map_flatten)] +#![allow(clippy::needless_question_mark)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] + +use ::re_types_core::external::arrow2; +use ::re_types_core::ComponentName; +use ::re_types_core::SerializationResult; +use ::re_types_core::{ComponentBatch, MaybeOwnedComponentBatch}; +use ::re_types_core::{DeserializationError, DeserializationResult}; + +/// **Component**: 3D rotation represented by a rotation around a given axis. +#[derive(Clone, Debug, Default, Copy, PartialEq)] +#[repr(transparent)] +pub struct RotationAxisAngle(pub crate::datatypes::RotationAxisAngle); + +impl ::re_types_core::SizeBytes for RotationAxisAngle { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + ::is_pod() + } +} + +impl> From for RotationAxisAngle { + fn from(v: T) -> Self { + Self(v.into()) + } +} + +impl std::borrow::Borrow for RotationAxisAngle { + #[inline] + fn borrow(&self) -> &crate::datatypes::RotationAxisAngle { + &self.0 + } +} + +impl std::ops::Deref for RotationAxisAngle { + type Target = crate::datatypes::RotationAxisAngle; + + #[inline] + fn deref(&self) -> &crate::datatypes::RotationAxisAngle { + &self.0 + } +} + +impl std::ops::DerefMut for RotationAxisAngle { + #[inline] + fn deref_mut(&mut self) -> &mut crate::datatypes::RotationAxisAngle { + &mut self.0 + } +} + +::re_types_core::macros::impl_into_cow!(RotationAxisAngle); + +impl ::re_types_core::Loggable for RotationAxisAngle { + type Name = ::re_types_core::ComponentName; + + #[inline] + fn name() -> Self::Name { + "rerun.components.RotationAxisAngle".into() + } + + #[inline] + fn arrow_datatype() -> arrow2::datatypes::DataType { + crate::datatypes::RotationAxisAngle::arrow_datatype() + } + + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult> + where + Self: Clone + 'a, + { + crate::datatypes::RotationAxisAngle::to_arrow_opt(data.into_iter().map(|datum| { + datum.map(|datum| match datum.into() { + ::std::borrow::Cow::Borrowed(datum) => ::std::borrow::Cow::Borrowed(&datum.0), + ::std::borrow::Cow::Owned(datum) => ::std::borrow::Cow::Owned(datum.0), + }) + })) + } + + fn from_arrow_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + crate::datatypes::RotationAxisAngle::from_arrow_opt(arrow_data) + .map(|v| v.into_iter().map(|v| v.map(Self)).collect()) + } +} diff --git a/crates/store/re_types/src/components/rotation_axis_angle_ext.rs b/crates/store/re_types/src/components/rotation_axis_angle_ext.rs new file mode 100644 index 000000000000..76737ba8f10d --- /dev/null +++ b/crates/store/re_types/src/components/rotation_axis_angle_ext.rs @@ -0,0 +1,19 @@ +use crate::datatypes; + +use super::RotationAxisAngle; + +impl RotationAxisAngle { + /// Create a new rotation from an axis and an angle. + #[inline] + pub fn new(axis: impl Into, angle: impl Into) -> Self { + Self(datatypes::RotationAxisAngle::new(axis, angle)) + } +} + +#[cfg(feature = "glam")] +impl From for glam::Affine3A { + #[inline] + fn from(val: RotationAxisAngle) -> Self { + Self::from_axis_angle(val.0.axis.into(), val.0.angle.radians()) + } +} diff --git a/crates/store/re_types/src/components/rotation_quat.rs b/crates/store/re_types/src/components/rotation_quat.rs new file mode 100644 index 000000000000..8f9629497b8e --- /dev/null +++ b/crates/store/re_types/src/components/rotation_quat.rs @@ -0,0 +1,116 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs +// Based on "crates/store/re_types/definitions/rerun/components/rotation_quat.fbs". + +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::cloned_instead_of_copied)] +#![allow(clippy::map_flatten)] +#![allow(clippy::needless_question_mark)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] + +use ::re_types_core::external::arrow2; +use ::re_types_core::ComponentName; +use ::re_types_core::SerializationResult; +use ::re_types_core::{ComponentBatch, MaybeOwnedComponentBatch}; +use ::re_types_core::{DeserializationError, DeserializationResult}; + +/// **Component**: A 3D rotation expressed as a quaternion. +/// +/// Note: although the x,y,z,w components of the quaternion will be passed through to the +/// datastore as provided, when used in the Viewer Quaternions will always be normalized. +#[derive(Clone, Debug, Default, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(transparent)] +pub struct RotationQuat(pub crate::datatypes::Quaternion); + +impl ::re_types_core::SizeBytes for RotationQuat { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + ::is_pod() + } +} + +impl> From for RotationQuat { + fn from(v: T) -> Self { + Self(v.into()) + } +} + +impl std::borrow::Borrow for RotationQuat { + #[inline] + fn borrow(&self) -> &crate::datatypes::Quaternion { + &self.0 + } +} + +impl std::ops::Deref for RotationQuat { + type Target = crate::datatypes::Quaternion; + + #[inline] + fn deref(&self) -> &crate::datatypes::Quaternion { + &self.0 + } +} + +impl std::ops::DerefMut for RotationQuat { + #[inline] + fn deref_mut(&mut self) -> &mut crate::datatypes::Quaternion { + &mut self.0 + } +} + +::re_types_core::macros::impl_into_cow!(RotationQuat); + +impl ::re_types_core::Loggable for RotationQuat { + type Name = ::re_types_core::ComponentName; + + #[inline] + fn name() -> Self::Name { + "rerun.components.RotationQuat".into() + } + + #[inline] + fn arrow_datatype() -> arrow2::datatypes::DataType { + crate::datatypes::Quaternion::arrow_datatype() + } + + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult> + where + Self: Clone + 'a, + { + crate::datatypes::Quaternion::to_arrow_opt(data.into_iter().map(|datum| { + datum.map(|datum| match datum.into() { + ::std::borrow::Cow::Borrowed(datum) => ::std::borrow::Cow::Borrowed(&datum.0), + ::std::borrow::Cow::Owned(datum) => ::std::borrow::Cow::Owned(datum.0), + }) + })) + } + + fn from_arrow_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + crate::datatypes::Quaternion::from_arrow_opt(arrow_data) + .map(|v| v.into_iter().map(|v| v.map(Self)).collect()) + } + + #[inline] + fn from_arrow(arrow_data: &dyn arrow2::array::Array) -> DeserializationResult> + where + Self: Sized, + { + crate::datatypes::Quaternion::from_arrow(arrow_data).map(bytemuck::cast_vec) + } +} diff --git a/crates/store/re_types/src/components/rotation_quat_ext.rs b/crates/store/re_types/src/components/rotation_quat_ext.rs new file mode 100644 index 000000000000..ef4510515e32 --- /dev/null +++ b/crates/store/re_types/src/components/rotation_quat_ext.rs @@ -0,0 +1,17 @@ +use super::RotationQuat; + +impl RotationQuat { + /// The identity rotation, representing no rotation. + /// + /// Keep in mind that logging an identity rotation is different from logging no rotation at all + /// in thus far that it will write data to the store. + pub const IDENTITY: Self = Self(crate::datatypes::Quaternion::IDENTITY); +} + +#[cfg(feature = "glam")] +impl From for glam::Affine3A { + #[inline] + fn from(val: RotationQuat) -> Self { + Self::from_quat(val.0.into()) + } +} diff --git a/crates/store/re_types/src/datatypes/quaternion.rs b/crates/store/re_types/src/datatypes/quaternion.rs index be00af7c3900..e4ef5ef70edb 100644 --- a/crates/store/re_types/src/datatypes/quaternion.rs +++ b/crates/store/re_types/src/datatypes/quaternion.rs @@ -22,7 +22,8 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// /// Note: although the x,y,z,w components of the quaternion will be passed through to the /// datastore as provided, when used in the Viewer Quaternions will always be normalized. -#[derive(Clone, Debug, Copy, PartialEq, PartialOrd)] +#[derive(Clone, Debug, Copy, PartialEq, PartialOrd, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] pub struct Quaternion(pub [f32; 4usize]); impl ::re_types_core::SizeBytes for Quaternion { diff --git a/crates/store/re_types/src/datatypes/rotation3d_ext.rs b/crates/store/re_types/src/datatypes/rotation3d_ext.rs index f0ba1deafc0f..27d27485b55c 100644 --- a/crates/store/re_types/src/datatypes/rotation3d_ext.rs +++ b/crates/store/re_types/src/datatypes/rotation3d_ext.rs @@ -65,3 +65,14 @@ impl Default for Rotation3D { Self::IDENTITY } } + +// TODO(#6831): Transitional, to be removed with the rest of this file. +impl From for Rotation3D { + #[inline] + fn from(r: crate::Rotation3D) -> Self { + match r { + crate::Rotation3D::Quaternion(q) => Self::Quaternion(q.0), + crate::Rotation3D::AxisAngle(a) => Self::AxisAngle(a.0), + } + } +} diff --git a/crates/store/re_types/src/datatypes/rotation_axis_angle_ext.rs b/crates/store/re_types/src/datatypes/rotation_axis_angle_ext.rs index a5b8cfadf52a..8a7a7b40f2cd 100644 --- a/crates/store/re_types/src/datatypes/rotation_axis_angle_ext.rs +++ b/crates/store/re_types/src/datatypes/rotation_axis_angle_ext.rs @@ -38,3 +38,13 @@ impl From for mint::Quaternion { [val.axis.x() * s, val.axis.y() * s, val.axis.z() * s, c].into() } } + +impl Default for RotationAxisAngle { + #[inline] + fn default() -> Self { + Self { + axis: Vec3D::new(1.0, 0.0, 0.0), + angle: Angle::from_radians(0.0), + } + } +} diff --git a/crates/store/re_types/src/lib.rs b/crates/store/re_types/src/lib.rs index 6ac3162e82c1..97fd657e0949 100644 --- a/crates/store/re_types/src/lib.rs +++ b/crates/store/re_types/src/lib.rs @@ -280,5 +280,8 @@ pub mod image; pub mod tensor_data; pub mod view_coordinates; +mod rotation3d; +pub use rotation3d::Rotation3D; + #[cfg(feature = "testing")] pub mod testing; diff --git a/crates/store/re_types/src/rotation3d.rs b/crates/store/re_types/src/rotation3d.rs new file mode 100644 index 000000000000..67e996c45d69 --- /dev/null +++ b/crates/store/re_types/src/rotation3d.rs @@ -0,0 +1,54 @@ +use crate::{components, datatypes}; + +/// A 3D rotation. +/// +/// This is *not* a component, but a helper type for populating [`crate::archetypes::Transform3D`] with rotations. +#[derive(Clone, Debug, Copy, PartialEq)] +pub enum Rotation3D { + /// Rotation defined by a quaternion. + Quaternion(components::RotationQuat), + + /// Rotation defined with an axis and an angle. + AxisAngle(components::RotationAxisAngle), +} + +impl Rotation3D { + /// The identity rotation, expressed as a quaternion + pub const IDENTITY: Self = Self::Quaternion(components::RotationQuat::IDENTITY); +} + +impl From for Rotation3D { + #[inline] + fn from(quat: components::RotationQuat) -> Self { + Self::Quaternion(quat) + } +} + +impl From for Rotation3D { + #[inline] + fn from(quat: datatypes::Quaternion) -> Self { + Self::Quaternion(quat.into()) + } +} + +#[cfg(feature = "glam")] +impl From for Rotation3D { + #[inline] + fn from(quat: glam::Quat) -> Self { + Self::Quaternion(quat.into()) + } +} + +impl From for Rotation3D { + #[inline] + fn from(axis_angle: components::RotationAxisAngle) -> Self { + Self::AxisAngle(axis_angle) + } +} + +impl From for Rotation3D { + #[inline] + fn from(axis_angle: datatypes::RotationAxisAngle) -> Self { + Self::AxisAngle(axis_angle.into()) + } +} diff --git a/crates/store/re_types/tests/transform3d.rs b/crates/store/re_types/tests/transform3d.rs index a0e78a9739b6..27dd1835a8f3 100644 --- a/crates/store/re_types/tests/transform3d.rs +++ b/crates/store/re_types/tests/transform3d.rs @@ -3,9 +3,7 @@ use std::{collections::HashMap, f32::consts::TAU}; use re_types::{ archetypes::Transform3D, components::{self, Scale3D}, - datatypes::{ - self, Angle, Mat3x3, Rotation3D, RotationAxisAngle, TranslationRotationScale3D, Vec3D, - }, + datatypes::{self, Angle, Mat3x3, RotationAxisAngle, TranslationRotationScale3D, Vec3D}, Archetype as _, AsComponents as _, }; @@ -21,10 +19,7 @@ fn roundtrip() { from_parent: false, }, )), - mat3x3: None, - scale: None, - translation: None, - axis_length: None, + ..Default::default() }, // Transform3D { transform: components::Transform3D(datatypes::Transform3D::TranslationRotationScale( @@ -35,44 +30,44 @@ fn roundtrip() { from_parent: true, }, )), - mat3x3: None, - scale: Some(vec![Scale3D::uniform(42.0)]), translation: Some(vec![Vec3D([1.0, 2.0, 3.0]).into()]), - axis_length: None, + scale: Some(vec![Scale3D::uniform(42.0)]), + ..Default::default() }, // Transform3D { transform: components::Transform3D(datatypes::Transform3D::TranslationRotationScale( TranslationRotationScale3D { translation: None, - rotation: Some(Rotation3D::AxisAngle(RotationAxisAngle { - axis: Vec3D([0.2, 0.2, 0.8]), - angle: Angle::from_radians(0.5 * TAU), - })), + rotation: None, scale: None, from_parent: false, }, )), - mat3x3: None, - scale: None, translation: Some(vec![[1.0, 2.0, 3.0].into()]), - axis_length: None, + rotation_axis_angle: Some(vec![RotationAxisAngle { + axis: Vec3D([0.2, 0.2, 0.8]), + angle: Angle::from_radians(0.5 * TAU), + } + .into()]), + ..Default::default() }, // Transform3D { transform: components::Transform3D(datatypes::Transform3D::TranslationRotationScale( TranslationRotationScale3D { translation: None, - rotation: Some(Rotation3D::AxisAngle(RotationAxisAngle { - axis: Vec3D([0.2, 0.2, 0.8]), - angle: Angle::from_radians(0.5 * TAU), - })), + rotation: None, scale: None, from_parent: true, }, )), - mat3x3: None, - scale: Some(vec![Scale3D::uniform(42.0)]), translation: Some(vec![Vec3D([1.0, 2.0, 3.0]).into()]), - axis_length: None, + rotation_axis_angle: Some(vec![RotationAxisAngle { + axis: Vec3D([0.2, 0.2, 0.8]), + angle: Angle::from_radians(0.5 * TAU), + } + .into()]), + scale: Some(vec![Scale3D::uniform(42.0)]), + ..Default::default() }, // Transform3D { transform: components::Transform3D(datatypes::Transform3D::TranslationRotationScale( @@ -83,10 +78,8 @@ fn roundtrip() { from_parent: true, }, )), - mat3x3: None, - scale: None, translation: Some(vec![Vec3D([1.0, 2.0, 3.0]).into()]), - axis_length: None, + ..Default::default() }, // Transform3D { transform: components::Transform3D(datatypes::Transform3D::TranslationRotationScale( @@ -100,9 +93,7 @@ fn roundtrip() { mat3x3: Some(vec![ Mat3x3([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]).into() ]), - scale: None, - translation: None, - axis_length: None, + ..Default::default() }, // ]; diff --git a/crates/top/rerun/src/sdk.rs b/crates/top/rerun/src/sdk.rs index 1277be52e29e..720331c1fd6f 100644 --- a/crates/top/rerun/src/sdk.rs +++ b/crates/top/rerun/src/sdk.rs @@ -20,6 +20,9 @@ mod prelude { // the amount of typing for our users. pub use re_types::archetypes::*; + // Special utility types. + pub use re_types::Rotation3D; + // Also import any component or datatype that has a unique name: pub use re_chunk::ChunkTimeline; pub use re_types::components::{ @@ -29,8 +32,8 @@ mod prelude { }; pub use re_types::datatypes::{ Angle, AnnotationInfo, ClassDescription, Float32, KeypointPair, Mat3x3, Quaternion, Rgba32, - Rotation3D, RotationAxisAngle, TensorBuffer, TensorData, TensorDimension, - TranslationRotationScale3D, Vec2D, Vec3D, Vec4D, + RotationAxisAngle, TensorBuffer, TensorData, TensorDimension, TranslationRotationScale3D, + Vec2D, Vec3D, Vec4D, }; } pub use prelude::*; diff --git a/crates/viewer/re_space_view_spatial/src/contexts/transform_context.rs b/crates/viewer/re_space_view_spatial/src/contexts/transform_context.rs index 28a188062e28..a8bc7d9eea31 100644 --- a/crates/viewer/re_space_view_spatial/src/contexts/transform_context.rs +++ b/crates/viewer/re_space_view_spatial/src/contexts/transform_context.rs @@ -6,8 +6,8 @@ use re_space_view::DataResultQuery as _; use re_types::{ archetypes::Pinhole, components::{ - DisconnectedSpace, ImagePlaneDistance, PinholeProjection, Scale3D, Transform3D, - TransformMat3x3, Translation3D, ViewCoordinates, + DisconnectedSpace, ImagePlaneDistance, PinholeProjection, RotationAxisAngle, RotationQuat, + Scale3D, Transform3D, TransformMat3x3, Translation3D, ViewCoordinates, }, ComponentNameSet, Loggable as _, }; @@ -307,6 +307,8 @@ fn debug_assert_transform_field_order(reflection: &re_types::reflection::Reflect let expected_order = vec![ Transform3D::name(), Translation3D::name(), + RotationAxisAngle::name(), + RotationQuat::name(), Scale3D::name(), TransformMat3x3::name(), ]; @@ -353,6 +355,8 @@ fn get_parent_from_child_transform( [ Transform3D::name(), Translation3D::name(), + RotationAxisAngle::name(), + RotationQuat::name(), Scale3D::name(), TransformMat3x3::name(), ], @@ -370,6 +374,12 @@ fn get_parent_from_child_transform( if let Some(scale) = result.get_instance::(resolver, 0) { transform *= glam::Affine3A::from(scale); } + if let Some(rotation) = result.get_instance::(resolver, 0) { + transform *= glam::Affine3A::from(rotation); + } + if let Some(rotation) = result.get_instance::(resolver, 0) { + transform *= glam::Affine3A::from(rotation); + } if let Some(translation) = result.get_instance::(resolver, 0) { transform *= glam::Affine3A::from(translation); } diff --git a/crates/viewer/re_time_panel/benches/bench_density_graph.rs b/crates/viewer/re_time_panel/benches/bench_density_graph.rs index 88e81d354f09..a80c5da91ec0 100644 --- a/crates/viewer/re_time_panel/benches/bench_density_graph.rs +++ b/crates/viewer/re_time_panel/benches/bench_density_graph.rs @@ -98,13 +98,10 @@ fn add_data( let components = (0..num_rows_per_chunk).map(|i| { let angle_deg = i as f32 % 360.0; re_types::archetypes::Transform3D::from_rotation( - re_types::datatypes::Rotation3D::AxisAngle( - ( - (0.0, 0.0, 1.0), - re_types::datatypes::Angle::from_degrees(angle_deg), - ) - .into(), - ), + re_types::datatypes::RotationAxisAngle { + axis: (0.0, 0.0, 1.0).into(), + angle: re_types::datatypes::Angle::from_degrees(angle_deg), + }, ) }); diff --git a/crates/viewer/re_viewer/src/reflection/mod.rs b/crates/viewer/re_viewer/src/reflection/mod.rs index 0204fbd0bc9a..1a7d2436b9c3 100644 --- a/crates/viewer/re_viewer/src/reflection/mod.rs +++ b/crates/viewer/re_viewer/src/reflection/mod.rs @@ -496,6 +496,20 @@ fn generate_component_reflection() -> Result::name(), + ComponentReflection { + docstring_md: "3D rotation represented by a rotation around a given axis.", + placeholder: Some(RotationAxisAngle::default().to_arrow()?), + }, + ), + ( + ::name(), + ComponentReflection { + docstring_md: "A 3D rotation expressed as a quaternion.\n\nNote: although the x,y,z,w components of the quaternion will be passed through to the\ndatastore as provided, when used in the Viewer Quaternions will always be normalized.", + placeholder: Some(RotationQuat::default().to_arrow()?), + }, + ), ( ::name(), ComponentReflection { @@ -638,6 +652,12 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap { component_name : "rerun.components.Translation3D".into(), display_name : "Translation", docstring_md : "Translation vectors.", }, ArchetypeFieldReflection { component_name : + "rerun.components.RotationAxisAngle".into(), display_name : + "Rotation axis angle", docstring_md : "Rotation via axis + angle.", + }, ArchetypeFieldReflection { component_name : + "rerun.components.RotationQuat".into(), display_name : "Quaternion", + docstring_md : "Rotation via quaternion.", }, + ArchetypeFieldReflection { component_name : "rerun.components.Scale3D".into(), display_name : "Scale", docstring_md : "Scaling factor.", }, ArchetypeFieldReflection { component_name : "rerun.components.TransformMat3x3".into(), diff --git a/docs/content/reference/types/archetypes/transform3d.md b/docs/content/reference/types/archetypes/transform3d.md index d20b90aaa4af..0edcd93b1999 100644 --- a/docs/content/reference/types/archetypes/transform3d.md +++ b/docs/content/reference/types/archetypes/transform3d.md @@ -15,7 +15,7 @@ TODO(#6831): write more about the exact interaction with the to be written `OutO ## Components -**Optional**: [`Transform3D`](../components/transform3d.md), [`Translation3D`](../components/translation3d.md), [`Scale3D`](../components/scale3d.md), [`TransformMat3x3`](../components/transform_mat3x3.md), [`AxisLength`](../components/axis_length.md) +**Optional**: [`Transform3D`](../components/transform3d.md), [`Translation3D`](../components/translation3d.md), [`RotationAxisAngle`](../components/rotation_axis_angle.md), [`RotationQuat`](../components/rotation_quat.md), [`Scale3D`](../components/scale3d.md), [`TransformMat3x3`](../components/transform_mat3x3.md), [`AxisLength`](../components/axis_length.md) ## Shown in * [Spatial3DView](../views/spatial3d_view.md) diff --git a/docs/content/reference/types/components.md b/docs/content/reference/types/components.md index ead8906d4967..dcd0cc8348fe 100644 --- a/docs/content/reference/types/components.md +++ b/docs/content/reference/types/components.md @@ -50,6 +50,8 @@ on [Entities and Components](../../concepts/entity-component.md). * [`Resolution`](components/resolution.md): Pixel resolution width & height, e.g. of a camera sensor. * [`Resolution2D`](components/resolution2d.md): The width and height of a 2D image. * [`Rotation3D`](components/rotation3d.md): A 3D rotation, represented either by a quaternion or a rotation around axis. +* [`RotationAxisAngle`](components/rotation_axis_angle.md): 3D rotation represented by a rotation around a given axis. +* [`RotationQuat`](components/rotation_quat.md): A 3D rotation expressed as a quaternion. * [`Scalar`](components/scalar.md): A scalar value, encoded as a 64-bit floating point. * [`Scale3D`](components/scale3d.md): A 3D scale factor. * [`StrokeWidth`](components/stroke_width.md): The width of a stroke specified in UI points. diff --git a/docs/content/reference/types/components/.gitattributes b/docs/content/reference/types/components/.gitattributes index 21d7f361a6fd..c7b73c3d4531 100644 --- a/docs/content/reference/types/components/.gitattributes +++ b/docs/content/reference/types/components/.gitattributes @@ -38,6 +38,8 @@ range1d.md linguist-generated=true resolution.md linguist-generated=true resolution2d.md linguist-generated=true rotation3d.md linguist-generated=true +rotation_axis_angle.md linguist-generated=true +rotation_quat.md linguist-generated=true scalar.md linguist-generated=true scale3d.md linguist-generated=true stroke_width.md linguist-generated=true diff --git a/docs/content/reference/types/components/rotation_axis_angle.md b/docs/content/reference/types/components/rotation_axis_angle.md new file mode 100644 index 000000000000..ca85966a639c --- /dev/null +++ b/docs/content/reference/types/components/rotation_axis_angle.md @@ -0,0 +1,20 @@ +--- +title: "RotationAxisAngle" +--- + + +3D rotation represented by a rotation around a given axis. + +## Fields + +* rotation: [`RotationAxisAngle`](../datatypes/rotation_axis_angle.md) + +## API reference links + * 🌊 [C++ API docs for `RotationAxisAngle`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1RotationAxisAngle.html?speculative-link) + * 🐍 [Python API docs for `RotationAxisAngle`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.RotationAxisAngle) + * 🦀 [Rust API docs for `RotationAxisAngle`](https://docs.rs/rerun/latest/rerun/components/struct.RotationAxisAngle.html?speculative-link) + + +## Used by + +* [`Transform3D`](../archetypes/transform3d.md) diff --git a/docs/content/reference/types/components/rotation_quat.md b/docs/content/reference/types/components/rotation_quat.md new file mode 100644 index 000000000000..1422195afdec --- /dev/null +++ b/docs/content/reference/types/components/rotation_quat.md @@ -0,0 +1,23 @@ +--- +title: "RotationQuat" +--- + + +A 3D rotation expressed as a quaternion. + +Note: although the x,y,z,w components of the quaternion will be passed through to the +datastore as provided, when used in the Viewer Quaternions will always be normalized. + +## Fields + +* quaternion: [`Quaternion`](../datatypes/quaternion.md) + +## API reference links + * 🌊 [C++ API docs for `RotationQuat`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1RotationQuat.html?speculative-link) + * 🐍 [Python API docs for `RotationQuat`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.RotationQuat) + * 🦀 [Rust API docs for `RotationQuat`](https://docs.rs/rerun/latest/rerun/components/struct.RotationQuat.html?speculative-link) + + +## Used by + +* [`Transform3D`](../archetypes/transform3d.md) diff --git a/docs/content/reference/types/datatypes/quaternion.md b/docs/content/reference/types/datatypes/quaternion.md index ff420a19c993..2dee7995e377 100644 --- a/docs/content/reference/types/datatypes/quaternion.md +++ b/docs/content/reference/types/datatypes/quaternion.md @@ -21,3 +21,4 @@ datastore as provided, when used in the Viewer Quaternions will always be normal ## Used by * [`Rotation3D`](../datatypes/rotation3d.md) +* [`RotationQuat`](../components/rotation_quat.md?speculative-link) diff --git a/docs/content/reference/types/datatypes/rotation_axis_angle.md b/docs/content/reference/types/datatypes/rotation_axis_angle.md index afa3e7f669cb..d758088d2df4 100644 --- a/docs/content/reference/types/datatypes/rotation_axis_angle.md +++ b/docs/content/reference/types/datatypes/rotation_axis_angle.md @@ -19,3 +19,4 @@ title: "RotationAxisAngle" ## Used by * [`Rotation3D`](../datatypes/rotation3d.md) +* [`RotationAxisAngle`](../components/rotation_axis_angle.md?speculative-link) diff --git a/rerun_cpp/src/rerun.hpp b/rerun_cpp/src/rerun.hpp index 387bdb90e8bb..d994085f5d43 100644 --- a/rerun_cpp/src/rerun.hpp +++ b/rerun_cpp/src/rerun.hpp @@ -55,7 +55,6 @@ namespace rerun { using datatypes::Mat3x3; using datatypes::Quaternion; using datatypes::Rgba32; - using datatypes::Rotation3D; using datatypes::RotationAxisAngle; using datatypes::Scale3D; using datatypes::TensorBuffer; diff --git a/rerun_cpp/src/rerun/archetypes/transform3d.cpp b/rerun_cpp/src/rerun/archetypes/transform3d.cpp index 3376c7631464..f7e6050a21ab 100644 --- a/rerun_cpp/src/rerun/archetypes/transform3d.cpp +++ b/rerun_cpp/src/rerun/archetypes/transform3d.cpp @@ -14,7 +14,7 @@ namespace rerun { ) { using namespace archetypes; std::vector cells; - cells.reserve(6); + cells.reserve(8); { auto result = DataCell::from_loggable(archetype.transform); @@ -26,6 +26,16 @@ namespace rerun { RR_RETURN_NOT_OK(result.error); cells.push_back(std::move(result.value)); } + if (archetype.rotation_axis_angle.has_value()) { + auto result = DataCell::from_loggable(archetype.rotation_axis_angle.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.quaternion.has_value()) { + auto result = DataCell::from_loggable(archetype.quaternion.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } if (archetype.scale.has_value()) { auto result = DataCell::from_loggable(archetype.scale.value()); RR_RETURN_NOT_OK(result.error); diff --git a/rerun_cpp/src/rerun/archetypes/transform3d.hpp b/rerun_cpp/src/rerun/archetypes/transform3d.hpp index d14e182327ec..3a35f6035662 100644 --- a/rerun_cpp/src/rerun/archetypes/transform3d.hpp +++ b/rerun_cpp/src/rerun/archetypes/transform3d.hpp @@ -6,6 +6,8 @@ #include "../collection.hpp" #include "../compiler_utils.hpp" #include "../components/axis_length.hpp" +#include "../components/rotation_axis_angle.hpp" +#include "../components/rotation_quat.hpp" #include "../components/scale3d.hpp" #include "../components/transform3d.hpp" #include "../components/transform_mat3x3.hpp" @@ -14,6 +16,7 @@ #include "../indicator_component.hpp" #include "../rerun_sdk_export.hpp" #include "../result.hpp" +#include "../rotation3d.hpp" #include #include @@ -151,6 +154,12 @@ namespace rerun::archetypes { /// Translation vectors. std::optional> translation; + /// Rotation via axis + angle. + std::optional> rotation_axis_angle; + + /// Rotation via quaternion. + std::optional> quaternion; + /// Scaling factor. std::optional> scale; @@ -282,28 +291,30 @@ namespace rerun::archetypes { /// Creates a new 3D transform from translation/rotation/scale. /// /// \param translation_ \copydoc Transform3D::translation - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param scale_ \copydoc Transform3D::scale /// \param from_parent \copydoc datatypes::TranslationRotationScale3D::from_parent Transform3D( - const components::Translation3D& translation_, const datatypes::Rotation3D& rotation, + const components::Translation3D& translation_, const Rotation3D& rotation, const components::Scale3D& scale_, bool from_parent = false ) - : transform(datatypes::TranslationRotationScale3D(rotation, from_parent)), + : transform(datatypes::TranslationRotationScale3D(from_parent)), translation(Collection::take_ownership(translation_)), - scale(Collection::take_ownership(scale_)) {} + scale(Collection::take_ownership(scale_)) { + set_rotation(rotation); + } /// Creates a new 3D transform from translation/rotation/uniform-scale. /// /// \param translation_ \copydoc Transform3D::translation - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param uniform_scale Uniform scale factor that is applied to all axis equally. /// \param from_parent \copydoc datatypes::TranslationRotationScale3D::from_parent /// /// _Implementation note:_ This explicit overload prevents interpretation of the float as /// bool, leading to a call to the wrong overload. Transform3D( - const components::Translation3D& translation_, const datatypes::Rotation3D& rotation, + const components::Translation3D& translation_, const Rotation3D& rotation, float uniform_scale, bool from_parent = false ) : Transform3D(translation_, rotation, components::Scale3D(uniform_scale), from_parent) { @@ -312,10 +323,10 @@ namespace rerun::archetypes { /// From a translation, applied after a rotation & scale, known as an affine transformation. /// /// \param translation \copydoc Transform3D::translation - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param scale \copydoc Transform3D::scale static Transform3D from_translation_rotation_scale( - const components::Translation3D& translation, const datatypes::Rotation3D& rotation, + const components::Translation3D& translation, const Rotation3D& rotation, const components::Scale3D& scale ) { return Transform3D(translation, rotation, scale, false); @@ -324,10 +335,10 @@ namespace rerun::archetypes { /// From a translation, applied after a rotation & scale, known as an affine transformation. /// /// \param translation \copydoc Transform3D::translation - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param uniform_scale Uniform scale factor that is applied to all axis equally. static Transform3D from_translation_rotation_scale( - const components::Translation3D& translation, const datatypes::Rotation3D& rotation, + const components::Translation3D& translation, const Rotation3D& rotation, float uniform_scale ) { return Transform3D(translation, rotation, components::Scale3D(uniform_scale), false); @@ -336,21 +347,23 @@ namespace rerun::archetypes { /// Creates a new rigid transform (translation & rotation only). /// /// \param translation_ \copydoc Transform3D::translation - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param from_parent \copydoc datatypes::TranslationRotationScale3D::from_parent Transform3D( - const components::Translation3D& translation_, const datatypes::Rotation3D& rotation, + const components::Translation3D& translation_, const Rotation3D& rotation, bool from_parent = false ) - : transform(datatypes::TranslationRotationScale3D(rotation, from_parent)), - translation(Collection::take_ownership(translation_)) {} + : transform(datatypes::TranslationRotationScale3D(from_parent)), + translation(Collection::take_ownership(translation_)) { + set_rotation(rotation); + } /// From a rotation & scale. /// /// \param translation \copydoc Transform3D::translation - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. static Transform3D from_translation_rotation( - const components::Translation3D& translation, const datatypes::Rotation3D& rotation + const components::Translation3D& translation, const Rotation3D& rotation ) { return Transform3D(translation, rotation, false); } @@ -394,60 +407,59 @@ namespace rerun::archetypes { /// From rotation & scale. /// - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param scale_ Transform3D::scale /// \param from_parent \copydoc datatypes::TranslationRotationScale3D::from_parent Transform3D( - const datatypes::Rotation3D& rotation, const components::Scale3D& scale_, - bool from_parent = false + const Rotation3D& rotation, const components::Scale3D& scale_, bool from_parent = false ) - : transform(datatypes::TranslationRotationScale3D(rotation, from_parent)), - scale(Collection::take_ownership(scale_)) {} + : transform(datatypes::TranslationRotationScale3D(from_parent)), + scale(Collection::take_ownership(scale_)) { + set_rotation(rotation); + } /// From rotation & uniform scale. /// - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param uniform_scale Uniform scale factor that is applied to all axis equally. /// \param from_parent \copydoc datatypes::TranslationRotationScale3D::from_parent /// /// _Implementation note:_ This explicit overload prevents interpretation of the float as /// bool, leading to a call to the wrong overload. - Transform3D( - const datatypes::Rotation3D& rotation, float uniform_scale, bool from_parent = false - ) + Transform3D(const Rotation3D& rotation, float uniform_scale, bool from_parent = false) : Transform3D(rotation, components::Scale3D(uniform_scale), from_parent) {} /// From a rotation & scale. /// - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param scale Transform3D::scale static Transform3D from_rotation_scale( - const datatypes::Rotation3D& rotation, const components::Scale3D& scale + const Rotation3D& rotation, const components::Scale3D& scale ) { return Transform3D(rotation, scale, false); } /// From a rotation & uniform scale. /// - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param uniform_scale Uniform scale factor that is applied to all axis equally. - static Transform3D from_rotation_scale( - const datatypes::Rotation3D& rotation, float uniform_scale - ) { + static Transform3D from_rotation_scale(const Rotation3D& rotation, float uniform_scale) { return Transform3D(rotation, components::Scale3D(uniform_scale), false); } /// From rotation only. /// - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param from_parent \copydoc datatypes::TranslationRotationScale3D::from_parent - Transform3D(const datatypes::Rotation3D& rotation, bool from_parent = false) - : transform(datatypes::TranslationRotationScale3D(rotation, from_parent)) {} + Transform3D(const Rotation3D& rotation, bool from_parent = false) + : transform(datatypes::TranslationRotationScale3D(from_parent)) { + set_rotation(rotation); + } /// From rotation only. /// - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation - static Transform3D from_rotation(const datatypes::Rotation3D& rotation) { + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. + static Transform3D from_rotation(const Rotation3D& rotation) { return Transform3D(rotation, false); } @@ -485,6 +497,21 @@ namespace rerun::archetypes { RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } + private: + /// Set the rotation component of the transform using the `rerun::Rotation3D` utility. + void set_rotation(const Rotation3D& rotation) { + if (rotation.axis_angle.has_value()) { + rotation_axis_angle = Collection::take_ownership( + rotation.axis_angle.value() + ); + } + if (rotation.quaternion.has_value()) { + quaternion = + Collection::take_ownership(rotation.quaternion.value() + ); + } + } + public: Transform3D() = default; Transform3D(Transform3D&& other) = default; @@ -499,6 +526,22 @@ namespace rerun::archetypes { RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } + /// Rotation via axis + angle. + Transform3D with_rotation_axis_angle( + Collection _rotation_axis_angle + ) && { + rotation_axis_angle = std::move(_rotation_axis_angle); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Rotation via quaternion. + Transform3D with_quaternion(Collection _quaternion) && { + quaternion = std::move(_quaternion); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + /// Scaling factor. Transform3D with_scale(Collection _scale) && { scale = std::move(_scale); diff --git a/rerun_cpp/src/rerun/archetypes/transform3d_ext.cpp b/rerun_cpp/src/rerun/archetypes/transform3d_ext.cpp index 9ff2878947c6..e6473bd160c2 100644 --- a/rerun_cpp/src/rerun/archetypes/transform3d_ext.cpp +++ b/rerun_cpp/src/rerun/archetypes/transform3d_ext.cpp @@ -2,6 +2,7 @@ // #include "../rerun_sdk_export.hpp" +#include "../rotation3d.hpp" // @@ -118,28 +119,30 @@ namespace rerun::archetypes { /// Creates a new 3D transform from translation/rotation/scale. /// /// \param translation_ \copydoc Transform3D::translation - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param scale_ \copydoc Transform3D::scale /// \param from_parent \copydoc datatypes::TranslationRotationScale3D::from_parent Transform3D( - const components::Translation3D& translation_, const datatypes::Rotation3D& rotation, + const components::Translation3D& translation_, const Rotation3D& rotation, const components::Scale3D& scale_, bool from_parent = false ) - : transform(datatypes::TranslationRotationScale3D(rotation, from_parent)), + : transform(datatypes::TranslationRotationScale3D(from_parent)), translation(Collection::take_ownership(translation_)), - scale(Collection::take_ownership(scale_)) {} + scale(Collection::take_ownership(scale_)) { + set_rotation(rotation); + } /// Creates a new 3D transform from translation/rotation/uniform-scale. /// /// \param translation_ \copydoc Transform3D::translation - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param uniform_scale Uniform scale factor that is applied to all axis equally. /// \param from_parent \copydoc datatypes::TranslationRotationScale3D::from_parent /// /// _Implementation note:_ This explicit overload prevents interpretation of the float as /// bool, leading to a call to the wrong overload. Transform3D( - const components::Translation3D& translation_, const datatypes::Rotation3D& rotation, + const components::Translation3D& translation_, const Rotation3D& rotation, float uniform_scale, bool from_parent = false ) : Transform3D(translation_, rotation, components::Scale3D(uniform_scale), from_parent) {} @@ -147,10 +150,10 @@ namespace rerun::archetypes { /// From a translation, applied after a rotation & scale, known as an affine transformation. /// /// \param translation \copydoc Transform3D::translation - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param scale \copydoc Transform3D::scale static Transform3D from_translation_rotation_scale( - const components::Translation3D& translation, const datatypes::Rotation3D& rotation, + const components::Translation3D& translation, const Rotation3D& rotation, const components::Scale3D& scale ) { return Transform3D(translation, rotation, scale, false); @@ -159,10 +162,10 @@ namespace rerun::archetypes { /// From a translation, applied after a rotation & scale, known as an affine transformation. /// /// \param translation \copydoc Transform3D::translation - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param uniform_scale Uniform scale factor that is applied to all axis equally. static Transform3D from_translation_rotation_scale( - const components::Translation3D& translation, const datatypes::Rotation3D& rotation, + const components::Translation3D& translation, const Rotation3D& rotation, float uniform_scale ) { return Transform3D(translation, rotation, components::Scale3D(uniform_scale), false); @@ -171,21 +174,23 @@ namespace rerun::archetypes { /// Creates a new rigid transform (translation & rotation only). /// /// \param translation_ \copydoc Transform3D::translation - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param from_parent \copydoc datatypes::TranslationRotationScale3D::from_parent Transform3D( - const components::Translation3D& translation_, const datatypes::Rotation3D& rotation, + const components::Translation3D& translation_, const Rotation3D& rotation, bool from_parent = false ) - : transform(datatypes::TranslationRotationScale3D(rotation, from_parent)), - translation(Collection::take_ownership(translation_)) {} + : transform(datatypes::TranslationRotationScale3D(from_parent)), + translation(Collection::take_ownership(translation_)) { + set_rotation(rotation); + } /// From a rotation & scale. /// /// \param translation \copydoc Transform3D::translation - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. static Transform3D from_translation_rotation( - const components::Translation3D& translation, const datatypes::Rotation3D& rotation + const components::Translation3D& translation, const Rotation3D& rotation ) { return Transform3D(translation, rotation, false); } @@ -228,60 +233,59 @@ namespace rerun::archetypes { /// From rotation & scale. /// - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param scale_ Transform3D::scale /// \param from_parent \copydoc datatypes::TranslationRotationScale3D::from_parent Transform3D( - const datatypes::Rotation3D& rotation, const components::Scale3D& scale_, - bool from_parent = false + const Rotation3D& rotation, const components::Scale3D& scale_, bool from_parent = false ) - : transform(datatypes::TranslationRotationScale3D(rotation, from_parent)), - scale(Collection::take_ownership(scale_)) {} + : transform(datatypes::TranslationRotationScale3D(from_parent)), + scale(Collection::take_ownership(scale_)) { + set_rotation(rotation); + } /// From rotation & uniform scale. /// - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param uniform_scale Uniform scale factor that is applied to all axis equally. /// \param from_parent \copydoc datatypes::TranslationRotationScale3D::from_parent /// /// _Implementation note:_ This explicit overload prevents interpretation of the float as /// bool, leading to a call to the wrong overload. - Transform3D( - const datatypes::Rotation3D& rotation, float uniform_scale, bool from_parent = false - ) + Transform3D(const Rotation3D& rotation, float uniform_scale, bool from_parent = false) : Transform3D(rotation, components::Scale3D(uniform_scale), from_parent) {} /// From a rotation & scale. /// - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param scale Transform3D::scale static Transform3D from_rotation_scale( - const datatypes::Rotation3D& rotation, const components::Scale3D& scale + const Rotation3D& rotation, const components::Scale3D& scale ) { return Transform3D(rotation, scale, false); } /// From a rotation & uniform scale. /// - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param uniform_scale Uniform scale factor that is applied to all axis equally. - static Transform3D from_rotation_scale( - const datatypes::Rotation3D& rotation, float uniform_scale - ) { + static Transform3D from_rotation_scale(const Rotation3D& rotation, float uniform_scale) { return Transform3D(rotation, components::Scale3D(uniform_scale), false); } /// From rotation only. /// - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. /// \param from_parent \copydoc datatypes::TranslationRotationScale3D::from_parent - Transform3D(const datatypes::Rotation3D& rotation, bool from_parent = false) - : transform(datatypes::TranslationRotationScale3D(rotation, from_parent)) {} + Transform3D(const Rotation3D& rotation, bool from_parent = false) + : transform(datatypes::TranslationRotationScale3D(from_parent)) { + set_rotation(rotation); + } /// From rotation only. /// - /// \param rotation \copydoc datatypes::TranslationRotationScale3D::rotation - static Transform3D from_rotation(const datatypes::Rotation3D& rotation) { + /// \param rotation Rotation represented either as a quaternion or axis + angle rotation. + static Transform3D from_rotation(const Rotation3D& rotation) { return Transform3D(rotation, false); } @@ -319,6 +323,21 @@ namespace rerun::archetypes { RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } + private: + /// Set the rotation component of the transform using the `rerun::Rotation3D` utility. + void set_rotation(const Rotation3D& rotation) { + if (rotation.axis_angle.has_value()) { + rotation_axis_angle = Collection::take_ownership( + rotation.axis_angle.value() + ); + } + if (rotation.quaternion.has_value()) { + quaternion = Collection::take_ownership( + rotation.quaternion.value() + ); + } + } + // #endif diff --git a/rerun_cpp/src/rerun/components.hpp b/rerun_cpp/src/rerun/components.hpp index a5d523c875ec..9f737aec1add 100644 --- a/rerun_cpp/src/rerun/components.hpp +++ b/rerun_cpp/src/rerun/components.hpp @@ -39,6 +39,8 @@ #include "components/resolution.hpp" #include "components/resolution2d.hpp" #include "components/rotation3d.hpp" +#include "components/rotation_axis_angle.hpp" +#include "components/rotation_quat.hpp" #include "components/scalar.hpp" #include "components/scale3d.hpp" #include "components/stroke_width.hpp" diff --git a/rerun_cpp/src/rerun/components/.gitattributes b/rerun_cpp/src/rerun/components/.gitattributes index a014022f9846..17943469c5ab 100644 --- a/rerun_cpp/src/rerun/components/.gitattributes +++ b/rerun_cpp/src/rerun/components/.gitattributes @@ -47,6 +47,8 @@ range1d.hpp linguist-generated=true resolution.hpp linguist-generated=true resolution2d.hpp linguist-generated=true rotation3d.hpp linguist-generated=true +rotation_axis_angle.hpp linguist-generated=true +rotation_quat.hpp linguist-generated=true scalar.hpp linguist-generated=true scale3d.hpp linguist-generated=true stroke_width.hpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/components/rotation_axis_angle.hpp b/rerun_cpp/src/rerun/components/rotation_axis_angle.hpp new file mode 100644 index 000000000000..09159176dfb0 --- /dev/null +++ b/rerun_cpp/src/rerun/components/rotation_axis_angle.hpp @@ -0,0 +1,59 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/store/re_types/definitions/rerun/components/rotation_axis_angle.fbs". + +#pragma once + +#include "../datatypes/rotation_axis_angle.hpp" +#include "../result.hpp" + +#include +#include + +namespace rerun::components { + /// **Component**: 3D rotation represented by a rotation around a given axis. + struct RotationAxisAngle { + rerun::datatypes::RotationAxisAngle rotation; + + public: + RotationAxisAngle() = default; + + RotationAxisAngle(rerun::datatypes::RotationAxisAngle rotation_) : rotation(rotation_) {} + + RotationAxisAngle& operator=(rerun::datatypes::RotationAxisAngle rotation_) { + rotation = rotation_; + return *this; + } + + /// Cast to the underlying RotationAxisAngle datatype + operator rerun::datatypes::RotationAxisAngle() const { + return rotation; + } + }; +} // namespace rerun::components + +namespace rerun { + static_assert( + sizeof(rerun::datatypes::RotationAxisAngle) == sizeof(components::RotationAxisAngle) + ); + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.RotationAxisAngle"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype() { + return Loggable::arrow_datatype(); + } + + /// Serializes an array of `rerun::components::RotationAxisAngle` into an arrow array. + static Result> to_arrow( + const components::RotationAxisAngle* instances, size_t num_instances + ) { + return Loggable::to_arrow( + &instances->rotation, + num_instances + ); + } + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components/rotation_quat.hpp b/rerun_cpp/src/rerun/components/rotation_quat.hpp new file mode 100644 index 000000000000..a34d7dc62429 --- /dev/null +++ b/rerun_cpp/src/rerun/components/rotation_quat.hpp @@ -0,0 +1,60 @@ +// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/store/re_types/definitions/rerun/components/rotation_quat.fbs". + +#pragma once + +#include "../datatypes/quaternion.hpp" +#include "../result.hpp" + +#include +#include + +namespace rerun::components { + /// **Component**: A 3D rotation expressed as a quaternion. + /// + /// Note: although the x,y,z,w components of the quaternion will be passed through to the + /// datastore as provided, when used in the Viewer Quaternions will always be normalized. + struct RotationQuat { + rerun::datatypes::Quaternion quaternion; + + public: + RotationQuat() = default; + + RotationQuat(rerun::datatypes::Quaternion quaternion_) : quaternion(quaternion_) {} + + RotationQuat& operator=(rerun::datatypes::Quaternion quaternion_) { + quaternion = quaternion_; + return *this; + } + + /// Cast to the underlying Quaternion datatype + operator rerun::datatypes::Quaternion() const { + return quaternion; + } + }; +} // namespace rerun::components + +namespace rerun { + static_assert(sizeof(rerun::datatypes::Quaternion) == sizeof(components::RotationQuat)); + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.RotationQuat"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype() { + return Loggable::arrow_datatype(); + } + + /// Serializes an array of `rerun::components::RotationQuat` into an arrow array. + static Result> to_arrow( + const components::RotationQuat* instances, size_t num_instances + ) { + return Loggable::to_arrow( + &instances->quaternion, + num_instances + ); + } + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/rotation3d.hpp b/rerun_cpp/src/rerun/rotation3d.hpp new file mode 100644 index 000000000000..9b57d194cf47 --- /dev/null +++ b/rerun_cpp/src/rerun/rotation3d.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "components/rotation_axis_angle.hpp" +#include "components/rotation_quat.hpp" +#include "datatypes/quaternion.hpp" +#include "datatypes/rotation_axis_angle.hpp" + +namespace rerun { + /// Utility for representing a single 3D rotation, agnostic to the underlying representation. + /// + /// This is not a component, but a utility for building `rerun::Transform3D`. + struct Rotation3D { + std::optional axis_angle; + std::optional quaternion; + + public: + Rotation3D() : axis_angle(std::nullopt), quaternion(std::nullopt) {} + + /// Construct a `Rotation3D` from a rotation axis and angle component. + Rotation3D(rerun::components::RotationAxisAngle axis_angle_) : axis_angle(axis_angle_) {} + + /// Construct a `Rotation3D` from a quaternion component. + Rotation3D(rerun::components::RotationQuat quaternion_) : quaternion(quaternion_) {} + + /// Construct a `Rotation3D` from a rotation axis and angle datatype. + Rotation3D(rerun::datatypes::RotationAxisAngle axis_angle_) : axis_angle(axis_angle_) {} + + /// Construct a `Rotation3D` from a quaternion datatype. + Rotation3D(rerun::datatypes::Quaternion quaternion_) : quaternion(quaternion_) {} + }; +} // namespace rerun diff --git a/rerun_cpp/tests/archetypes/transform3d.cpp b/rerun_cpp/tests/archetypes/transform3d.cpp index d271a09df333..c4f007580737 100644 --- a/rerun_cpp/tests/archetypes/transform3d.cpp +++ b/rerun_cpp/tests/archetypes/transform3d.cpp @@ -28,7 +28,8 @@ SCENARIO( } \ } rrd::Vec3D columns[3] = MATRIX_ILIST; - const auto rotation = rrd::Quaternion::from_xyzw(1.0f, 2.0f, 3.0f, 4.0f); + const auto quaternion = rrd::Quaternion::from_xyzw(1.0f, 2.0f, 3.0f, 4.0f); + const auto axis_angle = rrd::RotationAxisAngle({1.0f, 2.0f, 3.0f}, rrd::Angle::degrees(90.0f)); Transform3D manual; // List out everything so that GCC doesn't get nervous around uninitialized values. @@ -110,34 +111,73 @@ SCENARIO( test_compare_archetype_serialization(manual, utility); } - GIVEN("Transform3D from translation & rotation & scale and from_parent==" << from_parent) { + GIVEN( + "Transform3D from translation & rotation (quaternion) & scale and from_parent==" + << from_parent + ) { auto utility = Transform3D::from_translation_rotation_scale( {1.0f, 2.0f, 3.0f}, - rotation, + quaternion, {3.0f, 2.0f, 1.0f} ) .with_from_parent(from_parent); manual.translation = rerun::components::Translation3D(1.0f, 2.0f, 3.0f); - manual_translation_rotation_scale.rotation = rotation; + manual.quaternion = quaternion; manual.scale = rerun::components::Scale3D(3.0f, 2.0f, 1.0f); - manual.transform.repr = - rrd::Transform3D::translation_rotation_scale(manual_translation_rotation_scale); test_compare_archetype_serialization(manual, utility); } - GIVEN("Transform3D from rotation & scale and from_parent==" << from_parent) { - auto utility = Transform3D::from_rotation_scale(rotation, {3.0f, 2.0f, 1.0f}) + GIVEN( + "Transform3D from translation & rotation (axis angle) & scale and from_parent==" + << from_parent + ) { + auto utility = Transform3D::from_translation_rotation_scale( + {1.0f, 2.0f, 3.0f}, + axis_angle, + {3.0f, 2.0f, 1.0f} + ) .with_from_parent(from_parent); - manual_translation_rotation_scale.rotation = rotation; + manual.translation = rerun::components::Translation3D(1.0f, 2.0f, 3.0f); + manual.rotation_axis_angle = axis_angle; manual.scale = rerun::components::Scale3D(3.0f, 2.0f, 1.0f); - manual.transform.repr = - rrd::Transform3D::translation_rotation_scale(manual_translation_rotation_scale); test_compare_archetype_serialization(manual, utility); } + + GIVEN("Transform3D from rotation (quaternion) & scale and from_parent==" << from_parent) { + auto utility = Transform3D::from_rotation_scale(quaternion, {3.0f, 2.0f, 1.0f}) + .with_from_parent(from_parent); + + manual.quaternion = quaternion; + manual.scale = rerun::components::Scale3D(3.0f, 2.0f, 1.0f); + + test_compare_archetype_serialization(manual, utility); + } + + GIVEN("Transform3D from rotation (axis angle) & scale and from_parent==" << from_parent) { + auto utility = Transform3D::from_rotation_scale(axis_angle, {3.0f, 2.0f, 1.0f}) + .with_from_parent(from_parent); + + manual.rotation_axis_angle = axis_angle; + manual.scale = rerun::components::Scale3D(3.0f, 2.0f, 1.0f); + + test_compare_archetype_serialization(manual, utility); + } + + GIVEN("Transform3D from rotation (quaternion) and from_parent==" << from_parent) { + auto utility = Transform3D::from_rotation(quaternion).with_from_parent(from_parent); + manual.quaternion = quaternion; + test_compare_archetype_serialization(manual, utility); + } + + GIVEN("Transform3D from rotation (axis angle) and from_parent==" << from_parent) { + auto utility = Transform3D::from_rotation(axis_angle).with_from_parent(from_parent); + manual.rotation_axis_angle = axis_angle; + test_compare_archetype_serialization(manual, utility); + } } RR_DISABLE_MAYBE_UNINITIALIZED_POP diff --git a/rerun_py/rerun_sdk/rerun/__init__.py b/rerun_py/rerun_sdk/rerun/__init__.py index d5ccc0c492b6..9203380deb18 100644 --- a/rerun_py/rerun_sdk/rerun/__init__.py +++ b/rerun_py/rerun_sdk/rerun/__init__.py @@ -84,6 +84,7 @@ TextLogLevel as TextLogLevel, ) from .datatypes import ( + Angle as Angle, AnnotationInfo as AnnotationInfo, ClassDescription as ClassDescription, Quaternion as Quaternion, diff --git a/rerun_py/rerun_sdk/rerun/archetypes/transform3d.py b/rerun_py/rerun_sdk/rerun/archetypes/transform3d.py index 26c531ad9a6c..fac09389e1c6 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/transform3d.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/transform3d.py @@ -139,6 +139,8 @@ def __attrs_clear__(self) -> None: self.__attrs_init__( transform=None, # type: ignore[arg-type] translation=None, # type: ignore[arg-type] + rotation_axis_angle=None, # type: ignore[arg-type] + quaternion=None, # type: ignore[arg-type] scale=None, # type: ignore[arg-type] mat3x3=None, # type: ignore[arg-type] axis_length=None, # type: ignore[arg-type] @@ -168,6 +170,24 @@ def _clear(cls) -> Transform3D: # # (Docstring intentionally commented out to hide this field from the docs) + rotation_axis_angle: components.RotationAxisAngleBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.RotationAxisAngleBatch._optional, # type: ignore[misc] + ) + # Rotation via axis + angle. + # + # (Docstring intentionally commented out to hide this field from the docs) + + quaternion: components.RotationQuatBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.RotationQuatBatch._optional, # type: ignore[misc] + ) + # Rotation via quaternion. + # + # (Docstring intentionally commented out to hide this field from the docs) + scale: components.Scale3DBatch | None = field( metadata={"component": "optional"}, default=None, diff --git a/rerun_py/rerun_sdk/rerun/archetypes/transform3d_ext.py b/rerun_py/rerun_sdk/rerun/archetypes/transform3d_ext.py index 9c3febf1c035..ade6827998b7 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/transform3d_ext.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/transform3d_ext.py @@ -6,7 +6,10 @@ from rerun.datatypes import ( Float32Like, Mat3x3ArrayLike, - Rotation3DLike, + Quaternion, + QuaternionArrayLike, + RotationAxisAngle, + RotationAxisAngleArrayLike, TranslationRotationScale3D, Vec3DArrayLike, ) @@ -22,7 +25,9 @@ def __init__( self: Any, *, translation: Vec3DArrayLike | None = None, - rotation: Rotation3DLike | None = None, # TODO(#6831): Should allow arrays. + rotation: QuaternionArrayLike | RotationAxisAngleArrayLike | None = None, + rotation_axis_angle: RotationAxisAngleArrayLike | None = None, + quaternion: QuaternionArrayLike | None = None, scale: Vec3DArrayLike | Float32Like | None = None, mat3x3: Mat3x3ArrayLike | None = None, from_parent: bool | None = None, @@ -36,7 +41,14 @@ def __init__( translation: 3D translation vector. rotation: - 3D rotation. + 3D rotation, either a quaternion or an axis-angle. + Mutually exclusive with `quaternion` and `rotation_axis_angle`. + rotation_axis_angle: + Axis-angle representing rotation. + Mutually exclusive with `rotation` parameter. + quaternion: + Quaternion representing rotation. + Mutually exclusive with `rotation` parameter. scale: 3D scale. mat3x3: @@ -58,18 +70,47 @@ def __init__( if from_parent is None: from_parent = False + if rotation is not None: + if quaternion is not None or rotation_axis_angle is not None: + raise ValueError( + "`rotation` parameter can't be combined with `quaternion` or `rotation_axis_angle`." + ) + + is_rotation_axis_angle = False + try: + if isinstance(rotation, RotationAxisAngle): + is_rotation_axis_angle = True + elif isinstance(rotation[0], RotationAxisAngle): # type: ignore[index] + is_rotation_axis_angle = True + except Exception: # Failed to subscript rotation. + pass + + if is_rotation_axis_angle: + rotation_axis_angle = rotation # type: ignore[assignment] + else: + try: + is_quaternion = False + if isinstance(rotation, Quaternion): + is_quaternion = True + elif isinstance(rotation[0], Quaternion): # type: ignore[index] + is_quaternion = True + except Exception: # Failed to subscript quaternion. + pass + if not is_quaternion: + raise ValueError("Rotation must be compatible with either RotationQuat or RotationAxisAngle") + quaternion = rotation # type: ignore[assignment] + if scale is not None and (not hasattr(scale, "__len__") or len(scale) == 1): # type: ignore[arg-type] scale = Scale3D(scale) # type: ignore[arg-type] self.__attrs_init__( # TODO(#6831): Remove. - transform=TranslationRotationScale3D( - rotation=rotation, - from_parent=from_parent, - ), - mat3x3=mat3x3, - scale=scale, + transform=TranslationRotationScale3D(from_parent=from_parent), translation=translation, + rotation_axis_angle=rotation_axis_angle, + quaternion=quaternion, + scale=scale, + mat3x3=mat3x3, axis_length=axis_length, ) return diff --git a/rerun_py/rerun_sdk/rerun/components/.gitattributes b/rerun_py/rerun_sdk/rerun/components/.gitattributes index 84e018387160..079dc135fdb4 100644 --- a/rerun_py/rerun_sdk/rerun/components/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/components/.gitattributes @@ -39,6 +39,8 @@ range1d.py linguist-generated=true resolution.py linguist-generated=true resolution2d.py linguist-generated=true rotation3d.py linguist-generated=true +rotation_axis_angle.py linguist-generated=true +rotation_quat.py linguist-generated=true scalar.py linguist-generated=true scale3d.py linguist-generated=true stroke_width.py linguist-generated=true diff --git a/rerun_py/rerun_sdk/rerun/components/__init__.py b/rerun_py/rerun_sdk/rerun/components/__init__.py index 0a7d0888d25c..26a2f9f98f57 100644 --- a/rerun_py/rerun_sdk/rerun/components/__init__.py +++ b/rerun_py/rerun_sdk/rerun/components/__init__.py @@ -63,6 +63,8 @@ from .resolution import Resolution, ResolutionBatch, ResolutionType from .resolution2d import Resolution2D, Resolution2DBatch, Resolution2DType from .rotation3d import Rotation3D, Rotation3DBatch, Rotation3DType +from .rotation_axis_angle import RotationAxisAngle, RotationAxisAngleBatch, RotationAxisAngleType +from .rotation_quat import RotationQuat, RotationQuatBatch, RotationQuatType from .scalar import Scalar, ScalarBatch, ScalarType from .scale3d import Scale3D, Scale3DBatch, Scale3DType from .stroke_width import StrokeWidth, StrokeWidthBatch, StrokeWidthType @@ -215,6 +217,12 @@ "Rotation3D", "Rotation3DBatch", "Rotation3DType", + "RotationAxisAngle", + "RotationAxisAngleBatch", + "RotationAxisAngleType", + "RotationQuat", + "RotationQuatBatch", + "RotationQuatType", "Scalar", "ScalarBatch", "ScalarType", diff --git a/rerun_py/rerun_sdk/rerun/components/rotation_axis_angle.py b/rerun_py/rerun_sdk/rerun/components/rotation_axis_angle.py new file mode 100644 index 000000000000..fd0723a42278 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/rotation_axis_angle.py @@ -0,0 +1,36 @@ +# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs +# Based on "crates/store/re_types/definitions/rerun/components/rotation_axis_angle.fbs". + +# You can extend this class by creating a "RotationAxisAngleExt" class in "rotation_axis_angle_ext.py". + +from __future__ import annotations + +from .. import datatypes +from .._baseclasses import ( + ComponentBatchMixin, + ComponentMixin, +) + +__all__ = ["RotationAxisAngle", "RotationAxisAngleBatch", "RotationAxisAngleType"] + + +class RotationAxisAngle(datatypes.RotationAxisAngle, ComponentMixin): + """**Component**: 3D rotation represented by a rotation around a given axis.""" + + _BATCH_TYPE = None + # You can define your own __init__ function as a member of RotationAxisAngleExt in rotation_axis_angle_ext.py + + # Note: there are no fields here because RotationAxisAngle delegates to datatypes.RotationAxisAngle + pass + + +class RotationAxisAngleType(datatypes.RotationAxisAngleType): + _TYPE_NAME: str = "rerun.components.RotationAxisAngle" + + +class RotationAxisAngleBatch(datatypes.RotationAxisAngleBatch, ComponentBatchMixin): + _ARROW_TYPE = RotationAxisAngleType() + + +# This is patched in late to avoid circular dependencies. +RotationAxisAngle._BATCH_TYPE = RotationAxisAngleBatch # type: ignore[assignment] diff --git a/rerun_py/rerun_sdk/rerun/components/rotation_quat.py b/rerun_py/rerun_sdk/rerun/components/rotation_quat.py new file mode 100644 index 000000000000..b042f6000e77 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/rotation_quat.py @@ -0,0 +1,41 @@ +# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs +# Based on "crates/store/re_types/definitions/rerun/components/rotation_quat.fbs". + +# You can extend this class by creating a "RotationQuatExt" class in "rotation_quat_ext.py". + +from __future__ import annotations + +from .. import datatypes +from .._baseclasses import ( + ComponentBatchMixin, + ComponentMixin, +) + +__all__ = ["RotationQuat", "RotationQuatBatch", "RotationQuatType"] + + +class RotationQuat(datatypes.Quaternion, ComponentMixin): + """ + **Component**: A 3D rotation expressed as a quaternion. + + Note: although the x,y,z,w components of the quaternion will be passed through to the + datastore as provided, when used in the Viewer Quaternions will always be normalized. + """ + + _BATCH_TYPE = None + # You can define your own __init__ function as a member of RotationQuatExt in rotation_quat_ext.py + + # Note: there are no fields here because RotationQuat delegates to datatypes.Quaternion + pass + + +class RotationQuatType(datatypes.QuaternionType): + _TYPE_NAME: str = "rerun.components.RotationQuat" + + +class RotationQuatBatch(datatypes.QuaternionBatch, ComponentBatchMixin): + _ARROW_TYPE = RotationQuatType() + + +# This is patched in late to avoid circular dependencies. +RotationQuat._BATCH_TYPE = RotationQuatBatch # type: ignore[assignment] diff --git a/rerun_py/tests/unit/test_expected_warnings.py b/rerun_py/tests/unit/test_expected_warnings.py index ea5fa7991758..94a0bd8523e5 100644 --- a/rerun_py/tests/unit/test_expected_warnings.py +++ b/rerun_py/tests/unit/test_expected_warnings.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Callable + import pytest import rerun as rr from rerun.error_utils import RerunWarning @@ -9,42 +11,42 @@ mem = rr.memory_recording() +def expect_warning(call: Callable[..., None], expected_warning: str) -> None: + with pytest.warns(RerunWarning) as warnings: + call() + print("Logged warnings:") + for warning in warnings: + print(warning) + assert len(warnings) == 1 + assert expected_warning in str(warnings[0]) + + def test_expected_warnings() -> None: # Always set strict mode to false in case it leaked from another test rr.set_strict_mode(False) - with pytest.warns(RerunWarning) as warnings: - # Each of these calls will fail as they are executed and aggregate warnings into the single warnings recording. - # We then check that all the warnings were emitted in the expected order. - # If a log-call is expected to produce multiple warnings, add another tuple to the list that doesn't produce a warning, - # or else the offsets will get out of alignment. - expected_warnings = [ - ( - rr.log("points", rr.Points3D([1, 2, 3, 4, 5])), - "Expected either a flat array with a length multiple of 3 elements, or an array with shape (`num_elements`, 3). Shape of passed array was (5,).", - ), - ( - rr.log("points", rr.Points2D([1, 2, 3, 4, 5])), - "Expected either a flat array with a length multiple of 2 elements, or an array with shape (`num_elements`, 2). Shape of passed array was (5,).", - ), - ( - rr.log("test_transform", rr.Transform3D(rotation=[1, 2, 3, 4, 5])), - "rotation must be compatible with Rotation3D", - ), - ( - # TODO(jleibs): This should ideally capture the field name as mat3x3 as above - rr.log("test_transform", rr.Transform3D(mat3x3=[1, 2, 3, 4, 5])), - "cannot reshape array of size 5 into shape (3,3))", - ), - ( - rr.log("test_transform", rr.datatypes.Vec3D([1, 0, 0])), # type: ignore[arg-type] - "Expected an object implementing rerun.AsComponents or an iterable of rerun.ComponentBatchLike, but got", - ), - ( - rr.log("world/image", rr.Pinhole(focal_length=3)), - "Must provide one of principal_point, resolution, or width/height)", - ), - ] - - assert len(warnings) == len(expected_warnings) - for warning, (_, expected) in zip(warnings, expected_warnings): - assert expected in str(warning) + + expect_warning( + lambda: rr.log("points", rr.Points3D([1, 2, 3, 4, 5])), + "Expected either a flat array with a length multiple of 3 elements, or an array with shape (`num_elements`, 3). Shape of passed array was (5,).", + ) + expect_warning( + lambda: rr.log("points", rr.Points2D([1, 2, 3, 4, 5])), + "Expected either a flat array with a length multiple of 2 elements, or an array with shape (`num_elements`, 2). Shape of passed array was (5,).", + ) + expect_warning( + lambda: rr.log("test_transform", rr.Transform3D(rotation=[1, 2, 3, 4, 5])), # type: ignore[arg-type] + "Rotation must be compatible with either RotationQuat or RotationAxisAngle", + ) + expect_warning( + # TODO(jleibs): This should ideally capture the field name as mat3x3 as above + lambda: rr.log("test_transform", rr.Transform3D(mat3x3=[1, 2, 3, 4, 5])), + "cannot reshape array of size 5 into shape (3,3))", + ) + expect_warning( + lambda: rr.log("test_transform", rr.datatypes.Vec3D([1, 0, 0])), # type: ignore[arg-type] + "Expected an object implementing rerun.AsComponents or an iterable of rerun.ComponentBatchLike, but got", + ) + expect_warning( + lambda: rr.log("world/image", rr.Pinhole(focal_length=3)), + "Must provide one of principal_point, resolution, or width/height)", + ) diff --git a/rerun_py/tests/unit/test_transform3d.py b/rerun_py/tests/unit/test_transform3d.py index 29a96d38e872..1e200052c52a 100644 --- a/rerun_py/tests/unit/test_transform3d.py +++ b/rerun_py/tests/unit/test_transform3d.py @@ -95,58 +95,75 @@ def test_angle() -> None: def test_transform3d() -> None: # TODO(#6831): Test with arrays of all fields. + rotation_axis_angle = [None, RotationAxisAngle([1, 2, 3], rr.Angle(deg=10))] + quaternion_arrays = [None, Quaternion(xyzw=[1, 2, 3, 4])] + scale_arrays = [None, 1.0, 1, [1.0, 2.0, 3.0]] axis_lengths = [None, 1, 1.0] from_parent_arrays = [None, True, False] - scale_arrays = [None, 1.0, 1, [1.0, 2.0, 3.0]] # TODO(#6831): repopulate this list with all transform variants all_arrays = itertools.zip_longest( - MAT_3X3_INPUT + [None], - scale_arrays, VEC_3D_INPUT + [None], + rotation_axis_angle, + quaternion_arrays, + scale_arrays, + MAT_3X3_INPUT + [None], from_parent_arrays, axis_lengths, ) for ( - mat3x3, - scale, translation, + rotation_axis_angle, + quaternion, + scale, + mat3x3, from_parent, axis_length, ) in all_arrays: - mat3x3 = cast(Optional[rr.datatypes.Mat3x3Like], mat3x3) - scale = cast(Optional[rr.datatypes.Vec3DLike | rr.datatypes.Float32Like], scale) translation = cast(Optional[rr.datatypes.Vec3DLike], translation) + quaternion = cast(Optional[rr.datatypes.QuaternionLike], quaternion) + scale = cast(Optional[rr.datatypes.Vec3DLike | rr.datatypes.Float32Like], scale) + mat3x3 = cast(Optional[rr.datatypes.Mat3x3Like], mat3x3) from_parent = cast(Optional[bool], from_parent) axis_length = cast(Optional[rr.datatypes.Float32Like], axis_length) print( f"rr.Transform3D(\n" - f" mat3x3={mat3x3!r}\n" # f" translation={translation!r}\n" # + f" rotation_axis_angle={rotation_axis_angle!r}\n" # + f" quaternion={quaternion!r}\n" # f" scale={scale!r}\n" # + f" mat3x3={mat3x3!r}\n" # f" from_parent={from_parent!r}\n" # f" axis_length={axis_length!r}\n" # f")" ) arch = rr.Transform3D( - mat3x3=mat3x3, translation=translation, + rotation_axis_angle=rotation_axis_angle, # type: ignore[assignment, arg-type] # prior cast didn't work here + quaternion=quaternion, scale=scale, + mat3x3=mat3x3, from_parent=from_parent, axis_length=axis_length, ) print(f"{arch}\n") - assert arch.mat3x3 == rr.components.TransformMat3x3Batch._optional( - none_empty_or_value(mat3x3, rr.components.TransformMat3x3([[1, 2, 3], [4, 5, 6], [7, 8, 9]])) + assert arch.scale == rr.components.Scale3DBatch._optional( + none_empty_or_value(scale, rr.components.Scale3D(scale)) # type: ignore[arg-type] + ) + assert arch.rotation_axis_angle == rr.components.RotationAxisAngleBatch._optional( + none_empty_or_value(rotation_axis_angle, rr.components.RotationAxisAngle([1, 2, 3], Angle(deg=10))) + ) + assert arch.quaternion == rr.components.RotationQuatBatch._optional( + none_empty_or_value(quaternion, rr.components.RotationQuat(xyzw=[1, 2, 3, 4])) ) assert arch.translation == rr.components.Translation3DBatch._optional( none_empty_or_value(translation, rr.components.Translation3D([1, 2, 3])) ) - assert arch.scale == rr.components.Scale3DBatch._optional( - none_empty_or_value(scale, rr.components.Scale3D(scale)) # type: ignore[arg-type] + assert arch.mat3x3 == rr.components.TransformMat3x3Batch._optional( + none_empty_or_value(mat3x3, rr.components.TransformMat3x3([[1, 2, 3], [4, 5, 6], [7, 8, 9]])) ) assert arch.axis_length == rr.components.AxisLengthBatch._optional( none_empty_or_value(axis_length, rr.components.AxisLength(1.0)) @@ -172,3 +189,12 @@ def test_transform_mat3x3_snippets() -> None: rr.components.TransformMat3x3(columns=[[1, 2, 3], [4, 5, 6], [7, 8, 9]]).flat_columns, np.array([1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=np.float32), ) + + +def test_transform3d_rotation() -> None: + assert rr.Transform3D(rotation=RotationAxisAngle([1, 2, 3], rr.Angle(deg=10))) == rr.Transform3D( + rotation_axis_angle=RotationAxisAngle([1, 2, 3], rr.Angle(deg=10)) + ) + assert rr.Transform3D(rotation=Quaternion(xyzw=[1, 2, 3, 4])) == rr.Transform3D( + quaternion=Quaternion(xyzw=[1, 2, 3, 4]) + )