use crate::DrawPrimitive;
use crate::bounds::Aabb;
use crate::camera::Camera;
use embedded_graphics_core::pixelcolor::Rgb565;
use nalgebra::{Matrix4, Point2, Point3, Vector3};
#[allow(unused_imports)]
use nalgebra::ComplexField;
const AABB_EDGES: [(usize, usize); 12] = [
(0, 1),
(1, 2),
(2, 3),
(3, 0),
(4, 5),
(5, 6),
(6, 7),
(7, 4),
(0, 4),
(1, 5),
(2, 6),
(3, 7),
];
pub fn emit_aabb_wireframe<F>(
aabb: &Aabb,
model_matrix: &Matrix4<f32>,
vp_matrix: &Matrix4<f32>,
color: Rgb565,
mut emit: F,
) where
F: FnMut(DrawPrimitive),
{
let mvp = vp_matrix * model_matrix;
let corners = aabb.corners();
let mut screen: [Option<Point2<i32>>; 8] = [None; 8];
for (i, c) in corners.iter().enumerate() {
let clip = mvp * nalgebra::Vector4::new(c.x, c.y, c.z, 1.0);
if clip.w <= 0.0 {
continue;
}
let inv_w = 1.0 / clip.w;
screen[i] = Some(Point2::new(
(clip.x * inv_w) as i32,
(clip.y * inv_w) as i32,
));
}
for &(a, b) in &AABB_EDGES {
if let (Some(pa), Some(pb)) = (screen[a], screen[b]) {
emit(DrawPrimitive::Line([pa, pb], color));
}
}
}
pub fn emit_aabb_wireframe_projected<F>(
aabb: &Aabb,
model_matrix: &Matrix4<f32>,
project: impl Fn([f32; 3]) -> Option<Point3<i32>>,
color: Rgb565,
mut emit: F,
) where
F: FnMut(DrawPrimitive),
{
let corners = aabb.corners();
let mut screen: [Option<Point2<i32>>; 8] = [None; 8];
for (i, c) in corners.iter().enumerate() {
let world = model_matrix.transform_point(&Point3::new(c.x, c.y, c.z));
screen[i] = project([world.x, world.y, world.z]).map(|p| p.xy());
}
for &(a, b) in &AABB_EDGES {
if let (Some(pa), Some(pb)) = (screen[a], screen[b]) {
emit(DrawPrimitive::Line([pa, pb], color));
}
}
}
pub fn emit_frustum_wireframe<F>(
camera: &Camera,
project: impl Fn([f32; 3]) -> Option<Point3<i32>>,
color: Rgb565,
mut emit: F,
) where
F: FnMut(DrawPrimitive),
{
let aspect = camera.get_aspect_ratio();
let view = camera.view_matrix;
let r = view.fixed_view::<3, 3>(0, 0);
let right = Vector3::new(r[(0, 0)], r[(0, 1)], r[(0, 2)]);
let up = Vector3::new(r[(1, 0)], r[(1, 1)], r[(1, 2)]);
let forward = -Vector3::new(r[(2, 0)], r[(2, 1)], r[(2, 2)]);
let half_v = (camera.fovy() * 0.5).tan();
let half_h = half_v * aspect;
let eye = camera.position.coords;
let mut corners = [Vector3::zeros(); 8];
for (i, &z) in [camera.near, camera.far].iter().enumerate() {
let center = eye + forward * z;
let h = half_h * z;
let v = half_v * z;
let base = i * 4;
corners[base] = center - right * h - up * v;
corners[base + 1] = center + right * h - up * v;
corners[base + 2] = center + right * h + up * v;
corners[base + 3] = center - right * h + up * v;
}
let mut screen: [Option<Point2<i32>>; 8] = [None; 8];
for (i, c) in corners.iter().enumerate() {
screen[i] = project([c.x, c.y, c.z]).map(|p| p.xy());
}
for &(a, b) in &AABB_EDGES {
if let (Some(pa), Some(pb)) = (screen[a], screen[b]) {
emit(DrawPrimitive::Line([pa, pb], color));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use embedded_graphics_core::pixelcolor::WebColors;
#[test]
fn aabb_emits_up_to_twelve_lines() {
let aabb = Aabb::from_min_max(Vector3::new(-1.0, -1.0, -1.0), Vector3::new(1.0, 1.0, 1.0));
let mut count = 0;
emit_aabb_wireframe_projected(
&aabb,
&Matrix4::identity(),
|p| Some(Point3::new(p[0] as i32, p[1] as i32, 1)),
Rgb565::CSS_LIME,
|_| count += 1,
);
assert_eq!(count, 12);
}
}