use crate::{Gizmo, GizmoCamera, HandleId, Overlay, Vec3};
pub const AXIS_X_COLOR: [f32; 4] = [1.0, 0.302, 0.302, 1.0];
pub const AXIS_Y_COLOR: [f32; 4] = [0.302, 1.0, 0.302, 1.0];
pub const AXIS_Z_COLOR: [f32; 4] = [0.302, 0.490, 1.0, 1.0];
pub const PLANE_COLOR: [f32; 4] = [0.180, 1.0, 0.180, 1.0];
pub const DEFAULT_PLANE_SCREEN_PX: f32 = 140.0;
pub const DEFAULT_FRAME_PX: f32 = 70.0;
pub const PICK_PX: f32 = 6.0;
pub fn datum_plane(
origin: Vec3,
x_axis: Vec3,
y_axis: Vec3,
size: f32,
color: [f32; 4],
) -> Overlay {
datum_plane_half(origin, x_axis, y_axis, size * 0.5, color)
}
pub fn datum_plane_screen(
origin: Vec3,
x_axis: Vec3,
y_axis: Vec3,
screen_px: f32,
color: [f32; 4],
camera: &GizmoCamera,
) -> Overlay {
let half = camera.world_per_pixel(origin) * screen_px * 0.5;
datum_plane_half(origin, x_axis, y_axis, half, color)
}
fn datum_plane_half(
origin: Vec3,
x_axis: Vec3,
y_axis: Vec3,
half: f32,
color: [f32; 4],
) -> Overlay {
let mut ov = Overlay::new();
let hx = x_axis.scale(half);
let hy = y_axis.scale(half);
let c00 = origin.sub(hx).sub(hy);
let c10 = origin.add(hx).sub(hy);
let c11 = origin.add(hx).add(hy);
let c01 = origin.sub(hx).add(hy);
let fill = with_alpha(scale_rgb(color, 0.55), (color[3] * 0.28).min(0.28));
ov.tri(c00, c10, c11, fill);
ov.tri(c00, c11, c01, fill);
let border = [color[0], color[1], color[2], 1.0];
ov.line(c00, c10, border);
ov.line(c10, c11, border);
ov.line(c11, c01, border);
ov.line(c01, c00, border);
let m = half * 0.28;
ov.line(c00, c00.add(x_axis.scale(m)), border);
ov.line(c00, c00.add(y_axis.scale(m)), border);
ov
}
pub fn datum_axis(point: Vec3, direction: Vec3, length: f32, color: [f32; 4]) -> Overlay {
let mut ov = Overlay::new();
let d = direction.normalized();
let end = point.add(d.scale(length));
ov.line(point, end, color); let head = length * 0.14;
push_cone(&mut ov, end, d, head, head * 0.45, color, 12);
ov
}
pub fn datum_frame(
origin: Vec3,
x: Vec3,
y: Vec3,
z: Vec3,
screen_len_px: f32,
camera: &GizmoCamera,
) -> Overlay {
let mut ov = Overlay::new();
let len = camera.world_per_pixel(origin) * screen_len_px;
let axes = [
(x.normalized(), AXIS_X_COLOR),
(y.normalized(), AXIS_Y_COLOR),
(z.normalized(), AXIS_Z_COLOR),
];
for (dir, col) in axes {
ov.line(origin, origin.add(dir.scale(len)), col);
}
let head = len * 0.16;
for (dir, col) in axes {
push_cone(&mut ov, origin.add(dir.scale(len)), dir, head, head * 0.5, col, 10);
}
let marker = (len * 0.08).max(camera.world_per_pixel(origin) * 3.0);
push_octahedron(&mut ov, origin, marker, [0.88, 0.88, 0.92, 1.0]);
ov
}
pub fn world_axes(camera: &GizmoCamera, length_px: f32) -> Overlay {
datum_frame(Vec3::ZERO, Vec3::X, Vec3::Y, Vec3::Z, length_px, camera)
}
#[derive(Debug, Clone, Copy)]
pub struct DatumPlane {
pub origin: Vec3,
pub x_axis: Vec3,
pub y_axis: Vec3,
pub size: Option<f32>,
pub color: [f32; 4],
pub handle: HandleId,
}
impl DatumPlane {
pub fn normal(&self) -> Vec3 {
self.x_axis.cross(self.y_axis).normalized()
}
fn half(&self, camera: &GizmoCamera) -> f32 {
match self.size {
Some(s) => s * 0.5,
None => camera.world_per_pixel(self.origin) * DEFAULT_PLANE_SCREEN_PX * 0.5,
}
}
pub fn hit_point(&self, camera: &GizmoCamera, screen: [f32; 2]) -> Option<Vec3> {
let ray = camera.ray_from_screen(screen[0], screen[1]);
let t = ray.intersect_plane(self.origin, self.normal())?;
if t < 0.0 {
return None;
}
let p = ray.at(t);
let rel = p.sub(self.origin);
let u = rel.dot(self.x_axis.normalized());
let v = rel.dot(self.y_axis.normalized());
let half = self.half(camera);
(u.abs() <= half && v.abs() <= half).then_some(p)
}
}
impl Gizmo for DatumPlane {
fn geometry(
&self,
camera: &GizmoCamera,
hovered: Option<HandleId>,
active: Option<HandleId>,
) -> Overlay {
let hot = hovered == Some(self.handle) || active == Some(self.handle);
let color = if hot { brighten(self.color) } else { self.color };
match self.size {
Some(s) => datum_plane(self.origin, self.x_axis, self.y_axis, s, color),
None => datum_plane_screen(
self.origin,
self.x_axis,
self.y_axis,
DEFAULT_PLANE_SCREEN_PX,
color,
camera,
),
}
}
fn hit(&self, camera: &GizmoCamera, screen: [f32; 2]) -> Option<HandleId> {
self.hit_point(camera, screen).map(|_| self.handle)
}
}
#[derive(Debug, Clone, Copy)]
pub struct DatumAxis {
pub point: Vec3,
pub direction: Vec3,
pub length: f32,
pub color: [f32; 4],
pub handle: HandleId,
}
impl DatumAxis {
fn end(&self) -> Vec3 {
self.point.add(self.direction.normalized().scale(self.length))
}
}
impl Gizmo for DatumAxis {
fn geometry(
&self,
_camera: &GizmoCamera,
hovered: Option<HandleId>,
active: Option<HandleId>,
) -> Overlay {
let hot = hovered == Some(self.handle) || active == Some(self.handle);
let color = if hot { brighten(self.color) } else { self.color };
datum_axis(self.point, self.direction, self.length, color)
}
fn hit(&self, camera: &GizmoCamera, screen: [f32; 2]) -> Option<HandleId> {
let ray = camera.ray_from_screen(screen[0], screen[1]);
let end = self.end();
let world_dist = ray.distance_to_segment(self.point, end);
let mid = self.point.lerp(end, 0.5);
let px = world_dist / camera.world_per_pixel(mid);
if px <= PICK_PX {
Some(self.handle)
} else {
None
}
}
}
fn push_cone(
ov: &mut Overlay,
apex: Vec3,
dir: Vec3,
len: f32,
radius: f32,
color: [f32; 4],
segments: usize,
) {
let base = apex.sub(dir.scale(len));
let p1 = dir.any_perp();
let p2 = dir.cross(p1).normalized();
let seg = segments.max(3);
for i in 0..seg {
let a0 = (i as f32) / (seg as f32) * std::f32::consts::TAU;
let a1 = ((i + 1) as f32) / (seg as f32) * std::f32::consts::TAU;
let r0 = base.add(p1.scale(a0.cos() * radius)).add(p2.scale(a0.sin() * radius));
let r1 = base.add(p1.scale(a1.cos() * radius)).add(p2.scale(a1.sin() * radius));
ov.tri(apex, r0, r1, color); ov.tri(base, r1, r0, color); }
}
fn push_octahedron(ov: &mut Overlay, center: Vec3, half: f32, color: [f32; 4]) {
let px = center.add(Vec3::X.scale(half));
let nx = center.sub(Vec3::X.scale(half));
let py = center.add(Vec3::Y.scale(half));
let ny = center.sub(Vec3::Y.scale(half));
let pz = center.add(Vec3::Z.scale(half));
let nz = center.sub(Vec3::Z.scale(half));
let faces = [
(px, py, pz),
(py, nx, pz),
(nx, ny, pz),
(ny, px, pz),
(py, px, nz),
(nx, py, nz),
(ny, nx, nz),
(px, ny, nz),
];
for (a, b, c) in faces {
ov.tri(a, b, c, color);
}
}
fn scale_rgb(c: [f32; 4], s: f32) -> [f32; 4] {
[c[0] * s, c[1] * s, c[2] * s, c[3]]
}
fn with_alpha(c: [f32; 4], a: f32) -> [f32; 4] {
[c[0], c[1], c[2], a]
}
fn brighten(c: [f32; 4]) -> [f32; 4] {
[
(c[0] * 1.35 + 0.1).min(1.0),
(c[1] * 1.35 + 0.1).min(1.0),
(c[2] * 1.35 + 0.1).min(1.0),
c[3],
]
}