1#![cfg_attr(not(feature = "std"), no_std)]
2#![deny(rust_2018_idioms, unsafe_code, missing_docs)]
3#![doc = include_str!("../README.md")]
4
5#[cfg(not(feature = "std"))]
108extern crate core as std;
109
110#[cfg(feature = "serde-serialize")]
111use serde::{Deserialize, Serialize};
112
113use nalgebra::{
114 allocator::Allocator,
115 storage::{Owned, Storage},
116 DefaultAllocator, Dim, DimName, Isometry3, Matrix, Point3, RealField, SMatrix, Vector3, U1, U2,
117 U3,
118};
119
120#[cfg(feature = "std")]
121pub mod intrinsic_test_utils;
122
123mod intrinsics_perspective;
124pub use intrinsics_perspective::{IntrinsicParametersPerspective, PerspectiveParams};
125
126mod intrinsics_orthographic;
127pub use intrinsics_orthographic::{IntrinsicParametersOrthographic, OrthographicParams};
128
129mod extrinsics;
130pub use extrinsics::ExtrinsicParameters;
131
132mod camera;
133pub use camera::Camera;
134
135pub mod ray_bundle_types;
137
138#[cfg(feature = "alloc")]
139mod ray_intersection;
140#[cfg(feature = "alloc")]
141pub use ray_intersection::best_intersection_of_rays;
142
143pub mod linearize;
144
145#[cfg_attr(feature = "std", derive(Debug))]
147#[non_exhaustive]
148pub enum Error {
149 InvalidInput,
151 SvdFailed,
153 MinimumTwoRaysNeeded,
155 InvalidRotationMatrix,
157}
158
159#[cfg(feature = "std")]
160impl std::error::Error for Error {}
161
162#[cfg(feature = "std")]
163impl std::fmt::Display for Error {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 std::fmt::Debug::fmt(self, f)
166 }
167}
168
169#[derive(Clone)]
177pub struct Pixels<R: RealField, NPTS: Dim, STORAGE> {
178 pub data: nalgebra::Matrix<R, NPTS, U2, STORAGE>,
180}
181
182impl<R: RealField, NPTS: Dim, STORAGE> Pixels<R, NPTS, STORAGE> {
183 #[inline]
185 pub fn new(data: nalgebra::Matrix<R, NPTS, U2, STORAGE>) -> Self {
186 Self { data }
187 }
188}
189
190pub trait CoordinateSystem {}
192
193pub mod coordinate_system {
195
196 #[cfg(feature = "serde-serialize")]
197 use serde::{Deserialize, Serialize};
198
199 #[derive(Debug, Clone, PartialEq)]
204 #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
205 pub struct CameraFrame {}
206 impl crate::CoordinateSystem for CameraFrame {}
207
208 #[derive(Debug, Clone, PartialEq)]
213 #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
214 pub struct WorldFrame {}
215 impl crate::CoordinateSystem for WorldFrame {}
216}
217pub use coordinate_system::{CameraFrame, WorldFrame};
218
219pub struct Points<Coords: CoordinateSystem, R: RealField, NPTS: Dim, STORAGE> {
223 coords: std::marker::PhantomData<Coords>,
224 pub data: nalgebra::Matrix<R, NPTS, U3, STORAGE>,
226}
227
228#[cfg(feature = "std")]
229impl<Coords: CoordinateSystem, R: RealField, NPTS: Dim, STORAGE: std::fmt::Debug> std::fmt::Debug
230 for Points<Coords, R, NPTS, STORAGE>
231{
232 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
233 f.debug_struct("Points")
234 .field("coords", &self.coords)
235 .field("data", &self.data)
236 .finish()
237 }
238}
239
240impl<Coords, R, NPTS, STORAGE> Points<Coords, R, NPTS, STORAGE>
241where
242 Coords: CoordinateSystem,
243 R: RealField,
244 NPTS: Dim,
245{
246 #[inline]
248 pub fn new(data: nalgebra::Matrix<R, NPTS, U3, STORAGE>) -> Self {
249 Self {
250 coords: std::marker::PhantomData,
251 data,
252 }
253 }
254}
255
256#[derive(Debug, Clone, PartialEq)]
261#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
262pub struct RayBundle<Coords, BType, R, NPTS, StorageMultiple>
263where
264 Coords: CoordinateSystem,
265 BType: Bundle<R>,
266 R: RealField,
267 NPTS: Dim,
268 StorageMultiple: Storage<R, NPTS, U3>,
269{
270 coords: std::marker::PhantomData<Coords>,
271 pub data: Matrix<R, NPTS, U3, StorageMultiple>,
273 bundle_type: BType,
274}
275
276impl<Coords, BType, R, NPTS, StorageMultiple> RayBundle<Coords, BType, R, NPTS, StorageMultiple>
277where
278 Coords: CoordinateSystem,
279 BType: Bundle<R>,
280 R: RealField,
281 NPTS: DimName,
282 StorageMultiple: Storage<R, NPTS, U3>,
283 DefaultAllocator: Allocator<NPTS, U3>,
284{
285 #[inline]
287 pub fn directions(&self) -> Matrix<R, NPTS, U3, Owned<R, NPTS, U3>> {
288 self.bundle_type.directions(&self.data)
289 }
290
291 #[inline]
293 pub fn centers(&self) -> Matrix<R, NPTS, U3, Owned<R, NPTS, U3>> {
294 self.bundle_type.centers(&self.data)
295 }
296}
297
298impl<Coords, BType, R> RayBundle<Coords, BType, R, U1, Owned<R, U1, U3>>
299where
300 Coords: CoordinateSystem,
301 BType: Bundle<R>,
302 R: RealField,
303{
304 #[inline]
306 pub fn to_single_ray(&self) -> Ray<Coords, R> {
307 self.bundle_type.to_single_ray(&self.data)
308 }
309}
310
311impl<Coords, R, NPTS, StorageMultiple>
312 RayBundle<Coords, crate::ray_bundle_types::SharedOriginRayBundle<R>, R, NPTS, StorageMultiple>
313where
314 Coords: CoordinateSystem,
315 R: RealField,
316 NPTS: Dim,
317 StorageMultiple: Storage<R, NPTS, U3>,
318{
319 pub fn new_shared_zero_origin(data: Matrix<R, NPTS, U3, StorageMultiple>) -> Self {
325 let bundle_type = crate::ray_bundle_types::SharedOriginRayBundle::new_shared_zero_origin();
326 Self::new(bundle_type, data)
327 }
328}
329
330impl<Coords, R, NPTS, StorageMultiple>
331 RayBundle<
332 Coords,
333 crate::ray_bundle_types::SharedDirectionRayBundle<R>,
334 R,
335 NPTS,
336 StorageMultiple,
337 >
338where
339 Coords: CoordinateSystem,
340 R: RealField,
341 NPTS: Dim,
342 StorageMultiple: Storage<R, NPTS, U3>,
343{
344 pub fn new_shared_plusz_direction(data: Matrix<R, NPTS, U3, StorageMultiple>) -> Self {
350 let bundle_type =
351 crate::ray_bundle_types::SharedDirectionRayBundle::new_plusz_shared_direction();
352 Self::new(bundle_type, data)
353 }
354}
355
356impl<Coords, BType, R, NPTS, StorageMultiple> RayBundle<Coords, BType, R, NPTS, StorageMultiple>
357where
358 Coords: CoordinateSystem,
359 BType: Bundle<R>,
360 R: RealField,
361 NPTS: Dim,
362 StorageMultiple: Storage<R, NPTS, U3>,
363{
364 #[inline]
369 fn new(bundle_type: BType, data: nalgebra::Matrix<R, NPTS, U3, StorageMultiple>) -> Self {
370 Self {
371 coords: std::marker::PhantomData,
372 data,
373 bundle_type,
374 }
375 }
376
377 #[inline]
382 pub fn point_on_ray(&self) -> Points<Coords, R, NPTS, Owned<R, NPTS, U3>>
383 where
384 DefaultAllocator: Allocator<NPTS, U3>,
385 {
386 self.bundle_type.point_on_ray(&self.data)
387 }
388
389 #[inline]
391 pub fn point_on_ray_at_distance(
392 &self,
393 distance: R,
394 ) -> Points<Coords, R, NPTS, Owned<R, NPTS, U3>>
395 where
396 DefaultAllocator: Allocator<NPTS, U3>,
397 {
398 self.bundle_type
399 .point_on_ray_at_distance(&self.data, distance)
400 }
401
402 #[inline]
403 fn to_pose<OutFrame>(
404 &self,
405 pose: Isometry3<R>,
406 ) -> RayBundle<OutFrame, BType, R, NPTS, Owned<R, NPTS, U3>>
407 where
408 R: RealField,
409 NPTS: Dim,
410 OutFrame: CoordinateSystem,
411 DefaultAllocator: Allocator<NPTS, U3>,
412 {
413 self.bundle_type.to_pose(pose, &self.data)
414 }
415}
416
417pub struct Ray<Coords, R: RealField> {
422 pub center: SMatrix<R, 1, 3>,
424 pub direction: SMatrix<R, 1, 3>,
426 c: std::marker::PhantomData<Coords>,
427}
428
429impl<Coords, R: RealField> Ray<Coords, R> {
430 #[inline]
432 pub fn new(center: SMatrix<R, 1, 3>, direction: SMatrix<R, 1, 3>) -> Self {
433 Self {
434 center,
435 direction,
436 c: std::marker::PhantomData,
437 }
438 }
439}
440
441pub trait Bundle<R>
443where
444 R: RealField,
445{
446 fn to_single_ray<Coords>(&self, self_data: &SMatrix<R, 1, 3>) -> Ray<Coords, R>
448 where
449 Coords: CoordinateSystem;
450
451 fn directions<NPTS, StorageIn>(
457 &self,
458 self_data: &Matrix<R, NPTS, U3, StorageIn>,
459 ) -> Matrix<R, NPTS, U3, Owned<R, NPTS, U3>>
460 where
461 NPTS: DimName,
462 StorageIn: Storage<R, NPTS, U3>,
463 DefaultAllocator: Allocator<NPTS, U3>;
464
465 fn centers<NPTS, StorageIn>(
471 &self,
472 self_data: &Matrix<R, NPTS, U3, StorageIn>,
473 ) -> Matrix<R, NPTS, U3, Owned<R, NPTS, U3>>
474 where
475 NPTS: DimName,
476 StorageIn: Storage<R, NPTS, U3>,
477 DefaultAllocator: Allocator<NPTS, U3>;
478
479 fn point_on_ray<NPTS, StorageIn, OutFrame>(
484 &self,
485 self_data: &Matrix<R, NPTS, U3, StorageIn>,
486 ) -> Points<OutFrame, R, NPTS, Owned<R, NPTS, U3>>
487 where
488 Self: Sized,
489 NPTS: Dim,
490 StorageIn: Storage<R, NPTS, U3>,
491 OutFrame: CoordinateSystem,
492 DefaultAllocator: Allocator<NPTS, U3>;
493
494 fn point_on_ray_at_distance<NPTS, StorageIn, OutFrame>(
496 &self,
497 self_data: &Matrix<R, NPTS, U3, StorageIn>,
498 distance: R,
499 ) -> Points<OutFrame, R, NPTS, Owned<R, NPTS, U3>>
500 where
501 Self: Sized,
502 NPTS: Dim,
503 StorageIn: Storage<R, NPTS, U3>,
504 OutFrame: CoordinateSystem,
505 DefaultAllocator: Allocator<NPTS, U3>;
506
507 fn to_pose<NPTS, StorageIn, OutFrame>(
509 &self,
510 pose: Isometry3<R>,
511 self_data: &Matrix<R, NPTS, U3, StorageIn>,
512 ) -> RayBundle<OutFrame, Self, R, NPTS, Owned<R, NPTS, U3>>
513 where
514 Self: Sized,
515 R: RealField,
516 NPTS: Dim,
517 StorageIn: Storage<R, NPTS, U3>,
518 OutFrame: CoordinateSystem,
519 DefaultAllocator: Allocator<NPTS, U3>;
520}
521
522pub trait IntrinsicParameters<R>: std::fmt::Debug + Clone
524where
525 R: RealField,
526{
527 type BundleType;
529
530 fn pixel_to_camera<IN, NPTS>(
532 &self,
533 pixels: &Pixels<R, NPTS, IN>,
534 ) -> RayBundle<coordinate_system::CameraFrame, Self::BundleType, R, NPTS, Owned<R, NPTS, U3>>
535 where
536 Self::BundleType: Bundle<R>,
537 IN: Storage<R, NPTS, U2>,
538 NPTS: Dim,
539 DefaultAllocator: Allocator<U1, U2>, DefaultAllocator: Allocator<NPTS, U2>, DefaultAllocator: Allocator<NPTS, U3>;
542
543 fn camera_to_pixel<IN, NPTS>(
545 &self,
546 camera: &Points<coordinate_system::CameraFrame, R, NPTS, IN>,
547 ) -> Pixels<R, NPTS, Owned<R, NPTS, U2>>
548 where
549 IN: Storage<R, NPTS, U3>,
550 NPTS: Dim,
551 DefaultAllocator: Allocator<NPTS, U2>;
552}
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557 use nalgebra::convert;
558
559 #[cfg(not(feature = "std"))]
560 compile_error!("tests require std");
561
562 #[test]
563 fn rays_shared_origin() {
564 let b1 =
566 RayBundle::<WorldFrame, _, _, _, _>::new_shared_zero_origin(SMatrix::<_, 2, 3>::new(
567 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, ));
570
571 let actual_dist1 = b1.point_on_ray_at_distance(1.0).data;
573
574 {
575 let r1m = (1.0_f64 + 4.0 + 9.0).sqrt();
577 let r2m = (16.0_f64 + 25.0 + 36.0).sqrt();
578 let expected = SMatrix::<_, 2, 3>::new(
579 1.0 / r1m,
580 2.0 / r1m,
581 3.0 / r1m, 4.0 / r2m,
583 5.0 / r2m,
584 6.0 / r2m, );
586
587 approx::assert_abs_diff_eq!(actual_dist1, expected, epsilon = 1e-10);
589 }
590
591 let actual_dist10 = b1.point_on_ray_at_distance(10.0).data;
593
594 let actual = b1.point_on_ray().data;
596
597 for i in 0..actual_dist1.nrows() {
598 assert_on_line(actual_dist1.row(i), actual_dist10.row(i), actual.row(i));
599 }
600 }
601
602 #[test]
603 fn rays_shared_direction() {
604 let b1 = RayBundle::<WorldFrame, _, _, _, _>::new_shared_plusz_direction(
606 SMatrix::<_, 2, 3>::new(
607 1.0, 2.0, 0.0, 3.0, 4.0, 0.0, ),
610 );
611
612 let actual_dist10 = b1.point_on_ray_at_distance(10.0).data;
614
615 {
616 let expected_dist10 = SMatrix::<_, 2, 3>::new(
618 1.0, 2.0, 10.0, 3.0, 4.0, 10.0, );
621
622 approx::assert_abs_diff_eq!(actual_dist10, expected_dist10, epsilon = 1e-10);
624 }
625
626 let actual_dist0 = b1.point_on_ray_at_distance(0.0).data;
628
629 {
630 let expected_dist0 = SMatrix::<_, 2, 3>::new(
632 1.0, 2.0, 0.0, 3.0, 4.0, 0.0, );
635
636 approx::assert_abs_diff_eq!(actual_dist0, expected_dist0, epsilon = 1e-10);
638 }
639
640 let actual = b1.point_on_ray().data;
642
643 for i in 0..actual_dist0.nrows() {
644 assert_on_line(actual_dist0.row(i), actual_dist10.row(i), actual.row(i));
645 }
646 }
647
648 fn assert_on_line<R, S1, S2, S3>(
649 line_a: Matrix<R, U1, U3, S1>,
650 line_b: Matrix<R, U1, U3, S2>,
651 test_pt: Matrix<R, U1, U3, S3>,
652 ) where
653 R: RealField,
654 S1: Storage<R, U1, U3>,
655 S2: Storage<R, U1, U3>,
656 S3: Storage<R, U1, U3>,
657 {
658 let dir = &line_b - &line_a;
659 let testx = &test_pt - &line_a;
660 let mag_dir = (dir[0].clone() * dir[0].clone()
661 + dir[1].clone() * dir[1].clone()
662 + dir[2].clone() * dir[2].clone())
663 .sqrt();
664 let mag_testx = (testx[0].clone() * testx[0].clone()
665 + testx[1].clone() * testx[1].clone()
666 + testx[2].clone() * testx[2].clone())
667 .sqrt();
668 let scale = mag_dir / mag_testx;
669
670 for j in 0..3 {
671 approx::assert_abs_diff_eq!(
672 testx[j].clone() * scale.clone(),
673 dir[j].clone(),
674 epsilon = convert(1e-10)
675 );
676 }
677 }
678
679 #[test]
680 #[cfg(feature = "serde-serialize")]
681 fn test_ray_bundle_serde() {
682 let expected =
683 RayBundle::<WorldFrame, _, _, _, _>::new_shared_plusz_direction(
684 SMatrix::<_, 2, 3>::new(
685 1.0, 2.0, 0.0, 3.0, 4.0, 0.0, ),
688 );
689
690 let buf = serde_json::to_string(&expected).unwrap();
691 let actual: RayBundle<_, _, _, _, _> = serde_json::from_str(&buf).unwrap();
692 assert!(expected == actual);
693 }
694}