1#![allow(clippy::wrong_self_convention)] use arrow2::datatypes::DataType;
4use arrow2_convert::{
5 deserialize::ArrowDeserialize,
6 field::{ArrowField, FixedSizeBinary},
7 serialize::ArrowSerialize,
8};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
13pub enum ViewDir {
14 Up = 1,
15 Down = 2,
16 Right = 3,
17 Left = 4,
18 Forward = 5,
19 Back = 6,
20}
21
22impl ViewDir {
23 #[inline]
24 fn from_ascii_char(c: u8) -> Result<Self, String> {
25 match c {
26 b'U' => Ok(Self::Up),
27 b'D' => Ok(Self::Down),
28 b'R' => Ok(Self::Right),
29 b'L' => Ok(Self::Left),
30 b'F' => Ok(Self::Forward),
31 b'B' => Ok(Self::Back),
32 _ => Err("Expected one of UDRLFB (Up Down Right Left Forward Back)".to_owned()),
33 }
34 }
35
36 #[inline]
37 pub fn short(&self) -> &'static str {
38 match self {
39 Self::Up => "U",
40 Self::Down => "D",
41 Self::Right => "R",
42 Self::Left => "L",
43 Self::Forward => "F",
44 Self::Back => "B",
45 }
46 }
47
48 #[inline]
49 pub fn long(&self) -> &'static str {
50 match self {
51 Self::Up => "Up",
52 Self::Down => "Down",
53 Self::Right => "Right",
54 Self::Left => "Left",
55 Self::Forward => "Forward",
56 Self::Back => "Back",
57 }
58 }
59}
60
61impl TryFrom<u8> for ViewDir {
62 type Error = super::FieldError;
63
64 #[inline]
65 fn try_from(i: u8) -> super::Result<Self> {
66 match i {
67 1 => Ok(Self::Up),
68 2 => Ok(Self::Down),
69 3 => Ok(Self::Right),
70 4 => Ok(Self::Left),
71 5 => Ok(Self::Forward),
72 6 => Ok(Self::Back),
73 _ => Err(super::FieldError::BadValue),
74 }
75 }
76}
77
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
98pub struct ViewCoordinates(pub [ViewDir; 3]);
99
100impl re_log_types::LegacyComponent for ViewCoordinates {
101 #[inline]
102 fn legacy_name() -> re_log_types::ComponentName {
103 "rerun.view_coordinates".into()
104 }
105}
106
107impl ViewCoordinates {
108 pub const RDF: Self = Self([ViewDir::Right, ViewDir::Down, ViewDir::Forward]);
110
111 pub const RUB: Self = Self([ViewDir::Right, ViewDir::Up, ViewDir::Back]);
113
114 pub fn from_up_and_handedness(up: SignedAxis3, handedness: Handedness) -> Self {
116 use ViewDir::{Back, Down, Forward, Right, Up};
117 match handedness {
118 Handedness::Right => match up {
119 SignedAxis3::POSITIVE_X => Self([Up, Right, Forward]),
120 SignedAxis3::NEGATIVE_X => Self([Down, Right, Back]),
121 SignedAxis3::POSITIVE_Y => Self([Right, Up, Back]),
122 SignedAxis3::NEGATIVE_Y => Self([Right, Down, Forward]),
123 SignedAxis3::POSITIVE_Z => Self([Right, Forward, Up]),
124 SignedAxis3::NEGATIVE_Z => Self([Right, Back, Down]),
125 },
126 Handedness::Left => match up {
127 SignedAxis3::POSITIVE_X => Self([Up, Right, Back]),
128 SignedAxis3::NEGATIVE_X => Self([Down, Right, Forward]),
129 SignedAxis3::POSITIVE_Y => Self([Right, Up, Forward]),
130 SignedAxis3::NEGATIVE_Y => Self([Right, Down, Back]),
131 SignedAxis3::POSITIVE_Z => Self([Right, Back, Up]),
132 SignedAxis3::NEGATIVE_Z => Self([Right, Forward, Down]),
133 },
134 }
135 }
136
137 pub fn sanity_check(&self) -> Result<(), String> {
139 let mut dims = [false; 3];
140 for dir in self.0 {
141 let dim = match dir {
142 ViewDir::Up | ViewDir::Down => 0,
143 ViewDir::Right | ViewDir::Left => 1,
144 ViewDir::Forward | ViewDir::Back => 2,
145 };
146 dims[dim] = true;
147 }
148 if dims == [true; 3] {
149 Ok(())
150 } else {
151 Err(format!(
152 "Coordinate system does not cover all three cardinal directions: {}",
153 self.describe()
154 ))
155 }
156 }
157
158 #[inline]
159 pub fn up(&self) -> Option<SignedAxis3> {
160 for (dim, &dir) in self.0.iter().enumerate() {
161 if dir == ViewDir::Up {
162 return Some(SignedAxis3::new(Sign::Positive, Axis3::from_dim(dim)));
163 } else if dir == ViewDir::Down {
164 return Some(SignedAxis3::new(Sign::Negative, Axis3::from_dim(dim)));
165 }
166 }
167 None
168 }
169
170 #[inline]
171 pub fn right(&self) -> Option<SignedAxis3> {
172 for (dim, &dir) in self.0.iter().enumerate() {
173 if dir == ViewDir::Right {
174 return Some(SignedAxis3::new(Sign::Positive, Axis3::from_dim(dim)));
175 } else if dir == ViewDir::Left {
176 return Some(SignedAxis3::new(Sign::Negative, Axis3::from_dim(dim)));
177 }
178 }
179 None
180 }
181
182 #[inline]
183 pub fn forward(&self) -> Option<SignedAxis3> {
184 for (dim, &dir) in self.0.iter().enumerate() {
185 if dir == ViewDir::Forward {
186 return Some(SignedAxis3::new(Sign::Positive, Axis3::from_dim(dim)));
187 } else if dir == ViewDir::Back {
188 return Some(SignedAxis3::new(Sign::Negative, Axis3::from_dim(dim)));
189 }
190 }
191 None
192 }
193
194 pub fn describe_short(&self) -> String {
195 let [x, y, z] = self.0;
196 format!("{}{}{}", x.short(), y.short(), z.short(),)
197 }
198
199 pub fn describe(&self) -> String {
200 let [x, y, z] = self.0;
201 format!(
202 "{}{}{} (X={}, Y={}, Z={})",
203 x.short(),
204 y.short(),
205 z.short(),
206 x.long(),
207 y.long(),
208 z.long()
209 )
210 }
211
212 #[cfg(feature = "glam")]
214 #[inline]
215 pub fn from_other(&self, other: &Self) -> glam::Mat3 {
216 self.from_rdf() * other.to_rdf()
217 }
218
219 #[cfg(feature = "glam")]
223 #[inline]
224 pub fn to_rdf(&self) -> glam::Mat3 {
225 fn rdf(dir: ViewDir) -> [f32; 3] {
226 match dir {
227 ViewDir::Right => [1.0, 0.0, 0.0],
228 ViewDir::Left => [-1.0, 0.0, 0.0],
229 ViewDir::Up => [0.0, -1.0, 0.0],
230 ViewDir::Down => [0.0, 1.0, 0.0],
231 ViewDir::Back => [0.0, 0.0, -1.0],
232 ViewDir::Forward => [0.0, 0.0, 1.0],
233 }
234 }
235
236 glam::Mat3::from_cols_array_2d(&[rdf(self.0[0]), rdf(self.0[1]), rdf(self.0[2])])
237 }
238
239 #[cfg(feature = "glam")]
243 #[inline]
244 pub fn from_rdf(&self) -> glam::Mat3 {
245 self.to_rdf().transpose()
246 }
247
248 #[cfg(feature = "glam")]
252 #[inline]
253 pub fn to_rub(&self) -> glam::Mat3 {
254 fn rub(dir: ViewDir) -> [f32; 3] {
255 match dir {
256 ViewDir::Right => [1.0, 0.0, 0.0],
257 ViewDir::Left => [-1.0, 0.0, 0.0],
258 ViewDir::Up => [0.0, 1.0, 0.0],
259 ViewDir::Down => [0.0, -1.0, 0.0],
260 ViewDir::Back => [0.0, 0.0, 1.0],
261 ViewDir::Forward => [0.0, 0.0, -1.0],
262 }
263 }
264
265 glam::Mat3::from_cols_array_2d(&[rub(self.0[0]), rub(self.0[1]), rub(self.0[2])])
266 }
267
268 #[cfg(feature = "glam")]
272 #[inline]
273 pub fn from_rub(&self) -> glam::Mat3 {
274 self.to_rub().transpose()
275 }
276
277 #[cfg(feature = "glam")]
283 #[inline]
284 pub fn from_rub_quat(&self) -> Result<glam::Quat, String> {
285 let mat3 = self.from_rub();
286
287 let det = mat3.determinant();
288 if det == 1.0 {
289 Ok(glam::Quat::from_mat3(&mat3))
290 } else if det == -1.0 {
291 Err(format!(
292 "Rerun does not yet support left-handed coordinate systems (found {})",
293 self.describe()
294 ))
295 } else {
296 Err(format!(
297 "Found a degenerate coordinate system: {}",
298 self.describe()
299 ))
300 }
301 }
302
303 #[cfg(feature = "glam")]
304 #[inline]
305 pub fn handedness(&self) -> Option<Handedness> {
306 let to_rdf = self.to_rdf();
307 let det = to_rdf.determinant();
308 if det == -1.0 {
309 Some(Handedness::Left)
310 } else if det == 0.0 {
311 None } else {
313 Some(Handedness::Right)
314 }
315 }
316}
317
318impl std::str::FromStr for ViewCoordinates {
319 type Err = String;
320
321 #[inline]
322 fn from_str(s: &str) -> Result<Self, Self::Err> {
323 match s.as_bytes() {
324 [x, y, z] => {
325 let slf = Self([
326 ViewDir::from_ascii_char(*x)?,
327 ViewDir::from_ascii_char(*y)?,
328 ViewDir::from_ascii_char(*z)?,
329 ]);
330 slf.sanity_check()?;
331 Ok(slf)
332 }
333 _ => Err(format!("Expected three letters, got: {s:?}")),
334 }
335 }
336}
337
338impl ArrowField for ViewCoordinates {
339 type Type = Self;
340
341 #[inline]
342 fn data_type() -> DataType {
343 <FixedSizeBinary<3> as ArrowField>::data_type()
344 }
345}
346
347impl ArrowSerialize for ViewCoordinates {
348 type MutableArrayType = <FixedSizeBinary<3> as ArrowSerialize>::MutableArrayType;
349
350 #[inline]
351 fn new_array() -> Self::MutableArrayType {
352 FixedSizeBinary::<3>::new_array()
353 }
354
355 #[inline]
356 fn arrow_serialize(v: &Self, array: &mut Self::MutableArrayType) -> arrow2::error::Result<()> {
357 let bytes = [v.0[0] as u8, v.0[1] as u8, v.0[2] as u8];
358 array.try_push(Some(bytes))
359 }
360}
361
362impl ArrowDeserialize for ViewCoordinates {
363 type ArrayType = <FixedSizeBinary<3> as ArrowDeserialize>::ArrayType;
364
365 #[inline]
366 fn arrow_deserialize(
367 bytes: <&Self::ArrayType as IntoIterator>::Item,
368 ) -> Option<<Self as ArrowField>::Type> {
369 bytes.and_then(|bytes| {
370 let dirs = [
371 bytes[0].try_into().ok()?,
372 bytes[1].try_into().ok()?,
373 bytes[2].try_into().ok()?,
374 ];
375 Some(ViewCoordinates(dirs))
376 })
377 }
378}
379
380re_log_types::component_legacy_shim!(ViewCoordinates);
381
382#[derive(Clone, Copy, Debug, PartialEq, Eq)]
386#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
387pub enum Axis3 {
388 X,
389 Y,
390 Z,
391}
392
393impl Axis3 {
394 #[inline]
395 pub fn from_dim(dim: usize) -> Self {
396 match dim {
397 0 => Self::X,
398 1 => Self::Y,
399 2 => Self::Z,
400 _ => panic!("Expected a 3D axis, got {dim}"),
401 }
402 }
403}
404
405impl std::fmt::Display for Axis3 {
406 #[inline]
407 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
408 match self {
409 Self::X => "X".fmt(f),
410 Self::Y => "Y".fmt(f),
411 Self::Z => "Z".fmt(f),
412 }
413 }
414}
415
416#[derive(Clone, Copy, Debug, PartialEq, Eq)]
420#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
421pub enum Sign {
422 Positive,
423 Negative,
424}
425
426#[derive(Clone, Copy, Debug, PartialEq, Eq)]
431#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
432pub struct SignedAxis3 {
433 pub sign: Sign,
434 pub axis: Axis3,
435}
436
437impl SignedAxis3 {
438 pub const POSITIVE_X: Self = Self::new(Sign::Positive, Axis3::X);
439 pub const NEGATIVE_X: Self = Self::new(Sign::Negative, Axis3::X);
440 pub const POSITIVE_Y: Self = Self::new(Sign::Positive, Axis3::Y);
441 pub const NEGATIVE_Y: Self = Self::new(Sign::Negative, Axis3::Y);
442 pub const POSITIVE_Z: Self = Self::new(Sign::Positive, Axis3::Z);
443 pub const NEGATIVE_Z: Self = Self::new(Sign::Negative, Axis3::Z);
444
445 #[inline]
446 pub const fn new(sign: Sign, axis: Axis3) -> Self {
447 Self { sign, axis }
448 }
449
450 #[inline]
451 pub fn as_vec3(&self) -> [f32; 3] {
452 match (self.sign, self.axis) {
453 (Sign::Positive, Axis3::X) => [1.0, 0.0, 0.0],
454 (Sign::Negative, Axis3::X) => [-1.0, 0.0, 0.0],
455 (Sign::Positive, Axis3::Y) => [0.0, 1.0, 0.0],
456 (Sign::Negative, Axis3::Y) => [0.0, -1.0, 0.0],
457 (Sign::Positive, Axis3::Z) => [0.0, 0.0, 1.0],
458 (Sign::Negative, Axis3::Z) => [0.0, 0.0, -1.0],
459 }
460 }
461}
462
463impl std::fmt::Display for SignedAxis3 {
464 #[inline]
465 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
466 let sign = match self.sign {
467 Sign::Positive => "+",
468 Sign::Negative => "-",
469 };
470 write!(f, "{}{}", sign, self.axis)
471 }
472}
473
474impl std::str::FromStr for SignedAxis3 {
475 type Err = String;
476
477 fn from_str(s: &str) -> Result<Self, Self::Err> {
478 match s {
479 "+X" => Ok(Self::new(Sign::Positive, Axis3::X)),
480 "-X" => Ok(Self::new(Sign::Negative, Axis3::X)),
481 "+Y" => Ok(Self::new(Sign::Positive, Axis3::Y)),
482 "-Y" => Ok(Self::new(Sign::Negative, Axis3::Y)),
483 "+Z" => Ok(Self::new(Sign::Positive, Axis3::Z)),
484 "-Z" => Ok(Self::new(Sign::Negative, Axis3::Z)),
485 _ => Err("Expected one of: +X -X +Y -Y +Z -Z".to_owned()),
486 }
487 }
488}
489
490#[cfg(feature = "glam")]
491impl From<SignedAxis3> for glam::Vec3 {
492 #[inline]
493 fn from(signed_axis: SignedAxis3) -> Self {
494 glam::Vec3::from(signed_axis.as_vec3())
495 }
496}
497
498#[derive(Clone, Copy, Debug, PartialEq, Eq)]
502#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
503pub enum Handedness {
504 Right,
505 Left,
506}
507
508impl Handedness {
509 #[inline]
510 pub const fn from_right_handed(right_handed: bool) -> Self {
511 if right_handed {
512 Handedness::Right
513 } else {
514 Handedness::Left
515 }
516 }
517
518 #[inline]
519 pub fn describe(&self) -> &'static str {
520 match self {
521 Self::Left => "left handed",
522 Self::Right => "right handed",
523 }
524 }
525}
526
527#[cfg(feature = "glam")]
530#[test]
531fn view_coordinates() {
532 use glam::{vec3, Mat3};
533
534 assert_eq!(ViewCoordinates::RUB.to_rub(), Mat3::IDENTITY);
535 assert_eq!(ViewCoordinates::RUB.from_rub(), Mat3::IDENTITY);
536
537 {
538 assert!("UUDDLRLRBAStart".parse::<ViewCoordinates>().is_err());
539 assert!("UUD".parse::<ViewCoordinates>().is_err());
540
541 let rub = "RUB".parse::<ViewCoordinates>().unwrap();
542 let bru = "BRU".parse::<ViewCoordinates>().unwrap();
543
544 assert_eq!(rub, ViewCoordinates::RUB);
545
546 assert_eq!(rub.to_rub(), Mat3::IDENTITY);
547 assert_eq!(
548 bru.to_rub(),
549 Mat3::from_cols_array_2d(&[[0., 0., 1.], [1., 0., 0.], [0., 1., 0.]])
550 );
551 assert_eq!(bru.to_rub() * vec3(1.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0));
552 }
553
554 {
555 let cardinal_direction = [
556 SignedAxis3::POSITIVE_X,
557 SignedAxis3::NEGATIVE_X,
558 SignedAxis3::POSITIVE_Y,
559 SignedAxis3::NEGATIVE_Y,
560 SignedAxis3::POSITIVE_Z,
561 SignedAxis3::NEGATIVE_Z,
562 ];
563
564 for axis in cardinal_direction {
565 for handedness in [Handedness::Right, Handedness::Left] {
566 let system = ViewCoordinates::from_up_and_handedness(axis, handedness);
567 assert_eq!(system.handedness(), Some(handedness));
568
569 let det = system.to_rub().determinant();
570 assert!(det == -1.0 || det == 0.0 || det == 1.0);
571
572 let short = system.describe_short();
573 assert_eq!(short.parse(), Ok(system));
574 }
575 }
576 }
577}
578
579#[test]
580fn test_viewcoordinates_roundtrip() {
581 use arrow2::array::Array;
582 use arrow2_convert::{deserialize::TryIntoCollection, serialize::TryIntoArrow};
583
584 let views_in = vec![
585 "RUB".parse::<ViewCoordinates>().unwrap(),
586 "LFD".parse::<ViewCoordinates>().unwrap(),
587 ];
588 let array: Box<dyn Array> = views_in.try_into_arrow().unwrap();
589 let views_out: Vec<ViewCoordinates> = TryIntoCollection::try_into_collection(array).unwrap();
590 assert_eq!(views_in, views_out);
591}