//! Synthesized nonprint helper glyphs that are *not* CAD entities. //! //! Some drawing decorations are derived from document objects/tables rather //! than from an entity in model space, so they never ride the per-entity //! resident tessellation. Today that is the geographic-location "daisy" marker //! (from the `GEODATA` object). These are appended to the Model-space resident //! wire set by [`Scene::append_scene_markers`]. //! //! Coordinates are absolute f64 (drawings can sit at UTM-scale origins), so the //! wires are built with [`WireModel::solid_f64`], which fills the double-single //! residual buffer and keeps the glyph precise and jitter-free. use std::f64::consts::TAU; use acadrust::objects::ObjectType; use crate::scene::model::wire_model::WireModel; use crate::scene::Scene; /// World-space radius of a synthesized marker glyph. const MARKER_R: f64 = 3.0; const NAN: [f64; 3] = [f64::NAN, f64::NAN, f64::NAN]; fn push_ring(out: &mut Vec<[f64; 3]>, c: [f64; 3], r: f64, n: usize) { if !out.is_empty() { out.push(NAN); } for i in 0..=n { let a = i as f64 * TAU / n as f64; out.push([c[0] + a.cos() * r, c[1] + a.sin() * r, c[2]]); } } fn push_seg(out: &mut Vec<[f64; 3]>, a: [f64; 3], b: [f64; 3]) { if !out.is_empty() { out.push(NAN); } out.push(a); out.push(b); } // ── vector helpers for oriented glyphs ───────────────────────────────────── fn v_sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] { [a[0] - b[0], a[1] - b[1], a[2] - b[2]] } fn v_cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] { [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], ] } fn v_norm(a: [f64; 3]) -> [f64; 3] { let l = (a[0] * a[0] + a[1] * a[1] + a[2] * a[2]).sqrt(); if l < 1e-12 { [0.0, 0.0, 0.0] } else { [a[0] / l, a[1] / l, a[2] / l] } } /// Two unit axes spanning the plane perpendicular to `a`. fn perp_basis(a: [f64; 3]) -> ([f64; 3], [f64; 3]) { let up = if a[2].abs() > 0.9 { [1.0, 0.0, 0.0] } else { [0.0, 0.0, 1.0] }; let u = v_norm(v_cross(up, a)); let v = v_cross(a, u); (u, v) } /// Camera display glyph at `cam_pos`, aimed along `fwd` (unit, toward the /// subject): an oriented body box with a lens cone poking out the front. fn camera_glyph(cam_pos: [f64; 3], fwd: [f64; 3], r: f64) -> Vec<[f64; 3]> { let (u, v) = perp_basis(fwd); // Point in the camera frame: `along` runs on the aim axis (front is +). let f = |along: f64, su: f64, sv: f64| -> [f64; 3] { [ cam_pos[0] + fwd[0] * along + u[0] * su + v[0] * sv, cam_pos[1] + fwd[1] * along + u[1] * su + v[1] * sv, cam_pos[2] + fwd[2] * along + u[2] * su + v[2] * sv, ] }; let mut out: Vec<[f64; 3]> = Vec::new(); // Body box: behind the lens plane (along −1.3r..−0.3r). let (bw, bh) = (0.6 * r, 0.45 * r); let (zb, zf) = (-1.3 * r, -0.3 * r); let corner = |along: f64, sx: f64, sy: f64| f(along, sx * bw, sy * bh); let back = [ corner(zb, -1.0, -1.0), corner(zb, 1.0, -1.0), corner(zb, 1.0, 1.0), corner(zb, -1.0, 1.0), ]; let front = [ corner(zf, -1.0, -1.0), corner(zf, 1.0, -1.0), corner(zf, 1.0, 1.0), corner(zf, -1.0, 1.0), ]; for k in 0..4 { let n = (k + 1) % 4; push_seg(&mut out, back[k], back[n]); push_seg(&mut out, front[k], front[n]); push_seg(&mut out, back[k], front[k]); } // Lens cone: apex at the body front, rim ring in front of the camera. let apex = f(zf, 0.0, 0.0); let rim_along = 0.5 * r; let rim_r = 0.35 * r; let n = 16; if !out.is_empty() { out.push(NAN); } for i in 0..=n { let a = i as f64 * TAU / n as f64; out.push(f(rim_along, a.cos() * rim_r, a.sin() * rim_r)); } for k in 0..4 { let a = k as f64 * TAU / 4.0; push_seg( &mut out, apex, f(rim_along, a.cos() * rim_r, a.sin() * rim_r), ); } out } /// Build the geographic-location "daisy": concentric rings plus radial petals, /// with a longer spoke marking the drawing's north direction. fn geo_daisy(center: [f64; 3], north: [f64; 2], r: f64) -> Vec<[f64; 3]> { let mut out: Vec<[f64; 3]> = Vec::new(); push_ring(&mut out, center, r, 24); push_ring(&mut out, center, r * 0.45, 16); // 12 radial petals from the inner ring to the rim. for k in 0..12 { let a = k as f64 * TAU / 12.0; let (ca, sa) = (a.cos(), a.sin()); push_seg( &mut out, [ center[0] + ca * r * 0.45, center[1] + sa * r * 0.45, center[2], ], [center[0] + ca * r, center[1] + sa * r, center[2]], ); } // North spoke — normalized north direction, defaulting to +Y. let nlen = (north[0] * north[0] + north[1] * north[1]).sqrt(); let (nx, ny) = if nlen < 1e-9 { (0.0, 1.0) } else { (north[0] / nlen, north[1] / nlen) }; push_seg( &mut out, center, [ center[0] + nx * r * 1.6, center[1] + ny * r * 1.6, center[2], ], ); out } impl Scene { /// Append synthesized nonprint markers to a freshly built Model-space /// resident wire set. No-op for any block other than model space. pub(super) fn append_scene_markers(&self, wires: &mut Vec, _bg: [f32; 4]) { // Geographic-location daisy (one per GEODATA object; normally one). for obj in self.document.objects.values() { if let ObjectType::GeoData(g) = obj { let c = [g.design_point.x, g.design_point.y, g.design_point.z]; let north = [g.north_direction.x, g.north_direction.y]; let pts = geo_daisy(c, north, MARKER_R); // A muted geo-marker colour (nonprint helper); the reader keeps // no display colour for GEODATA, so use a fixed grey-cyan. let w = WireModel::solid_f64( format!("geomarker-{}", u64::from(g.handle)), pts, [0.55, 0.75, 0.85, 1.0], false, ); wires.push(w); } } // Camera glyphs — one per perspective VIEW (created by the CAMERA // command; VIEWMODE perspective bit). The camera sits at target + // direction and looks back toward the target. for view in self.document.views.iter() { if !view.perspective { continue; } let target = [view.target.x, view.target.y, view.target.z]; let dir = [view.direction.x, view.direction.y, view.direction.z]; let cam_pos = [target[0] + dir[0], target[1] + dir[1], target[2] + dir[2]]; // Aim from the camera back toward the subject (= −direction). let fwd = v_norm(v_sub(target, cam_pos)); let fwd = if fwd == [0.0, 0.0, 0.0] { [0.0, 0.0, -1.0] } else { fwd }; let pts = camera_glyph(cam_pos, fwd, MARKER_R); let w = WireModel::solid_f64( format!("camera-{}", u64::from(view.handle)), pts, [0.60, 0.70, 0.90, 1.0], false, ); wires.push(w); } } }