pub mod hit_region;
pub mod math;
pub mod raster;
pub mod curve_display;
pub mod datum;
pub mod dimension;
pub mod transform;
pub mod view_cube;
pub use math::{Ray, Vec3};
#[derive(Debug, Clone, Copy)]
pub struct GizmoCamera {
pub view_proj: [[f32; 4]; 4],
pub eye: Vec3,
pub forward: Vec3,
pub up: Vec3,
pub viewport: [f32; 2],
pub orthographic: bool,
}
impl GizmoCamera {
pub fn world_to_screen(&self, p: Vec3) -> Option<[f32; 2]> {
let clip = mat_mul_point(&self.view_proj, p);
let w = clip[3];
if w <= 1e-6 {
return None;
}
let ndc_x = clip[0] / w;
let ndc_y = clip[1] / w;
Some([
(ndc_x * 0.5 + 0.5) * self.viewport[0],
(0.5 - ndc_y * 0.5) * self.viewport[1],
])
}
pub fn world_per_pixel(&self, at: Vec3) -> f32 {
let right = self.screen_right(at);
let base = match self.world_to_screen(at) {
Some(s) => s,
None => return 1.0,
};
let delta = 1.0_f32; let moved = match self.world_to_screen(at.add(right.scale(delta))) {
Some(s) => s,
None => return 1.0,
};
let px = ((moved[0] - base[0]).powi(2) + (moved[1] - base[1]).powi(2)).sqrt();
if px <= 1e-6 {
1.0
} else {
delta / px
}
}
pub fn view_depth(&self, p: Vec3) -> f32 {
p.sub(self.eye).dot(self.forward)
}
pub fn screen_right(&self, _at: Vec3) -> Vec3 {
let up = if self.forward.z.abs() > 0.9 {
Vec3::new(0.0, 1.0, 0.0)
} else {
Vec3::new(0.0, 0.0, 1.0)
};
self.forward.cross(up).normalized()
}
pub fn ray_from_screen(&self, x: f32, y: f32) -> Ray {
let ndc_x = (x / self.viewport[0]) * 2.0 - 1.0;
let ndc_y = 1.0 - (y / self.viewport[1]) * 2.0;
let inv = mat_inverse(&self.view_proj);
let near = mat_unproject(&inv, ndc_x, ndc_y, 0.0);
let far = mat_unproject(&inv, ndc_x, ndc_y, 1.0);
let dir = far.sub(near).normalized();
if self.orthographic {
Ray { origin: near, dir }
} else {
Ray {
origin: self.eye,
dir,
}
}
}
}
impl crate::hit_region::RegionCamera for GizmoCamera {
fn is_orthographic(&self) -> bool {
self.orthographic
}
fn depth(&self, p: [f64; 3]) -> f64 {
self.view_depth(Vec3::new(p[0] as f32, p[1] as f32, p[2] as f32)) as f64
}
fn project_px(&self, p: [f64; 3]) -> Option<[f32; 2]> {
self.world_to_screen(Vec3::new(p[0] as f32, p[1] as f32, p[2] as f32))
}
}
#[derive(Debug, Clone, Copy)]
pub struct LineVertex {
pub pos: [f32; 3],
pub color: [f32; 4],
}
#[derive(Debug, Clone, Copy)]
pub struct TriVertex {
pub pos: [f32; 3],
pub normal: [f32; 3],
pub color: [f32; 4],
}
#[derive(Debug, Clone, Default)]
pub struct Overlay {
pub lines: Vec<LineVertex>,
pub tris: Vec<TriVertex>,
}
impl Overlay {
pub fn new() -> Self {
Self::default()
}
pub fn line(&mut self, a: Vec3, b: Vec3, color: [f32; 4]) {
self.lines.push(LineVertex {
pos: a.into(),
color,
});
self.lines.push(LineVertex {
pos: b.into(),
color,
});
}
pub fn tri(&mut self, a: Vec3, b: Vec3, c: Vec3, color: [f32; 4]) {
let n = b.sub(a).cross(c.sub(a)).normalized();
for p in [a, b, c] {
self.tris.push(TriVertex {
pos: p.into(),
normal: n.into(),
color,
});
}
}
pub fn extend(&mut self, other: &Overlay) {
self.lines.extend_from_slice(&other.lines);
self.tris.extend_from_slice(&other.tris);
}
}
pub type HandleId = u32;
pub trait Gizmo {
fn geometry(&self, camera: &GizmoCamera, hovered: Option<HandleId>, active: Option<HandleId>)
-> Overlay;
fn hit(&self, camera: &GizmoCamera, screen: [f32; 2]) -> Option<HandleId>;
}
pub fn mat_mul_point(m: &[[f32; 4]; 4], p: Vec3) -> [f32; 4] {
let v = [p.x, p.y, p.z, 1.0];
let mut out = [0.0f32; 4];
for row in 0..4 {
let mut sum = 0.0;
for k in 0..4 {
sum += m[k][row] * v[k];
}
out[row] = sum;
}
out
}
fn mat_unproject(inv: &[[f32; 4]; 4], ndc_x: f32, ndc_y: f32, z: f32) -> Vec3 {
let clip = [ndc_x, ndc_y, z, 1.0];
let mut out = [0.0f32; 4];
for row in 0..4 {
let mut sum = 0.0;
for k in 0..4 {
sum += inv[k][row] * clip[k];
}
out[row] = sum;
}
let w = if out[3].abs() < 1e-9 { 1.0 } else { out[3] };
Vec3::new(out[0] / w, out[1] / w, out[2] / w)
}
pub fn mat_inverse(m: &[[f32; 4]; 4]) -> [[f32; 4]; 4] {
let a = |r: usize, c: usize| m[c][r] as f64;
let mut inv = [[0.0f64; 4]; 4];
let m00 = a(0, 0); let m01 = a(0, 1); let m02 = a(0, 2); let m03 = a(0, 3);
let m10 = a(1, 0); let m11 = a(1, 1); let m12 = a(1, 2); let m13 = a(1, 3);
let m20 = a(2, 0); let m21 = a(2, 1); let m22 = a(2, 2); let m23 = a(2, 3);
let m30 = a(3, 0); let m31 = a(3, 1); let m32 = a(3, 2); let m33 = a(3, 3);
let b00 = m00 * m11 - m01 * m10;
let b01 = m00 * m12 - m02 * m10;
let b02 = m00 * m13 - m03 * m10;
let b03 = m01 * m12 - m02 * m11;
let b04 = m01 * m13 - m03 * m11;
let b05 = m02 * m13 - m03 * m12;
let b06 = m20 * m31 - m21 * m30;
let b07 = m20 * m32 - m22 * m30;
let b08 = m20 * m33 - m23 * m30;
let b09 = m21 * m32 - m22 * m31;
let b10 = m21 * m33 - m23 * m31;
let b11 = m22 * m33 - m23 * m32;
let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
if det.abs() < 1e-18 {
return identity();
}
let inv_det = 1.0 / det;
let r = [
[
(m11 * b11 - m12 * b10 + m13 * b09) * inv_det,
(m02 * b10 - m01 * b11 - m03 * b09) * inv_det,
(m31 * b05 - m32 * b04 + m33 * b03) * inv_det,
(m22 * b04 - m21 * b05 - m23 * b03) * inv_det,
],
[
(m12 * b08 - m10 * b11 - m13 * b07) * inv_det,
(m00 * b11 - m02 * b08 + m03 * b07) * inv_det,
(m32 * b02 - m30 * b05 - m33 * b01) * inv_det,
(m20 * b05 - m22 * b02 + m23 * b01) * inv_det,
],
[
(m10 * b10 - m11 * b08 + m13 * b06) * inv_det,
(m01 * b08 - m00 * b10 - m03 * b06) * inv_det,
(m30 * b04 - m31 * b02 + m33 * b00) * inv_det,
(m21 * b02 - m20 * b04 - m23 * b00) * inv_det,
],
[
(m11 * b07 - m10 * b09 - m12 * b06) * inv_det,
(m00 * b09 - m01 * b07 + m02 * b06) * inv_det,
(m31 * b01 - m30 * b03 - m32 * b00) * inv_det,
(m20 * b03 - m21 * b01 + m22 * b00) * inv_det,
],
];
for row in 0..4 {
for col in 0..4 {
inv[col][row] = r[row][col];
}
}
let mut out = [[0.0f32; 4]; 4];
for c in 0..4 {
for rr in 0..4 {
out[c][rr] = inv[c][rr] as f32;
}
}
out
}
fn identity() -> [[f32; 4]; 4] {
[
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
}
#[cfg(test)]
mod tests {
use super::*;
fn perspective_cam() -> GizmoCamera {
let vp = crate::raster::test_view_proj([0.0, 0.0, 10.0], [0.0, 0.0, 0.0], 100.0, 100.0);
GizmoCamera {
view_proj: vp,
eye: Vec3::new(0.0, 0.0, 10.0),
forward: Vec3::new(0.0, 0.0, -1.0),
up: Vec3::Y,
viewport: [100.0, 100.0],
orthographic: true,
}
}
#[test]
fn origin_projects_to_viewport_center() {
let cam = perspective_cam();
let s = cam.world_to_screen(Vec3::new(0.0, 0.0, 0.0)).unwrap();
assert!((s[0] - 50.0).abs() < 0.5, "x {}", s[0]);
assert!((s[1] - 50.0).abs() < 0.5, "y {}", s[1]);
}
#[test]
fn ray_through_center_points_along_view() {
let cam = perspective_cam();
let ray = cam.ray_from_screen(50.0, 50.0);
assert!(ray.dir.z < -0.9, "ray should point toward -Z: {:?}", ray.dir);
}
#[test]
fn world_per_pixel_is_positive() {
let cam = perspective_cam();
assert!(cam.world_per_pixel(Vec3::new(0.0, 0.0, 0.0)) > 0.0);
}
}