1use nalgebra as na;
2use pyo3::prelude::*;
3use ndarray::{Axis, Array3};
4use anyhow::{Result, anyhow, ensure};
5use numpy::{PyReadonlyArray1, PyArray2, ToPyArray, PyUntypedArrayMethods};
6
7mod orthogonal;
8mod perspective;
9
10const EPS: f32 = 1e-6;
11
12pub use self::orthogonal::{orthogonal_ray, py_orthogonal_ray};
14pub use self::perspective::{perspective_ray, py_perspective_ray};
16
17fn from_ray_corner(ray_corner: Array3<f32>) -> Result<Array3<f32>> {
20 ensure!(ray_corner.shape()[1] > 1);
21 ensure!(ray_corner.shape()[2] > 1);
22 let mut ray = Array3::zeros((18, ray_corner.shape()[1] - 1, ray_corner.shape()[2] - 1));
23 ray.axis_iter_mut(Axis(2)).enumerate().for_each(|(x, mut ray)| {
24 ray.axis_iter_mut(Axis(1)).enumerate().for_each(|(y, mut ray)| {
25 for i in 0..3 {
26 ray[i] = ray_corner[(i, y, x)];
27 ray[i + 3] = ray_corner[(i, y, x + 1)] - ray_corner[(i, y, x)];
28 ray[i + 6] = ray_corner[(i, y + 1, x)] - ray_corner[(i, y, x)];
29 ray[i + 9] = ray_corner[(i + 3, y, x)];
30 ray[i + 12] = ray_corner[(i + 3, y, x + 1)] - ray_corner[(i + 3, y, x)];
31 ray[i + 15] = ray_corner[(i + 3, y + 1, x)] - ray_corner[(i + 3, y, x)];
32 }
33 });
34 });
35 Ok(ray)
36}
37
38pub fn look_at_extrinsic(
40 position: na::VectorView3<f32>,
41 look_at: na::VectorView3<f32>,
42 up: na::VectorView3<f32>) -> Result<na::Matrix4<f32>> {
43 ensure!(position.fold(true, |acc, v| { acc && v.is_finite() }),
44 "camera::look_at_extrinsic: `position` is not finite");
45 ensure!(look_at.fold(true, |acc, v| { acc && v.is_finite() }),
46 "camera::look_at_extrinsic: `look_at` is not finite");
47 ensure!(up.fold(true, |acc, v| { acc && v.is_finite() }),
48 "camera::look_at_extrinsic: `up` is not finite");
49 let d = (look_at - position).try_normalize(EPS).ok_or(anyhow!("camera::look_at_extrinsic: `d` is 0"))?;
50 let right = d.cross(&up).try_normalize(EPS).ok_or(anyhow!("camera::look_at_extrinsic: `right` is 0"))?;
51 let down = d.cross(&right).normalize();
52 let rotation = na::Matrix3::from_rows(&[right.transpose(), down.transpose(), d.transpose()]).to_homogeneous();
53 let translation = na::Matrix4::new_translation(&-position);
54 Ok(rotation * translation)
55}
56
57#[pyfunction]
60#[pyo3(name = "look_at_extrinsic")]
61pub fn py_look_at_extrinsic<'py>(
62 py: Python<'py>,
63 position: PyReadonlyArray1<f32>,
64 look_at: PyReadonlyArray1<f32>,
65 up: PyReadonlyArray1<f32>) -> PyResult<Bound<'py, PyArray2<f32>>> {
66 let position = position.try_as_matrix().ok_or(
67 anyhow!("camera::look_at_extrinsic: `position` expected shape {:?}, found {:?}", [3], position.shape()))?;
68 let look_at = look_at.try_as_matrix().ok_or(
69 anyhow!("camera::look_at_extrinsic: `look_at` expected shape {:?}, found {:?}", [3], look_at.shape()))?;
70 let up = up.try_as_matrix().ok_or(
71 anyhow!("camera::look_at_extrinsic: `up` expected shape {:?}, found {:?}", [3], up.shape()))?;
72 let extrinsic = look_at_extrinsic(position, look_at, up)?;
73 Ok(extrinsic.to_pyarray(py))
74}
75
76pub fn py_camera<'py>(py: Python<'py>, parent_module: &Bound<'py, PyModule>) -> PyResult<()> {
78 let module = PyModule::new(py, "camera")?;
79 module.add_function(wrap_pyfunction!(py_look_at_extrinsic, &module)?)?;
80 module.add_function(wrap_pyfunction!(py_orthogonal_ray, &module)?)?;
81 module.add_function(wrap_pyfunction!(py_perspective_ray, &module)?)?;
82 parent_module.add_submodule(&module)?;
83 Ok(())
84}