Rerun Quaternion 编码类型全解析:四元数在 3D 旋转数据中的表示、序列化与实战用法
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
本文围绕 Rerun 数据模型中的
Quaternion编码类型展开,说明它如何在 Rerun 中表示 3D 旋转、其 Arrow 内存布局(FixedSizeList(4 x non-null Float32))、跨 Python / Rust / C++ 三种 SDK 的构造与使用方式,并结合RotationQuat组件、Rotation3D辅助类型与Transform3D、Boxes3D等 Archetype 的源码实现,给出可直接运行的实战示例。
Quaternion是 Rerun 数据模型中的一种核心编码类型(Encoding),用于以 4 个浮点数紧凑地表达 3D 旋转。它本身不是可以直接记录到数据存储中的组件,而是作为RotationQuat组件的底层表示存在,广泛服务于Transform3D、Boxes3D、Capsules3D、Ellipsoids3D、GaussianSplats3D、InstancePoses3D、Volume3D、VoxelGridMap等 Archetype 的旋转表达。本文将以 Rerun 仓库(crates、rerun_py、rerun_cpp 等目录)中的类型定义与序列化实现为依据,完整解析该类型的设计与用法。
类型定义:一个由 4 个实数表示的四元数
Rerun 对Quaternion的权威定义位于类型定义文件,它是一份被re_types_builder解析、用于生成 Rust / Python / C++ 三种语言绑定的"类型说明书":
/// A Quaternion represented by 4 real numbers. /// /// 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. #[rerun::rerun_type] #[arrow(transparent)] #[cpp(no_field_ctors)] #[python(array_aliases = "npt.NDArray[Any] | npt.ArrayLike | Sequence[Sequence[float]]")] #[rust(derive(Copy, PartialEq, PartialOrd, bytemuck::Pod, bytemuck::Zeroable))] #[rust(repr = "C")] #[rust(tuple_struct)] #[rerun(state = "stable")] pub struct Quaternion { pub xyzw: [f32; 4], }这段定义透露了几个关键信息:
- 字段顺序约定为 XYZW:四元数的四个分量按
x, y, z, w顺序存储,其中w是标量(实部)。 #[arrow(transparent)]:该类型在 Arrow 序列化时采用"透明"表示——即直接暴露其内部字段[f32; 4],没有额外的嵌套包装层。- 状态为
stable:该类型处于稳定状态,属于公开的稳定 API 面。 - 派生
Copy、PartialOrd、bytemuck::Pod/Zeroable等 trait,说明它在 Rust 中是一个可零拷贝、可字节级转换的轻量 POD 结构。
由该定义自动生成的 Rust 结构体位于 crates/store/re_sdk_types/src/encodings/quaternion.rs:
#[repr(C)] pub struct Quaternion(pub [f32; 4usize]);可以看到生成代码以repr(C)的元组结构体形式保存一个长度为 4 的f32数组,正是"4 个实数"这一语义的最直接体现。
归一化语义:原样入库,Viewer 中归一化
原文档中特别强调了一条重要行为约定,这也是使用四元数时最容易踩的坑:
尽管
x,y,z,w四个分量会原样写入数据存储(datastore),但在 Viewer 中使用时,四元数总是会被归一化。
这意味着:
- 存储层不强制归一化:你记录什么值,数据存储中就保存什么值,不做任何预处理或校验。
- 可视化层归一化:Viewer 在渲染、计算变换时会先把四元数归一化到单位长度再使用,因此未归一化的输入在可视化时会被"纠正"。
- 归一化失败的后果:如果四元数的模长为 0(例如
[0, 0, 0, 0]),无法归一化,此时该旋转会被视为无效变换。这一点在RotationQuat组件的文档(docs/content/reference/types/components/rotation_quat.md)中有着一致的表述:"If normalization fails the rotation is treated as an invalid transform."
从源码看,Viewer 侧确实通过归一化路径消费四元数。例如在 crates/store/re_sdk_types/src/encodings/quaternion_ext.rs 中,Quaternion向glam::Quat的转换就显式调用了try_normalize(),归一化失败(如零向量)时返回错误并拒绝构造:
#[cfg(feature = "glam")] impl TryFrom<Quaternion> for glam::Quat { type Error = (); fn try_from(q: Quaternion) -> Result<Self, ()> { glam::Vec4::from(q.0) .try_normalize() .map(Self::from_vec4) .ok_or(()) } }这从实现层面印证了文档中"Viewer 中总会归一化、归一化失败视为无效"的约定。
Arrow 数据表示:FixedSizeList(4 x non-null Float32)
原文档给出了该编码类型的 Arrow 数据类型:
FixedSizeList(4 x non-null Float32)其序列化实现可在生成代码 crates/store/re_sdk_types/src/encodings/quaternion.rs 中看到:
impl ::re_types_core::ArrowDataType for Quaternion { fn arrow_data_type() -> arrow::datatypes::DataType { use arrow::datatypes::*; DataType::FixedSizeList( std::sync::Arc::new(Field::new("item", DataType::Float32, false)), 4, ) } }要点解读:
- 外层是一个定长列表(FixedSizeList),长度为 4,元素类型为
Float32,元素字段不可为 null(false)。 - 定长列表的优点是内存紧凑:4 个
f32连续排布,无需额外的 offsets 数组,适合表示在数量上"永远恰好是 4 个"的向量/四元数。 - 反序列化时,代码会先校验
value_length() == 4,不匹配则抛出datatype_mismatch错误(见 quaternion.rs),随后将内部 Float32 缓冲区直接按bytemuck::try_cast_slice零拷贝转换为[f32; 4]切片。
Python 侧同样采用 pyarrow 的FixedSizeListArray生成该表示。在 rerun_py/rerun_sdk/rerun/encodings/quaternion_ext.py 中:
@staticmethod def native_to_pa_array_override(data: QuaternionArrayLike, data_type: pa.DataType) -> pa.Array: quaternions = flat_np_float32_array_from_array_like(data, 4) return pa.FixedSizeListArray.from_arrays(quaternions, type=data_type)输入先被规整为形状为(N, 4)的float32ndarray,再封装成定长列表数组——这也解释了 Python 类型别名中npt.NDArray[Any] | npt.ArrayLike | Sequence[Sequence[float]]的由来:批量的四元数本质就是一个(N, 4)的数组。
核心常量与构造方法:XYZW 与 WXYZ 的约定
原文档正文只说明了"4 个实数"与归一化语义,而实际使用中还需掌握构造与分量顺序约定。Rerun 在手写扩展层 crates/store/re_sdk_types/src/encodings/quaternion_ext.rs 中提供了以下内容:
| 成员 | 含义 | 值 / 说明 |
|---|---|---|
IDENTITY | 单位四元数(无旋转) | [0.0, 0.0, 0.0, 1.0] |
INVALID | 无效变换四元数 | [0.0, 0.0, 0.0, 0.0] |
from_xyzw([f32; 4]) | 按 x,y,z,w 顺序构造 | 内部存储顺序即输入顺序 |
from_wxyz([f32; 4]) | 按 w,x,y,z 顺序构造 | 自动重排为[x, y, z, w] |
xyzw() -> [f32; 4] | 读取四个分量 | 返回 x,y,z,w 顺序 |
Default实现直接返回IDENTITY,即"默认四元数 = 无旋转":
impl Default for Quaternion { fn default() -> Self { Self::IDENTITY } }顺序约定提醒:数学社区与许多数学库(如 Eigen、部分 ROS 资料)习惯使用w, x, y, z(WXYZ)顺序,而 Rerun 内部统一使用x, y, z, w(XYZW)顺序。为此 Rerun 在 Rust 与 C++ 中都提供了from_wxyz便捷构造,避免手工重排出错。C++ 端实现见 rerun_cpp/src/rerun/encodings/quaternion.hpp,同时提供了x()/y()/z()/w()分量访问器与从float*指针构造的版本。
三种语言 SDK 的实战用法
Python:rr.Quaternion
Python 侧的扩展实现在 rerun_py/rerun_sdk/rerun/encodings/quaternion_ext.py,提供关键字构造、identity()与invalid()工厂方法:
import rerun as rr rr.init("rerun_example_quaternion", spawn=True) # 关键字构造,按 XYZW 顺序 q = rr.Quaternion(xyzw=[0.0, 0.0, 0.382683, 0.923880]) # 绕 Z 轴 45° # 单位四元数 identity = rr.Quaternion.identity() # 无效四元数(归一化失败时表示无效变换) invalid = rr.Quaternion.invalid()Rust:rerun::Quaternion
Rust 侧直接使用生成的rerun::Quaternion,配合常量与构造方法。完整可运行示例见 docs/snippets/all/archetypes/boxes3d_batch.rs:
let rec = rerun::RecordingStreamBuilder::new("rerun_example_box3d_batch").spawn()?; rec.log( "batch", &rerun::Boxes3D::from_centers_and_half_sizes( [(2.0, 0.0, 0.0), (-2.0, 0.0, 0.0), (0.0, 0.0, 2.0)], [(2.0, 2.0, 1.0), (1.0, 1.0, 0.5), (2.0, 0.5, 1.0)], ) .with_quaternions([ rerun::Quaternion::IDENTITY, rerun::Quaternion::from_xyzw([0.0, 0.0, 0.382683, 0.923880]), // 45 degrees around Z ]), )?;注意 Rust 批量 API(with_quaternions)接收一个四元数数组,其长度需要与centers/half_sizes的数量对齐。
C++:rerun::encodings::Quaternion
C++ 头文件 rerun_cpp/src/rerun/encodings/quaternion.hpp 提供同名结构体与丰富构造器:
#include <rerun.hpp> auto q = rerun::encodings::Quaternion::from_xyzw(0.0f, 0.0f, 0.382683f, 0.923880f); auto q2 = rerun::encodings::Quaternion::from_wxyz(0.923880f, 0.0f, 0.0f, 0.382683f); // 等价 auto identity = rerun::encodings::Quaternion::IDENTITY;from_wxyz的重载同时支持四个标量、std::array<float, 4>与const float*指针三种入参形式,便于与既有数学库互操作。
四元数如何进入 Rerun 数据模型:从 Encoding 到 Component 到 Archetype
原文档末尾给出了Quaternion的唯一直接消费者:RotationQuat组件(docs/content/reference/types/components/rotation_quat.md)。其完整的引用链如下:
1. Encoding → Component
Rust 生成代码中,RotationQuat是一个透明的包装组件,内部持有encodings::Quaternion:
#[repr(transparent)] pub struct RotationQuat(pub crate::encodings::Quaternion); impl ::re_types_core::WrapperComponent for RotationQuat { type Encoding = crate::encodings::Quaternion; fn name() -> ComponentType { "rerun.components.RotationQuat".into() } fn into_inner(self) -> Self::Encoding { self.0 } }它实现了Deref/DerefMut到Quaternion,因此组件可直接复用编码类型的所有方法与常量。
2. Component → Rotation3D 辅助类型
在 crates/store/re_sdk_types/src/rotation3d.rs 中定义了一个非组件的辅助枚举Rotation3D,用于填充Transform3D:
pub enum Rotation3D { Quaternion(components::RotationQuat), // 四元数表达 AxisAngle(components::RotationAxisAngle), // 轴角表达 }它提供从components::RotationQuat、encodings::Quaternion以及(启用glamfeature 时)glam::Quat的From转换,Rotation3D::IDENTITY也定义为以四元数形式表示的单位旋转。
3. Rotation3D → Transform3D Archetype
transform3d_ext.rs 中的with_rotation方法接收任意实现了Into<Rotation3D>的类型,四元数与轴角可以无缝混用:
pub fn with_rotation(self, rotation: impl Into<Rotation3D>) -> Self { match rotation.into() { Rotation3D::Quaternion(quaternion) => self.with_quaternion(quaternion), Rotation3D::AxisAngle(rotation_axis_angle) => self.with_rotation_axis_angle(rotation_axis_angle), } }于是,在 Python 中可以用rr.Transform3D(rotation=rr.Quaternion(xyzw=[...]))或rr.Transform3D(rotation=rr.RotationAxisAngle(...))表达同一种旋转——四元数只是 Rerun 支持的两种旋转编码之一。
4. 更广的消费面
根据RotationQuat组件的文档,使用四元数旋转的 Archetype 还包括Boxes3D、Capsules3D、Cylinders3D、Ellipsoids3D、GaussianSplats3D、GridMap、InstancePoses3D、Volume3D、VoxelGridMap等。以 Python 侧的批量包围盒示例 docs/snippets/all/archetypes/boxes3d_batch.py 为例:
rr.log( "batch", rr.Boxes3D( centers=[[2, 0, 0], [-2, 0, 0], [0, 0, 2]], half_sizes=[[2.0, 2.0, 1.0], [1.0, 1.0, 0.5], [2.0, 0.5, 1.0]], quaternions=[ rr.Quaternion.identity(), rr.Quaternion(xyzw=[0.0, 0.0, 0.382683, 0.923880]), # 45 degrees around Z ], ... ), )可见一个未旋转的盒子与一个绕 Z 轴旋转 45° 的盒子可以同时记录在同一个 Archetype 实例中。
与外部数学库的互操作
Quaternion在设计上充分考虑了与常见 Rust 数学库的双向转换(见 quaternion_ext.rs):
- glam(启用
glamfeature 时):Quaternion → glam::Quat使用TryFrom(归一化失败返回错误);glam::Quat → Quaternion使用From(直接取to_array()的 XYZW 顺序)。 - mint(启用
mintfeature 时):与mint::Quaternion<f32>双向From转换。
mint 是 Rust 生态中用于跨数学库互操作的标准类型约定,这意味着用户完全可以在自己的代码中使用任意数学库计算四元数,再在记录前转换为rerun::Quaternion。
此外,仓库的re_sdk_types测试(crates/store/re_sdk_types/tests/types/mint_conversions.rs)也覆盖了包括四元数在内的 mint 转换路径,可作进一步参考。
实践要点与注意事项
综合文档与源码,使用Quaternion时建议注意以下几点:
- 分量顺序统一为 XYZW:Rerun 内部约定是
x, y, z, w;若你的数据源是 WXYZ 顺序(如部分数学库),请使用from_wxyz/from_wxyz构造器或自行重排,避免静默错位。 - 记录时无需归一化:存储层会原样保存你提供的 4 个浮点数,Viewer 负责归一化;但为了一致性与可读性,建议记录前自行归一化。
- 零模长四元数 = 无效变换:
[0, 0, 0, 0](即Quaternion::invalid())无法归一化,会被 Viewer 视为无效旋转。这也是IDENTITY([0,0,0,1])与INVALID两个常量需要区分的原因。 - 内存表示紧凑:
FixedSizeList(4 x non-null Float32)意味着每个四元数固定占用 16 字节(4 × f32),批量记录时适合按(N, 4)的数组一次性传入,Python 侧会通过flat_np_float32_array_from_array_like自动规整。 - 二选一的旋转表达:
Transform3D中四元数与轴角(RotationAxisAngle)通过Rotation3D枚举统一收口,同一变换只能选择其中一种表达,二者共用with_rotation接口。
Quaternion编码类型虽小,却是 Rerun 3D 数据模型中旋转语义的基石:它定义了四元数的存储顺序(XYZW)、Arrow 表示(定长 4 元 Float32 列表)、归一化行为(原样入库、Viewer 归一化、失败视为无效)以及跨语言的一致性 API。理解这一定义,是正确使用Transform3D、Boxes3D等一切涉及旋转的 Archetype 的前提。
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考