use super::{Pose, Vec3};
use embedded_graphics::prelude::Point;
use super::Rgb888;
#[derive(Clone, Copy, Debug)]
pub struct Stroke {
pub(crate) start: Pose,
pub(crate) end: Pose,
pub(crate) color: Rgb888,
pub(crate) width: f32,
}
impl Stroke {
#[must_use]
pub const fn start(self) -> Pose {
self.start
}
#[must_use]
pub const fn end(self) -> Pose {
self.end
}
#[must_use]
pub const fn color(self) -> Rgb888 {
self.color
}
#[must_use]
pub const fn width(self) -> f32 {
self.width
}
}
#[derive(Clone, Copy, Debug)]
pub struct Disk {
pub(crate) pose: Pose,
pub(crate) radius: f32,
pub(crate) color: Rgb888,
}
impl Disk {
#[must_use]
pub const fn pose(self) -> Pose {
self.pose
}
#[must_use]
pub const fn radius(self) -> f32 {
self.radius
}
#[must_use]
pub const fn color(self) -> Rgb888 {
self.color
}
}
#[derive(Clone, Copy, Debug)]
pub struct Sphere {
pub(crate) pose: Pose,
pub(crate) radius: f32,
pub(crate) color: Rgb888,
}
impl Sphere {
#[must_use]
pub const fn pose(self) -> Pose {
self.pose
}
#[must_use]
pub const fn radius(self) -> f32 {
self.radius
}
#[must_use]
pub const fn color(self) -> Rgb888 {
self.color
}
}
#[derive(Clone, Copy, Debug)]
pub enum Item3d {
Stroke(Stroke),
Disk(Disk),
Sphere(Sphere),
}
impl Item3d {
#[must_use]
pub fn project(self, projection: &Projection) -> device_envoy_core::cyd::display::DrawItem {
match self {
Self::Stroke(stroke) => device_envoy_core::cyd::display::DrawItem::Stroke {
start: stroke.start().project(projection),
end: stroke.end().project(projection),
color: stroke.color(),
pixel_width: projection.project_width(stroke.width()),
},
Self::Disk(disk) => {
let orientation = disk.pose().orientation();
device_envoy_core::cyd::display::DrawItem::Ellipse {
center: disk.pose().project(projection),
axis_a: projection.project_dir(
disk.pose(),
orientation.forward(),
disk.radius(),
),
axis_b: projection.project_dir(disk.pose(), orientation.left(), disk.radius()),
color: disk.color(),
}
}
Self::Sphere(sphere) => device_envoy_core::cyd::display::DrawItem::Circle {
center: sphere.pose().project(projection),
pixel_radius: projection.project_radius(sphere.pose(), sphere.radius()),
color: sphere.color(),
},
}
}
}
pub struct Projection {
pub(crate) rotation: super::Mat3,
pub(crate) target_origin: Point,
pub(crate) scale: f32,
pub(crate) focal: Option<f32>,
}
const NEG_X_BASIS: super::Mat3 = super::Mat3([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]);
const NEG_Z_BASIS: super::Mat3 = super::Mat3([[0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]]);
impl Projection {
pub const fn front_orthographic(target_origin: Point, scale: f32) -> Self {
Self {
rotation: NEG_X_BASIS,
target_origin,
scale,
focal: None,
}
}
pub const fn top_orthographic(target_origin: Point, scale: f32) -> Self {
Self {
rotation: NEG_Z_BASIS,
target_origin,
scale,
focal: None,
}
}
pub const fn front_perspective(target_origin: Point, scale: f32, focal: f32) -> Self {
Self {
rotation: NEG_X_BASIS,
target_origin,
scale,
focal: Some(focal),
}
}
pub(crate) fn world_to_camera(&self, vector: Vec3) -> [f32; 3] {
let rotation = &self.rotation;
[
rotation[0][0] * vector[0] + rotation[0][1] * vector[1] + rotation[0][2] * vector[2],
rotation[1][0] * vector[0] + rotation[1][1] * vector[1] + rotation[1][2] * vector[2],
rotation[2][0] * vector[0] + rotation[2][1] * vector[1] + rotation[2][2] * vector[2],
]
}
pub(crate) fn depth_factor(&self, depth: f32) -> f32 {
match self.focal {
None => 1.0,
Some(focal) => focal / (focal + depth).max(focal * 0.05),
}
}
pub fn project_dir(&self, pose: Pose, world_dir: Vec3, radius: f32) -> (f32, f32) {
let factor = self.depth_factor(self.world_to_camera(pose.position())[0]);
let direction = self.world_to_camera(world_dir);
let scaled_radius = radius * self.scale * factor;
(-direction[1] * scaled_radius, -direction[2] * scaled_radius)
}
pub fn project_radius(&self, pose: Pose, radius: f32) -> f32 {
radius * self.scale * self.depth_factor(self.world_to_camera(pose.position())[0])
}
pub fn project_width(&self, width: f32) -> f32 {
(width * self.scale).max(1.0)
}
}