Skip to main content

geop_core_editor/view/
picture.rs

1//! [`picture`]: a scene drawn into a PNG as the viewer draws it, and the
2//! [`Camera`] it is seen by, which also says where the pointer of a pixel
3//! goes ([`Camera::pointer`]): what is clicked in a picture is what the
4//! editor's hit tests pick there, as for a click in a viewer.
5//!
6//! A picture is drawn, not reasoned about: in plain numbers, by a
7//! z-buffered rasterizer, supersampled. It draws what the web viewer draws
8//! (`web/src/partScene.ts`, `placed3d.ts`, `datums3d.ts`, `visuals3d.ts`),
9//! in its colours: faces shaded by a light that moves with the eye, edges
10//! over them where they are seen, sketches and datums where they are not
11//! hidden, what is lit over it all, then what the step being edited draws,
12//! and in a corner the world's axes, `x` red, `y` green, `z` blue.
13
14use std::collections::{BTreeMap, BTreeSet};
15
16use geop_core_math::{
17    geop_error::{GeopError, GeopResult},
18    plain::{self, P2, P3},
19    primitives::{DatumComponent, DatumKind, FrameAxis, Motion, Ray},
20    scalars::Scalar,
21    vector::Vector3,
22};
23use geop_core_program::placed::{INSTANCE_SEPARATOR, Located};
24
25use super::{FRAME, PartView, ViewDatum, lit::Lighting, lit::Lit};
26use crate::{
27    editing::{Pointer, Reach, Shape, Style, TRIAD, Visual},
28    editor::SceneState,
29};
30
31/// How far a pointer reaches, in pixels. What a picture draws at a
32/// constant size on screen — a frame datum, a handle, a triad — is laid
33/// out in reaches of this many pixels, as a viewer lays it out in its own,
34/// so it is hit where it is drawn.
35pub const REACH_PX: f64 = 4.0;
36
37/// Samples a pixel is drawn from, along each of its sides.
38const SUPERSAMPLE: usize = 2;
39/// The room a camera fitted to the scene leaves round it, of the
40/// picture's height or width, on each side.
41const MARGIN: f64 = 0.06;
42/// How far behind the faces drawn a line still shows, in pixels' worth of
43/// depth: an edge lies on its faces, which are drawn through the edge's
44/// samples by triangles of their own, and at a glancing look those part by
45/// several pixels' worth of depth within one pixel.
46const DEPTH_BIAS_PX: f64 = 3.0;
47/// Half an edge's width, in pixels.
48const EDGE_HALF_WIDTH: f64 = 0.7;
49/// Half a lit edge's width, in pixels.
50const LIT_HALF_WIDTH: f64 = 1.2;
51/// The light a face is never darker than, and what a face turned to the
52/// light adds to it.
53const AMBIENT: f32 = 0.4;
54const DIFFUSE: f32 = 0.6;
55
56/// The viewer's colours (see the module docs).
57const BACKGROUND: u32 = 0x1b1f26;
58const FACE: u32 = 0xb0b4b8;
59const EDGE: u32 = 0x30343b;
60const SKETCH: u32 = 0xffa040;
61const CONSTRUCTION: u32 = 0x808080;
62const ANNOTATION: u32 = 0x2a2a2a;
63const HIGHLIGHT: u32 = 0xffc94a;
64/// How far a lit face's colour goes towards [`HIGHLIGHT`], and how opaque
65/// it is drawn again over whatever stands in front of it.
66const LIT_TINT: f32 = 0.7;
67const LIT_FACE_OPACITY: f32 = 0.35;
68const DATUM: u32 = 0xb58cff;
69/// A frame's axes, `x`, `y`, `z`, and a point datum's, tinted so.
70const AXES: [u32; 3] = [0xff5555, 0x55dd55, 0x5599ff];
71const AXIS_TINTS: [u32; 3] = [0xff8080, 0x80e080, 0x80b0ff];
72/// How long a point datum's axes are, in reaches: 32 of a viewer's pixels.
73const POINT_TRIAD: f64 = 32.0 / 9.0;
74
75/// An orthographic camera: what a picture shows and where a pixel's
76/// pointer goes.
77#[derive(Clone, Debug, PartialEq)]
78pub struct Camera {
79    /// The direction the eye looks along: unit.
80    pub look: Vector3,
81    /// Up the picture: unit, square to `look`.
82    pub up: Vector3,
83    /// The world point at the picture's centre.
84    pub center: Vector3,
85    /// The world height the picture shows.
86    pub span: f64,
87    /// Its size in pixels.
88    pub width: u32,
89    pub height: u32,
90    /// How far before `center`, along `look`, a pointer's ray starts: in
91    /// front of everything drawn.
92    pub near: f64,
93}
94
95/// A camera's frame in plain numbers: right, up and into the picture, its
96/// centre, and the world length of a pixel.
97#[derive(Clone, Copy, Debug)]
98struct Frame {
99    right: P3,
100    up: P3,
101    look: P3,
102    center: P3,
103    per_px: f64,
104}
105
106impl Frame {
107    /// Where `p` is along right, up and into the picture, from its centre.
108    fn coordinates(&self, p: P3) -> P3 {
109        let d = plain::sub(p, self.center);
110        [
111            plain::dot(d, self.right),
112            plain::dot(d, self.up),
113            plain::dot(d, self.look),
114        ]
115    }
116
117    /// The direction `d` on screen, right and down, a pixel a unit of it.
118    fn on_screen(&self, d: P3) -> P2 {
119        [plain::dot(d, self.right), -plain::dot(d, self.up)]
120    }
121}
122
123/// The world's axis `k`.
124fn unit(k: usize) -> P3 {
125    std::array::from_fn(|i| f64::from(u8::from(i == k)))
126}
127
128/// `up` made square to the unit `look`: if it runs (nearly) along it, the
129/// world's axis `look` runs least along, the first of those it runs least
130/// along alike, made square to it instead.
131fn square_up(look: P3, up: P3) -> P3 {
132    let square = |up: P3| {
133        let up = plain::normalize(up)?;
134        let square = plain::sub(up, plain::scale(look, plain::dot(up, look)));
135        (plain::norm(square) > 1e-3).then(|| plain::normalize(square))?
136    };
137    square(up).unwrap_or_else(|| {
138        let least = (look.iter().enumerate()).fold((0, f64::INFINITY), |least, (k, x)| {
139            match x.abs() < least.1 {
140                true => (k, x.abs()),
141                false => least,
142            }
143        });
144        let axis = unit(least.0);
145        square(axis).unwrap_or(axis)
146    })
147}
148
149impl Camera {
150    /// Looking along `look` with `up` up — made square to `look`, or, if
151    /// it runs along `look`, the world's axis `look` runs least along —
152    /// framed to everything the scene draws, the part and every part
153    /// placed in it, with a margin; to the part's extent where it draws
154    /// nothing.
155    pub fn fitting(
156        scene: &SceneState,
157        look: Vector3,
158        up: Vector3,
159        width: u32,
160        height: u32,
161    ) -> GeopResult<Camera> {
162        if width == 0 || height == 0 {
163            return Err(GeopError::new(format!(
164                "a picture of {width} by {height} pixels shows nothing"
165            )));
166        }
167        let look = plain::normalize(look.to_f64_array()).ok_or_else(|| {
168            GeopError::new(format!("a camera looking along {look:?} looks nowhere"))
169        })?;
170        let up = square_up(look, up.to_f64_array());
171        let right = plain::cross(look, up);
172        let frame = Frame {
173            right,
174            up,
175            look,
176            center: [0.0; 3],
177            per_px: 1.0,
178        };
179        let mut low = [f64::INFINITY; 3];
180        let mut high = [f64::NEG_INFINITY; 3];
181        let hidden = hidden_of(scene);
182        for layer in &layers(scene, None, &hidden) {
183            layer.points(|p| {
184                let c = frame.coordinates(p);
185                for ((low, high), c) in low.iter_mut().zip(&mut high).zip(c) {
186                    *low = low.min(c);
187                    *high = high.max(c);
188                }
189            });
190        }
191        let extent = &scene.part.extent;
192        let size = extent.size.to_f64();
193        let aspect = f64::from(height) / f64::from(width);
194        let (middle, span) = match low[0] <= high[0] {
195            true => {
196                let middle = plain::scale(plain::add(low, high), 0.5);
197                let span = (high[1] - low[1]).max((high[0] - low[0]) * aspect);
198                (middle, span / (1.0 - 2.0 * MARGIN))
199            }
200            false => {
201                let c = frame.coordinates(extent.center.to_f64_array());
202                low[2] = c[2] - size / 2.0;
203                (c, size)
204            }
205        };
206        let span = match span > 0.0 && span.is_finite() {
207            true => span,
208            false => size,
209        };
210        let center = ([right, up, look].into_iter().zip(middle)).fold([0.0; 3], |c, (axis, x)| {
211            plain::add(c, plain::scale(axis, x))
212        });
213        let v = |p: P3| Vector3::from_array(p.map(Scalar::from_f64));
214        Ok(Camera {
215            look: v(look),
216            up: v(up),
217            center: v(center),
218            span,
219            width,
220            height,
221            near: middle[2] - low[2] + span,
222        })
223    }
224
225    fn frame(&self) -> Frame {
226        let look = self.look.to_f64_array();
227        let up = self.up.to_f64_array();
228        Frame {
229            right: plain::cross(look, up),
230            up,
231            look,
232            center: self.center.to_f64_array(),
233            per_px: self.span / f64::from(self.height),
234        }
235    }
236
237    /// How far the pointer reaches: [`REACH_PX`] pixels.
238    fn reach(&self) -> Reach {
239        Reach::Tube {
240            radius: Scalar::from_f64(REACH_PX * self.frame().per_px),
241        }
242    }
243
244    /// The pointer of the point `(x, y)` of the picture, in pixels from
245    /// its top left corner, `y` down — the middle of the pixel `(i, j)` is
246    /// `(i + 0.5, j + 0.5)`: a ray along `look` from in front of
247    /// everything drawn, reaching [`REACH_PX`] pixels, meaning the first
248    /// of what lies under it. What the editor's hit tests take.
249    pub fn pointer(&self, x: f64, y: f64) -> GeopResult<Pointer> {
250        let f = self.frame();
251        let across = (x - f64::from(self.width) / 2.0) * f.per_px;
252        let down = (y - f64::from(self.height) / 2.0) * f.per_px;
253        let at = plain::add(
254            f.center,
255            plain::sub(plain::scale(f.right, across), plain::scale(f.up, down)),
256        );
257        self.ray_through(at, &f)
258    }
259
260    /// The pointer through the world point `at`, along `look`, reaching as
261    /// [`Camera::pointer`]'s does.
262    pub fn through(&self, at: Vector3) -> GeopResult<Pointer> {
263        self.ray_through(at.to_f64_array(), &self.frame())
264    }
265
266    fn ray_through(&self, at: P3, f: &Frame) -> GeopResult<Pointer> {
267        let back = f.coordinates(at)[2] + self.near;
268        let origin = plain::sub(at, plain::scale(f.look, back));
269        let v = |p: P3| Vector3::from_array(p.map(Scalar::from_f64));
270        Ok(Pointer {
271            ray: Ray::try_new(v(origin), v(f.look))?,
272            reach: self.reach(),
273            cycle: 0,
274        })
275    }
276
277    /// Where the world point `at` is in the picture, in pixels from its top
278    /// left corner, `y` down, as [`Camera::pointer`] takes them.
279    pub fn pixel(&self, at: &Vector3) -> [f64; 2] {
280        let f = self.frame();
281        let c = f.coordinates(at.to_f64_array());
282        [
283            f64::from(self.width) / 2.0 + c[0] / f.per_px,
284            f64::from(self.height) / 2.0 - c[1] / f.per_px,
285        ]
286    }
287}
288
289/// The picture of `scene` seen by `camera`, as a PNG: its part, and every
290/// part placed in it, but what `hidden` names — none, if the scene does
291/// not say; what `lit` lights, lit; and `visuals`, what a step being
292/// edited draws, over it all.
293///
294/// A placed part's colour is that of the scene's `instances`, where it is
295/// one of them, else that of the part's own (see [`super::ViewInstance`]).
296pub fn picture(
297    scene: &SceneState,
298    lit: Option<&Lighting>,
299    visuals: &[Visual],
300    camera: &Camera,
301) -> GeopResult<Vec<u8>> {
302    let frame = camera.frame();
303    let (width, height) = (camera.width as usize, camera.height as usize);
304    let mut raster = Raster::new(width * SUPERSAMPLE, height * SUPERSAMPLE, frame);
305    let hidden = hidden_of(scene);
306    let layers = layers(scene, lit, &hidden);
307    let [own, ..] = layers.as_slice() else {
308        return Err(GeopError::new("a scene draws its part".to_string()));
309    };
310    for layer in &layers {
311        raster.faces(layer);
312    }
313    raster.datums(scene.part.as_ref(), own);
314    for layer in &layers {
315        raster.lines(layer);
316    }
317    for layer in &layers {
318        raster.lit(layer);
319    }
320    raster.frames(scene.part.as_ref(), own);
321    for visual in visuals {
322        raster.visual(visual);
323    }
324    raster.compass();
325    raster.png(width, height)
326}
327
328/// The part drawn or a part placed in it, where it is drawn, and how.
329struct Layer<'s> {
330    view: &'s PartView,
331    /// The placed part's name: empty for the part drawn.
332    instance: &'s str,
333    place: Place,
334    hidden: &'s BTreeSet<&'s str>,
335    lit: Option<&'s Lit>,
336    /// Its faces' colour.
337    face: [f32; 3],
338    /// Whether its instance is drawn in a colour of the user's: over the
339    /// colours of its faces too.
340    tinted: bool,
341}
342
343/// A rigid motion in plain numbers: where a placed part's origin and axes
344/// go.
345#[derive(Clone, Copy, Debug)]
346struct Place {
347    origin: P3,
348    axes: [P3; 3],
349}
350
351impl Place {
352    const IDENTITY: Place = Place {
353        origin: [0.0; 3],
354        axes: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
355    };
356
357    fn of(motion: &Motion) -> Self {
358        Self {
359            origin: motion.apply(&Vector3::zero()).to_f64_array(),
360            axes: [0, 1, 2].map(|k| motion.rotate(&Vector3::axis(k)).to_f64_array()),
361        }
362    }
363
364    fn rotate(&self, d: P3) -> P3 {
365        (self.axes.iter().zip(d)).fold([0.0; 3], |r, (axis, x)| {
366            plain::add(r, plain::scale(*axis, x))
367        })
368    }
369
370    fn apply(&self, p: &Vector3) -> P3 {
371        plain::add(self.origin, self.rotate(p.to_f64_array()))
372    }
373}
374
375/// The part drawn, then every part placed in it not hidden, nor placed in
376/// one hidden, each lit as `lit` says.
377fn layers<'s>(
378    scene: &'s SceneState,
379    lit: Option<&'s Lighting>,
380    hidden: &'s BTreeSet<&'s str>,
381) -> Vec<Layer<'s>> {
382    let part = scene.part.as_ref();
383    let own = |view: &PartView| parsed(view.color.as_ref()).unwrap_or(rgb(FACE));
384    let lit_of = |name: &str| lit.and_then(|l| l.get(name));
385    let colors: BTreeMap<&str, Option<&String>> = (scene.instances.iter())
386        .map(|i| (i.name.as_str(), i.color.as_ref()))
387        .collect();
388    let placed_hidden = |name: &str| {
389        let mut path = name;
390        loop {
391            if hidden.contains(path) {
392                return true;
393            }
394            match path.rsplit_once(INSTANCE_SEPARATOR) {
395                Some((outer, _)) => path = outer,
396                None => return false,
397            }
398        }
399    };
400    let mut layers = vec![Layer {
401        view: part,
402        instance: "",
403        place: Place::IDENTITY,
404        hidden,
405        lit: lit_of(""),
406        face: own(part),
407        tinted: false,
408    }];
409    for instance in part.instances.iter().filter(|i| !placed_hidden(&i.name)) {
410        let view = instance.view.as_ref();
411        let color = colors.get(instance.name.as_str()).copied();
412        let tint = parsed(color.unwrap_or(instance.color.as_ref()));
413        let face = own(view);
414        layers.push(Layer {
415            view,
416            instance: &instance.name,
417            place: Place::of(&instance.pose.motion()),
418            hidden,
419            lit: lit_of(&instance.name),
420            face: tint.map_or(face, |t| times(face, t)),
421            tinted: tint.is_some(),
422        });
423    }
424    layers
425}
426
427/// What `scene` says not to draw: nothing, where it does not say.
428fn hidden_of(scene: &SceneState) -> BTreeSet<&str> {
429    scene.hidden.iter().flatten().map(String::as_str).collect()
430}
431
432impl Layer<'_> {
433    /// The colour of its face `name`: its own, if it has one, unless a
434    /// colour its instance is drawn in is over them all.
435    fn color_of(&self, face: &super::ViewFace) -> [f32; 3] {
436        match self.tinted {
437            true => self.face,
438            false => parsed(self.view.color_of_face(face)).unwrap_or(self.face),
439        }
440    }
441
442    /// Whether what this layer names `name` is hidden — a solid, a face of
443    /// none, a sketch, a datum — by its key in the part drawn, as a pick
444    /// asks it.
445    fn hides(&self, name: &str) -> bool {
446        let key = Located::in_instance(self.instance, name).key();
447        self.hidden.contains(key.as_str())
448    }
449
450    fn shows_face(&self, solid: &Option<String>, name: &str) -> bool {
451        !self.hides(solid.as_deref().unwrap_or(name))
452    }
453
454    /// Whether a vertex or an edge of `solid`, bounding `faces`, is drawn:
455    /// its solid is not hidden, or, of none, not every face it bounds is.
456    fn shows_boundary(&self, solid: &Option<String>, faces: &[String]) -> bool {
457        match solid {
458            Some(solid) => !self.hides(solid),
459            None => faces.is_empty() || faces.iter().any(|f| !self.hides(f)),
460        }
461    }
462
463    fn shows_sketch(&self, name: &str) -> bool {
464        self.lit.is_some_and(|l| l.sketches.contains(name)) || !self.hides(name)
465    }
466
467    /// Every point drawn of its faces, edges, sketches and annotations,
468    /// where it is drawn: what a camera is fitted to.
469    fn points(&self, mut at: impl FnMut(P3)) {
470        let view = self.view;
471        for face in view
472            .faces
473            .iter()
474            .filter(|f| self.shows_face(&f.solid, &f.name))
475        {
476            for p in face.mesh.triangles.iter().flatten() {
477                at(self.place.apply(p));
478            }
479        }
480        for edge in &view.edges {
481            if self.shows_boundary(&edge.solid, &edge.faces) {
482                edge.polyline.iter().for_each(|p| at(self.place.apply(p)));
483            }
484        }
485        for a in &view.annotations {
486            a.polyline.iter().for_each(|p| at(self.place.apply(p)));
487        }
488        for sketch in view.sketches.iter().filter(|s| self.shows_sketch(&s.name)) {
489            for p in sketch.curves.iter().flat_map(|c| &c.polyline) {
490                at(self.place.apply(&sketch.plane.uv_to_xyz(p)));
491            }
492            for p in &sketch.points {
493                at(self.place.apply(&sketch.plane.uv_to_xyz(&p.at)));
494            }
495        }
496    }
497}
498
499/// The colour `hex`, `0xrrggbb`, as three numbers of one.
500fn rgb(hex: u32) -> [f32; 3] {
501    [16, 8, 0].map(|shift| ((hex >> shift) & 0xff) as f32 / 255.0)
502}
503
504/// The colour `#rrggbb`, if `color` is one.
505fn parsed(color: Option<&String>) -> Option<[f32; 3]> {
506    let hex = color?.strip_prefix('#')?;
507    (hex.len() == 6)
508        .then(|| u32::from_str_radix(hex, 16).ok())
509        .flatten()
510        .map(rgb)
511}
512
513/// `a` taken `t` of the way to `b`.
514fn mix(a: [f32; 3], b: [f32; 3], t: f32) -> [f32; 3] {
515    let mut mixed = a;
516    for (a, b) in mixed.iter_mut().zip(b) {
517        *a += (b - *a) * t;
518    }
519    mixed
520}
521
522/// The colour `a` seen through the colour `b`: a placed part's own, in the
523/// colour it is drawn in.
524fn times(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
525    let mut product = a;
526    for (a, b) in product.iter_mut().zip(b) {
527        *a *= b;
528    }
529    product
530}
531
532/// What is drawn at a sample: its colour, and how far into the picture its
533/// nearest face is.
534#[derive(Clone, Copy, Debug)]
535struct Sample {
536    color: [f32; 3],
537    depth: f64,
538}
539
540/// How a line is drawn: unbroken, or in dashes this long and gaps this
541/// wide, in pixels of the raster.
542#[derive(Clone, Copy, Debug)]
543enum Dashes {
544    Solid,
545    Dashed(f64, f64),
546}
547
548/// How a line or a disc is drawn: of what colour and opacity, how wide,
549/// and whether what is in front of it hides it.
550#[derive(Clone, Copy, Debug)]
551struct Pen {
552    color: [f32; 3],
553    alpha: f32,
554    /// Half its width, in pixels of the raster.
555    half: f64,
556    tested: bool,
557    dashes: Dashes,
558}
559
560/// How far into the picture the triangle `t`, projected, is at the
561/// barycentric coordinates `w`.
562fn depth(t: [P3; 3], w: [f64; 3]) -> f64 {
563    t.iter().zip(w).map(|(p, w)| p[2] * w).sum()
564}
565
566/// What is drawn, sample by sample, and how far into the picture each
567/// sample's nearest face is.
568struct Raster {
569    width: usize,
570    height: usize,
571    samples: Vec<Sample>,
572    frame: Frame,
573    /// Where the light comes from: above and to the left of the eye, so
574    /// that faces turned alike to the eye are not shaded alike.
575    light: P3,
576    /// How far behind the faces a line still shows (see
577    /// [`DEPTH_BIAS_PX`]).
578    bias: f64,
579}
580
581impl Raster {
582    fn new(width: usize, height: usize, frame: Frame) -> Self {
583        let mut frame = frame;
584        frame.per_px /= SUPERSAMPLE as f64;
585        let towards = plain::add(
586            plain::scale(frame.look, -1.0),
587            plain::add(plain::scale(frame.up, 0.6), plain::scale(frame.right, -0.4)),
588        );
589        Self {
590            width,
591            height,
592            samples: vec![
593                Sample {
594                    color: rgb(BACKGROUND),
595                    depth: f64::INFINITY,
596                };
597                width * height
598            ],
599            light: plain::normalize(towards).unwrap_or(frame.look),
600            bias: DEPTH_BIAS_PX * SUPERSAMPLE as f64 * frame.per_px,
601            frame,
602        }
603    }
604
605    /// Pixels of the picture, in pixels of the raster.
606    fn px(pixels: f64) -> f64 {
607        pixels * SUPERSAMPLE as f64
608    }
609
610    /// Where `p` is in the raster: across, down, and into the picture.
611    fn project(&self, p: P3) -> P3 {
612        let c = self.frame.coordinates(p);
613        [
614            self.width as f64 / 2.0 + c[0] / self.frame.per_px,
615            self.height as f64 / 2.0 - c[1] / self.frame.per_px,
616            c[2],
617        ]
618    }
619
620    /// The samples of the raster within the box `low` to `high`.
621    fn samples(&self, low: P2, high: P2) -> impl Iterator<Item = (usize, usize)> + use<> {
622        let clamp = |x: f64, n: usize| x.max(0.0).min(n as f64) as usize;
623        let (x0, x1) = (
624            clamp(low[0].floor(), self.width),
625            clamp(high[0].ceil(), self.width),
626        );
627        let (y0, y1) = (
628            clamp(low[1].floor(), self.height),
629            clamp(high[1].ceil(), self.height),
630        );
631        (y0..y1).flat_map(move |y| (x0..x1).map(move |x| (x, y)))
632    }
633
634    /// Draws `color` at the sample `i`, `alpha` opaque, if it is no further
635    /// into the picture than `depth` allows: than the face drawn there and
636    /// [`Raster::bias`], or anywhere, for `None`.
637    fn blend(&mut self, i: usize, color: [f32; 3], alpha: f32, depth: Option<f64>) {
638        let bias = self.bias;
639        if let Some(sample) = self.samples.get_mut(i)
640            && depth.is_none_or(|z| z <= sample.depth + bias)
641        {
642            sample.color = mix(sample.color, color, alpha.clamp(0.0, 1.0));
643        }
644    }
645
646    /// The triangle `t`, projected, sample by sample: its barycentric
647    /// coordinates at each sample it covers.
648    fn cover(&self, t: [P3; 3], mut at: impl FnMut(usize, [f64; 3])) {
649        let edge =
650            |a: P3, b: P3, p: P2| (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
651        let area = edge(t[0], t[1], [t[2][0], t[2][1]]);
652        if area == 0.0 || !area.is_finite() {
653            return;
654        }
655        let [low, high] = t.iter().fold(
656            [[f64::INFINITY; 2], [f64::NEG_INFINITY; 2]],
657            |[low, high], p| {
658                [
659                    [low[0].min(p[0]), low[1].min(p[1])],
660                    [high[0].max(p[0]), high[1].max(p[1])],
661                ]
662            },
663        );
664        for (x, y) in self.samples(low, high) {
665            let p = [x as f64 + 0.5, y as f64 + 0.5];
666            let w = [
667                edge(t[1], t[2], p) / area,
668                edge(t[2], t[0], p) / area,
669                edge(t[0], t[1], p) / area,
670            ];
671            if w.iter().all(|&w| w >= 0.0) {
672                at(y * self.width + x, w);
673            }
674        }
675    }
676
677    /// A face's triangle `t`, with the normals `n` at its corners, of the
678    /// colour `color`, shaded and kept where it is nearer than what was.
679    fn shaded(&mut self, t: [P3; 3], n: [P3; 3], color: [f32; 3]) {
680        let t = t.map(|p| self.project(p));
681        let mut drawn = Vec::new();
682        self.cover(t, |i, w| drawn.push((i, w)));
683        for (i, w) in drawn {
684            let z = depth(t, w);
685            let light = self.light;
686            let look = self.frame.look;
687            let Some(sample) = self.samples.get_mut(i).filter(|s| z < s.depth) else {
688                continue;
689            };
690            sample.depth = z;
691            let normal =
692                (n.iter().zip(w)).fold([0.0; 3], |s, (n, w)| plain::add(s, plain::scale(*n, w)));
693            let shade = match plain::normalize(normal) {
694                Some(normal) => {
695                    let facing = match plain::dot(normal, look) > 0.0 {
696                        true => plain::scale(normal, -1.0),
697                        false => normal,
698                    };
699                    AMBIENT + DIFFUSE * plain::dot(facing, light).max(0.0) as f32
700                }
701                None => AMBIENT + DIFFUSE / 2.0,
702            };
703            sample.color = color.map(|c| (c * shade).min(1.0));
704        }
705    }
706
707    /// A filled triangle `t`, in the raster's own coordinates, over what is
708    /// drawn — hidden by what is in front of it, if `tested`.
709    fn filled(&mut self, t: [P3; 3], color: [f32; 3], alpha: f32, tested: bool) {
710        let mut drawn = Vec::new();
711        self.cover(t, |i, w| drawn.push((i, w)));
712        for (i, w) in drawn {
713            self.blend(i, color, alpha, tested.then(|| depth(t, w)));
714        }
715    }
716
717    /// The segment from `a` to `b`, in the raster's own coordinates, drawn
718    /// by `pen`, `run` along its dashes where it starts; how far along
719    /// them it ends.
720    fn segment(&mut self, a: P3, b: P3, pen: &Pen, run: f64) -> f64 {
721        let d = [b[0] - a[0], b[1] - a[1]];
722        let length_sq = d[0] * d[0] + d[1] * d[1];
723        let reach = pen.half + 1.0;
724        let low = [a[0].min(b[0]) - reach, a[1].min(b[1]) - reach];
725        let high = [a[0].max(b[0]) + reach, a[1].max(b[1]) + reach];
726        let length = length_sq.sqrt();
727        if !(length.is_finite() && low[0].is_finite() && low[1].is_finite()) {
728            return run;
729        }
730        for (x, y) in self.samples(low, high) {
731            let p = [x as f64 + 0.5 - a[0], y as f64 + 0.5 - a[1]];
732            let s = match length_sq > 0.0 {
733                true => ((p[0] * d[0] + p[1] * d[1]) / length_sq).clamp(0.0, 1.0),
734                false => 0.0,
735            };
736            if let Dashes::Dashed(dash, gap) = pen.dashes
737                && (run + s * length) % (dash + gap) > dash
738            {
739                continue;
740            }
741            let off = [p[0] - s * d[0], p[1] - s * d[1]];
742            let distance = (off[0] * off[0] + off[1] * off[1]).sqrt();
743            let coverage = (pen.half + 0.5 - distance).clamp(0.0, 1.0) as f32;
744            if coverage <= 0.0 {
745                continue;
746            }
747            let i = y * self.width + x;
748            let z = pen.tested.then(|| a[2] + s * (b[2] - a[2]));
749            self.blend(i, pen.color, pen.alpha * coverage, z);
750        }
751        run + length
752    }
753
754    /// The polyline through the world points `points`, drawn by `pen`.
755    fn polyline(&mut self, points: impl IntoIterator<Item = P3>, pen: &Pen) {
756        let mut run = 0.0;
757        let mut last: Option<P3> = None;
758        for p in points {
759            let p = self.project(p);
760            if let Some(a) = last {
761                run = self.segment(a, p, pen, run);
762            }
763            last = Some(p);
764        }
765    }
766
767    /// A disc about `at`, in the raster's own coordinates, `pen.half`
768    /// across, drawn by `pen`.
769    fn disc(&mut self, at: P3, pen: &Pen) {
770        self.segment(at, at, pen, 0.0);
771    }
772
773    /// A cone's outline from `base` to `tip`, in the raster's own
774    /// coordinates, `radius` wide at its base, filled.
775    fn arrowhead(&mut self, base: P3, tip: P3, radius: f64, color: [f32; 3]) {
776        let d = [tip[0] - base[0], tip[1] - base[1]];
777        let Some(d) = plain::normalize(d) else {
778            return;
779        };
780        let side = [-d[1] * radius, d[0] * radius];
781        let corner = |sign: f64| [base[0] + sign * side[0], base[1] + sign * side[1], base[2]];
782        self.filled([corner(1.0), corner(-1.0), tip], color, 1.0, false);
783    }
784
785    /// The faces of `layer`, shaded, lit ones tinted towards the
786    /// highlight.
787    fn faces(&mut self, layer: &Layer<'_>) {
788        let highlight = rgb(HIGHLIGHT);
789        for face in &layer.view.faces {
790            if !layer.shows_face(&face.solid, &face.name) {
791                continue;
792            }
793            let lit = (layer.lit).is_some_and(|l| {
794                l.face(&face.name, face.solid.as_deref(), face.feature.as_deref())
795            });
796            let own = layer.color_of(face);
797            let color = match lit {
798                true => mix(own, highlight, LIT_TINT),
799                false => own,
800            };
801            for (t, n) in face.mesh.triangles.iter().zip(&face.mesh.normals) {
802                let t = t.map(|p| layer.place.apply(&p));
803                let n = n.map(|n| layer.place.rotate(n.to_f64_array()));
804                self.shaded(t, n, color);
805            }
806        }
807    }
808
809    /// The edges, threads and sketches of `layer`, where they are seen.
810    fn lines(&mut self, layer: &Layer<'_>) {
811        let pen = |color: u32| Pen {
812            color: rgb(color),
813            alpha: 1.0,
814            half: Self::px(EDGE_HALF_WIDTH),
815            tested: true,
816            dashes: Dashes::Solid,
817        };
818        let place = &layer.place;
819        for edge in &layer.view.edges {
820            if !edge.within_a_surface && layer.shows_boundary(&edge.solid, &edge.faces) {
821                self.polyline(edge.polyline.iter().map(|p| place.apply(p)), &pen(EDGE));
822            }
823        }
824        for a in &layer.view.annotations {
825            self.polyline(a.polyline.iter().map(|p| place.apply(p)), &pen(ANNOTATION));
826        }
827        for sketch in &layer.view.sketches {
828            if !layer.shows_sketch(&sketch.name) {
829                continue;
830            }
831            let lit = layer.lit.is_some_and(|l| l.sketches.contains(&sketch.name));
832            let in_plane = |p| place.apply(&sketch.plane.uv_to_xyz(p));
833            for curve in &sketch.curves {
834                let pen = match (lit, curve.construction) {
835                    (true, _) => Pen {
836                        tested: false,
837                        ..pen(HIGHLIGHT)
838                    },
839                    (false, true) => pen(CONSTRUCTION),
840                    (false, false) => pen(SKETCH),
841                };
842                self.polyline(curve.polyline.iter().map(in_plane), &pen);
843            }
844            for point in &sketch.points {
845                let at = self.project(in_plane(&point.at));
846                let color = if lit { HIGHLIGHT } else { SKETCH };
847                let pen = Pen {
848                    half: Self::px(2.5),
849                    tested: !lit,
850                    ..pen(color)
851                };
852                self.disc(at, &pen);
853            }
854        }
855    }
856
857    /// What is lit of `layer` again over everything, so that it is seen
858    /// wherever it is: its lit faces see-through — unless all of it is lit
859    /// — and its lit edges, vertices, curves and sketch points whole.
860    fn lit(&mut self, layer: &Layer<'_>) {
861        let Some(lit) = layer.lit else {
862            return;
863        };
864        let place = &layer.place;
865        let highlight = rgb(HIGHLIGHT);
866        let view = layer.view;
867        if !lit.whole {
868            for face in &view.faces {
869                if !lit.face(&face.name, face.solid.as_deref(), face.feature.as_deref()) {
870                    continue;
871                }
872                for t in &face.mesh.triangles {
873                    let t = t.map(|p| self.project(place.apply(&p)));
874                    self.filled(t, highlight, LIT_FACE_OPACITY, false);
875                }
876            }
877        }
878        let pen = Pen {
879            color: highlight,
880            alpha: 1.0,
881            half: Self::px(LIT_HALF_WIDTH),
882            tested: false,
883            dashes: Dashes::Solid,
884        };
885        let dot = Pen {
886            half: Self::px(4.5),
887            ..pen
888        };
889        for edge in view.edges.iter().filter(|e| lit.edges.contains(&e.name)) {
890            self.polyline(edge.polyline.iter().map(|p| place.apply(p)), &pen);
891        }
892        for vertex in view
893            .vertices
894            .iter()
895            .filter(|v| lit.vertices.contains(&v.name))
896        {
897            let at = self.project(place.apply(&vertex.at));
898            self.disc(at, &dot);
899        }
900        for sketch in &view.sketches {
901            let in_plane = |p| place.apply(&sketch.plane.uv_to_xyz(p));
902            let named = |id: &String| (sketch.name.clone(), id.clone());
903            for curve in sketch
904                .curves
905                .iter()
906                .filter(|c| lit.curves.contains(&named(&c.id)))
907            {
908                self.polyline(curve.polyline.iter().map(in_plane), &pen);
909            }
910            let points = sketch.points.iter();
911            for point in points.filter(|p| lit.sketch_points.contains(&named(&p.id))) {
912                let at = self.project(in_plane(&point.at));
913                self.disc(at, &dot);
914            }
915        }
916    }
917
918    /// The colour of the datum `d`'s part named `name` — the datum's own,
919    /// or a component's — as `layer` lights it.
920    fn datum_color(layer: &Layer<'_>, d: &ViewDatum, name: &str, own: u32) -> [f32; 3] {
921        let lit = layer
922            .lit
923            .is_some_and(|l| l.datums.contains(&d.name) || l.datums.contains(name));
924        rgb(if lit { HIGHLIGHT } else { own })
925    }
926
927    /// Whether `layer` draws the datum `d`: it is not hidden, or it or a
928    /// component of it is lit.
929    fn shows_datum(layer: &Layer<'_>, d: &ViewDatum) -> bool {
930        let lit = layer.lit.is_some_and(|l| {
931            (l.datums.iter()).any(|name| {
932                name == &d.name
933                    || (name.strip_prefix(d.name.as_str()))
934                        .is_some_and(|rest| rest.starts_with(':'))
935            })
936        });
937        lit || !layer.hides(&d.name)
938    }
939
940    /// The datum planes and axes of `part`, drawn as large as the drawing
941    /// around the point of them nearest its centre, part of the scene.
942    fn datums(&mut self, part: &PartView, layer: &Layer<'_>) {
943        let size = part.extent.size.to_f64();
944        let center = part.extent.center.to_f64_array();
945        for d in part.datums.iter().filter(|d| Self::shows_datum(layer, d)) {
946            let f = &d.frame;
947            let origin = f.origin().to_f64_array();
948            let [u, v, w] = [f.u(), f.v(), f.w()].map(|a| a.to_f64_array());
949            let offset = plain::sub(center, origin);
950            let color = Self::datum_color(layer, d, &d.name, DATUM);
951            let pen = Pen {
952                color,
953                alpha: 0.8,
954                half: Self::px(EDGE_HALF_WIDTH),
955                tested: true,
956                dashes: Dashes::Solid,
957            };
958            match d.kind {
959                DatumKind::Plane => {
960                    let at = plain::sub(center, plain::scale(w, plain::dot(offset, w)));
961                    let corner = |a: f64, b: f64| {
962                        let along = plain::add(plain::scale(u, a), plain::scale(v, b));
963                        plain::add(at, plain::scale(along, size / 2.0))
964                    };
965                    let corners = [
966                        corner(-1.0, -1.0),
967                        corner(1.0, -1.0),
968                        corner(1.0, 1.0),
969                        corner(-1.0, 1.0),
970                    ];
971                    let [a, b, c, e] = corners.map(|p| self.project(p));
972                    self.filled([a, b, c], color, 0.12, true);
973                    self.filled([a, c, e], color, 0.12, true);
974                    self.polyline(corners.into_iter().chain([corners[0]]), &pen);
975                }
976                DatumKind::Axis => {
977                    let at = plain::add(origin, plain::scale(w, plain::dot(offset, w)));
978                    let ends = [-1.0, 1.0].map(|s| plain::add(at, plain::scale(w, s * size)));
979                    let dash = size / 30.0 / self.frame.per_px;
980                    let pen = Pen {
981                        alpha: 0.9,
982                        dashes: Dashes::Dashed(dash, dash / 2.0),
983                        ..pen
984                    };
985                    self.polyline(ends, &pen);
986                }
987                DatumKind::Point | DatumKind::Frame => {}
988            }
989        }
990    }
991
992    /// The point and frame datums of `part`, drawn over everything at a
993    /// size of their own on screen, in reaches, as a pick lays them out:
994    /// a frame's ball, its axes and the squares of its planes; a point's
995    /// ball and its three axes.
996    fn frames(&mut self, part: &PartView, layer: &Layer<'_>) {
997        let reach = Self::px(REACH_PX);
998        for d in part.datums.iter().filter(|d| Self::shows_datum(layer, d)) {
999            let f = &d.frame;
1000            let o = self.project(f.origin().to_f64_array());
1001            let axes = FrameAxis::ALL.map(|a| self.frame.on_screen(a.of(f).to_f64_array()));
1002            let at = |size: f64, x: [f64; 3]| {
1003                let along = (axes.iter().zip(x)).fold([0.0; 2], |s, (axis, x)| {
1004                    plain::add(s, plain::scale(*axis, x))
1005                });
1006                [o[0] + along[0] * size, o[1] + along[1] * size, o[2]]
1007            };
1008            let pen = |color: [f32; 3], half: f64| Pen {
1009                color,
1010                alpha: 1.0,
1011                half: half.max(Self::px(0.6)),
1012                tested: false,
1013                dashes: Dashes::Solid,
1014            };
1015            let unit = |k: usize, length: f64| plain::scale(unit(k), length);
1016            match d.kind {
1017                DatumKind::Frame => {
1018                    let size = FRAME * reach;
1019                    let component = |c: DatumComponent| {
1020                        (d.components.iter())
1021                            .find(|(of, _)| *of == c)
1022                            .map(|(_, name)| name.as_str())
1023                    };
1024                    let axes = FrameAxis::ALL.into_iter().zip(AXES).enumerate();
1025                    for (k, (axis, own)) in axes.clone() {
1026                        let name = component(DatumComponent::Plane(axis)).unwrap_or_default();
1027                        let color = Self::datum_color(layer, d, name, own);
1028                        let mut others = (0..3).filter(|&j| j != k).map(|j| unit(j, 1.0));
1029                        let (Some(a), Some(b)) = (others.next(), others.next()) else {
1030                            continue;
1031                        };
1032                        let corner = |s: f64, t: f64| {
1033                            at(size, plain::add(plain::scale(a, s), plain::scale(b, t)))
1034                        };
1035                        let square = [
1036                            corner(0.3, 0.3),
1037                            corner(0.6, 0.3),
1038                            corner(0.6, 0.6),
1039                            corner(0.3, 0.6),
1040                        ];
1041                        let [p, q, r, s] = square;
1042                        self.filled([p, q, r], color, 0.3, false);
1043                        self.filled([p, r, s], color, 0.3, false);
1044                        for (a, b) in [(p, q), (q, r), (r, s), (s, p)] {
1045                            self.segment(a, b, &pen(color, Self::px(0.5)), 0.0);
1046                        }
1047                    }
1048                    for (k, (axis, own)) in axes {
1049                        let name = component(DatumComponent::Axis(axis)).unwrap_or_default();
1050                        let color = Self::datum_color(layer, d, name, own);
1051                        let (base, tip) = (at(size, unit(k, 0.85)), at(size, unit(k, 1.0)));
1052                        self.segment(at(size, unit(k, 0.1)), base, &pen(color, 0.015 * size), 0.0);
1053                        self.arrowhead(base, tip, 0.05 * size, color);
1054                    }
1055                    let ball = Self::datum_color(layer, d, &d.name, 0xffffff);
1056                    self.disc(o, &pen(ball, 0.07 * size));
1057                }
1058                DatumKind::Point => {
1059                    let size = POINT_TRIAD * reach;
1060                    for (k, tint) in AXIS_TINTS.into_iter().enumerate() {
1061                        let color = Self::datum_color(layer, d, &d.name, tint);
1062                        self.segment(o, at(size, unit(k, 1.0)), &pen(color, Self::px(0.6)), 0.0);
1063                    }
1064                    let ball = Self::datum_color(layer, d, &d.name, DATUM);
1065                    self.disc(o, &pen(ball, 0.12 * size));
1066                }
1067                DatumKind::Plane | DatumKind::Axis => {}
1068            }
1069        }
1070    }
1071
1072    /// What a step being edited draws, over everything, in its style's
1073    /// colour. A label is drawn as a dot where its text would stand: a
1074    /// picture has no type.
1075    fn visual(&mut self, visual: &Visual) {
1076        let color = rgb(match visual.style {
1077            Style::Free | Style::Region => 0x6cb4ff,
1078            Style::Fixed => 0xe6e6e6,
1079            Style::Selected => 0xffd84a,
1080            Style::Hover => 0xffe0a0,
1081            Style::Failed => 0xff5d5d,
1082            Style::Construction => 0x8a8a8a,
1083            Style::Reference => 0xb48cf0,
1084            Style::Draft | Style::Handle => 0xffa040,
1085            Style::Guide => 0x777777,
1086            Style::Snap => 0x58e07a,
1087            Style::Removed => 0xff4f9a,
1088            Style::Paper => 0xbbbbbb,
1089        });
1090        let reach = Self::px(REACH_PX);
1091        let pen = Pen {
1092            color,
1093            alpha: 1.0,
1094            half: Self::px(EDGE_HALF_WIDTH),
1095            tested: false,
1096            dashes: Dashes::Solid,
1097        };
1098        let disc = |radius: f64| Pen {
1099            half: Self::px(radius),
1100            ..pen
1101        };
1102        match &visual.shape {
1103            Shape::Point { at } => {
1104                let at = self.project(at.to_f64_array());
1105                match visual.style {
1106                    Style::Snap => {
1107                        let ring = Self::px(6.0);
1108                        let around = (0..=24).map(|i| {
1109                            let angle = f64::from(i) * std::f64::consts::TAU / 24.0;
1110                            [
1111                                at[0] + ring * angle.cos(),
1112                                at[1] + ring * angle.sin(),
1113                                at[2],
1114                            ]
1115                        });
1116                        let mut last = None;
1117                        for p in around {
1118                            if let Some(a) = last {
1119                                self.segment(a, p, &pen, 0.0);
1120                            }
1121                            last = Some(p);
1122                        }
1123                    }
1124                    Style::Selected | Style::Hover => self.disc(at, &disc(5.0)),
1125                    _ => self.disc(at, &disc(3.5)),
1126                }
1127            }
1128            Shape::Polyline { points } => {
1129                let dashes = match visual.style {
1130                    Style::Reference => Dashes::Dashed(Self::px(1.5), Self::px(3.0)),
1131                    Style::Construction | Style::Draft | Style::Guide => {
1132                        Dashes::Dashed(Self::px(6.0), Self::px(4.0))
1133                    }
1134                    _ => Dashes::Solid,
1135                };
1136                let pen = Pen { dashes, ..pen };
1137                self.polyline(points.iter().map(|p| p.to_f64_array()), &pen);
1138            }
1139            Shape::Triangles { triangles } => {
1140                for t in triangles {
1141                    let t = t.map(|p| self.project(p.to_f64_array()));
1142                    self.filled(t, color, 0.15, false);
1143                }
1144            }
1145            Shape::Label { at, offset, .. } => {
1146                let shift = self.frame.on_screen(offset.to_f64_array());
1147                let at = self.project(at.to_f64_array());
1148                let at = [at[0] + shift[0] * reach, at[1] + shift[1] * reach, at[2]];
1149                self.disc(at, &disc(2.0));
1150            }
1151            Shape::Handle { at, direction } => {
1152                let unit = 0.8 * reach;
1153                let at = self.project(at.to_f64_array());
1154                self.disc(at, &Pen { half: unit, ..pen });
1155                if let Some(d) = plain::normalize(self.frame.on_screen(direction.to_f64_array())) {
1156                    for sign in [1.0, -1.0] {
1157                        let along = |x: f64| {
1158                            [
1159                                at[0] + sign * d[0] * x * unit,
1160                                at[1] + sign * d[1] * x * unit,
1161                                at[2],
1162                            ]
1163                        };
1164                        self.arrowhead(along(1.4), along(3.0), unit, color);
1165                    }
1166                }
1167            }
1168            Shape::Instance { .. } => {}
1169            Shape::Triad { at, axes } => {
1170                let size = TRIAD * reach;
1171                let at = self.project(at.to_f64_array());
1172                for (axis, color) in axes.iter().zip(AXES.map(rgb)) {
1173                    let Some(d) = plain::normalize(axis.to_f64_array()) else {
1174                        continue;
1175                    };
1176                    let d = self.frame.on_screen(d);
1177                    let along = |x: f64| [at[0] + d[0] * x * size, at[1] + d[1] * x * size, at[2]];
1178                    let pen = Pen {
1179                        color,
1180                        half: (0.02 * size).max(Self::px(0.6)),
1181                        ..pen
1182                    };
1183                    self.segment(along(0.0), along(0.8), &pen, 0.0);
1184                    self.arrowhead(along(0.8), along(1.0), 0.06 * size, color);
1185                }
1186            }
1187        }
1188    }
1189
1190    /// The world's axes in the bottom left corner, `x` red, `y` green, `z`
1191    /// blue, each lettered at its tip: those further into the picture
1192    /// first, so that the nearer cross them.
1193    fn compass(&mut self) {
1194        let origin = [Self::px(34.0), self.height as f64 - Self::px(34.0), 0.0];
1195        let length = Self::px(24.0);
1196        let mut order: Vec<(usize, u32)> = AXES.into_iter().enumerate().collect();
1197        let into = |k: usize| plain::dot(unit(k), self.frame.look);
1198        order.sort_by(|a, b| into(b.0).total_cmp(&into(a.0)));
1199        for (k, color) in order {
1200            let d = self.frame.on_screen(unit(k));
1201            let tip = [origin[0] + d[0] * length, origin[1] + d[1] * length, 0.0];
1202            let pen = Pen {
1203                color: rgb(color),
1204                alpha: 1.0,
1205                half: Self::px(1.0),
1206                tested: false,
1207                dashes: Dashes::Solid,
1208            };
1209            self.segment(origin, tip, &pen, 0.0);
1210            let towards = plain::normalize(d).unwrap_or([0.0, 0.0]);
1211            let letter = [
1212                tip[0] + towards[0] * Self::px(9.0),
1213                tip[1] + towards[1] * Self::px(9.0),
1214            ];
1215            self.letter(k, letter, rgb(color));
1216        }
1217    }
1218
1219    /// The letter of the axis `k`, `x`, `y` or `z`, centred on `at`.
1220    fn letter(&mut self, k: usize, at: P2, color: [f32; 3]) {
1221        const GLYPHS: [[&str; 5]; 3] = [
1222            ["#.#", "#.#", ".#.", "#.#", "#.#"],
1223            ["#.#", "#.#", ".#.", ".#.", ".#."],
1224            ["###", "..#", ".#.", "#..", "###"],
1225        ];
1226        let cell = Self::px(2.0);
1227        for (row, line) in GLYPHS.get(k).into_iter().flatten().enumerate() {
1228            for (column, c) in line.chars().enumerate() {
1229                if c != '#' {
1230                    continue;
1231                }
1232                let x = at[0] + (column as f64 - 1.5) * cell;
1233                let y = at[1] + (row as f64 - 2.5) * cell;
1234                for (sx, sy) in self.samples([x, y], [x + cell, y + cell]) {
1235                    self.blend(sy * self.width + sx, color, 1.0, None);
1236                }
1237            }
1238        }
1239    }
1240
1241    /// The raster, each pixel of `width` by `height` the mean of its
1242    /// samples, as a PNG.
1243    fn png(&self, width: usize, height: usize) -> GeopResult<Vec<u8>> {
1244        let mut sums = vec![[0.0f32; 3]; width * height];
1245        for (i, sample) in self.samples.iter().enumerate() {
1246            let (x, y) = (i % self.width / SUPERSAMPLE, i / self.width / SUPERSAMPLE);
1247            if let Some(sum) = sums.get_mut(y * width + x) {
1248                for (s, c) in sum.iter_mut().zip(sample.color) {
1249                    *s += c;
1250                }
1251            }
1252        }
1253        let samples = (SUPERSAMPLE * SUPERSAMPLE) as f32;
1254        let data: Vec<u8> = (sums.iter().flatten())
1255            .map(|s| (s / samples * 255.0).round().clamp(0.0, 255.0) as u8)
1256            .collect();
1257        let failed =
1258            |e: png::EncodingError| GeopError::new(format!("writing a picture as a PNG: {e}"));
1259        let mut out = Vec::new();
1260        let mut encoder = png::Encoder::new(&mut out, width as u32, height as u32);
1261        encoder.set_color(png::ColorType::Rgb);
1262        encoder.set_depth(png::BitDepth::Eight);
1263        let mut writer = encoder.write_header().map_err(failed)?;
1264        writer.write_image_data(&data).map_err(failed)?;
1265        writer.finish().map_err(failed)?;
1266        Ok(out)
1267    }
1268}
1269
1270#[cfg(test)]
1271mod tests {
1272    use std::{collections::BTreeMap, rc::Rc};
1273
1274    use geop_core_math::primitives::{CoordinateSystem, Pose};
1275
1276    use super::*;
1277    use crate::view::{FaceMesh, ViewEdge, ViewFace, ViewInstance, drawn};
1278
1279    fn v3(p: P3) -> Vector3 {
1280        Vector3::from_array(p.map(Scalar::from_f64))
1281    }
1282
1283    /// The unit cube from `low`: a solid `block` of six faces, `x-` to
1284    /// `z+`, and twelve edges, with the origin's frame beside it.
1285    fn block(low: P3) -> PartView {
1286        let mut view = PartView::new();
1287        view.model = "model".into();
1288        view.solids = vec!["block".into()];
1289        let corner = |c: [usize; 3]| v3(std::array::from_fn(|k| low[k] + c[k] as f64));
1290        for k in 0..3 {
1291            let (a, b) = ((k + 1) % 3, (k + 2) % 3);
1292            for side in 0..2 {
1293                let at = |i: usize, j: usize| {
1294                    let mut c = [0; 3];
1295                    (c[k], c[a], c[b]) = (side, i, j);
1296                    corner(c)
1297                };
1298                let quad = [at(0, 0), at(1, 0), at(1, 1), at(0, 1)];
1299                let mut normal = [0.0; 3];
1300                normal[k] = if side == 0 { -1.0 } else { 1.0 };
1301                view.faces.push(ViewFace {
1302                    also: Vec::new(),
1303                    name: format!("{}{}", ["x", "y", "z"][k], ["-", "+"][side]),
1304                    solid: Some("block".into()),
1305                    feature: None,
1306                    mesh: FaceMesh::new(
1307                        vec![[quad[0], quad[1], quad[2]], [quad[0], quad[2], quad[3]]],
1308                        vec![[v3(normal); 3]; 2],
1309                    ),
1310                });
1311                for i in 0..2 {
1312                    let mut c = [0; 3];
1313                    (c[k], c[a]) = (side, i);
1314                    let mut d = c;
1315                    d[b] = 1;
1316                    view.edges.push(ViewEdge {
1317                        name: format!("e{}", view.edges.len()),
1318                        solid: Some("block".into()),
1319                        faces: vec![],
1320                        polyline: vec![corner(c), corner(d)],
1321                        within_a_surface: false,
1322                        ends: [String::new(), String::new()],
1323                    });
1324                }
1325            }
1326        }
1327        view.datum_target = "datums".into();
1328        view.datums.push(ViewDatum {
1329            name: "origin".into(),
1330            kind: DatumKind::Frame,
1331            frame: CoordinateSystem::world_at(Vector3::zero()),
1332            components: FrameAxis::ALL
1333                .into_iter()
1334                .flat_map(|a| [DatumComponent::Axis(a), DatumComponent::Plane(a)])
1335                .map(|c| (c, format!("origin:{c}")))
1336                .collect(),
1337        });
1338        view
1339    }
1340
1341    fn scene(part: PartView) -> SceneState {
1342        SceneState {
1343            part: Rc::new(part.finish()),
1344            hidden: Some(vec![]),
1345            instances: vec![],
1346            all: true,
1347            removed: vec![],
1348            components: BTreeMap::new(),
1349            forgotten: vec![],
1350            meshes: BTreeMap::new(),
1351            forgotten_meshes: vec![],
1352            structure: None,
1353        }
1354    }
1355
1356    fn isometric(scene: &SceneState) -> Camera {
1357        let (look, up) = (v3([-1.0, -1.0, -1.0]), v3([0.0, 0.0, 1.0]));
1358        Camera::fitting(scene, look, up, 400, 300).unwrap()
1359    }
1360
1361    /// The picture's pixels, `[r, g, b]` by row, and its size.
1362    fn decoded(png: &[u8]) -> (Vec<[u8; 3]>, u32, u32) {
1363        let decoder = png::Decoder::new(png);
1364        let mut reader = decoder.read_info().unwrap();
1365        let mut data = vec![0; reader.output_buffer_size()];
1366        let info = reader.next_frame(&mut data).unwrap();
1367        assert_eq!(info.color_type, png::ColorType::Rgb);
1368        let pixels = (data[..info.buffer_size()].chunks(3))
1369            .map(|c| [c[0], c[1], c[2]])
1370            .collect();
1371        (pixels, info.width, info.height)
1372    }
1373
1374    fn at(pixels: &(Vec<[u8; 3]>, u32, u32), p: [f64; 2]) -> [u8; 3] {
1375        pixels.0[p[1] as usize * pixels.1 as usize + p[0] as usize]
1376    }
1377
1378    fn grey(c: [u8; 3]) -> bool {
1379        let (low, high) = (c.iter().min().unwrap(), c.iter().max().unwrap());
1380        *low > 60 && high - low < 16
1381    }
1382
1383    #[test]
1384    fn a_block_is_drawn_in_the_middle_of_its_picture() {
1385        let scene = scene(block([2.0, 2.0, 2.0]));
1386        let camera = isometric(&scene);
1387        let png = picture(&scene, None, &[], &camera).unwrap();
1388        let pixels = decoded(&png);
1389        assert_eq!((pixels.1, pixels.2), (400, 300));
1390        let middle = camera.pixel(&v3([2.5, 2.5, 2.5]));
1391        assert!((middle[0] - 200.0).abs() < 1.0 && (middle[1] - 150.0).abs() < 1.0);
1392        assert!(grey(at(&pixels, middle)), "{:?}", at(&pixels, middle));
1393        assert_eq!(at(&pixels, [399.0, 0.0]), [0x1b, 0x1f, 0x26]);
1394        // The three faces seen are shaded apart, so the block reads as one.
1395        let faces = [[2.5, 2.5, 3.0], [3.0, 2.5, 2.5], [2.5, 3.0, 2.5]];
1396        let shades = faces.map(|f| at(&pixels, camera.pixel(&v3(f)))[0]);
1397        assert!(shades[0] != shades[1] && shades[1] != shades[2] && shades[0] != shades[2]);
1398        // And drawn alike every time.
1399        assert_eq!(png, picture(&scene, None, &[], &camera).unwrap());
1400    }
1401
1402    #[test]
1403    fn a_pixel_points_where_it_is_drawn() {
1404        let scene = scene(block([2.0, 2.0, 2.0]));
1405        let camera = isometric(&scene);
1406        let pointer = camera.pointer(200.0, 150.0).unwrap();
1407        let accepts = [drawn::FACE.to_string()];
1408        let hit = scene.part.pick(&pointer, &accepts, &[]).unwrap();
1409        // Looking along (-1, -1, -1), the faces seen are x+, y+ and z+.
1410        assert!(hit.element.name.ends_with('+'), "{:?}", hit.element);
1411        let p = v3([2.3, 2.9, 2.1]);
1412        let [x, y] = camera.pixel(&p);
1413        let through = camera.through(p).unwrap();
1414        let back = camera.pixel(through.ray.origin());
1415        assert!((back[0] - x).abs() < 1e-9 && (back[1] - y).abs() < 1e-9);
1416        let (distance, t) = camera.pointer(x, y).unwrap().ray.distance_to_point(&p);
1417        assert!(distance.to_f64() < 1e-9 && t.to_f64() > 0.0);
1418        let Reach::Tube { radius } = pointer.reach else {
1419            panic!("an orthographic pointer reaches as a tube");
1420        };
1421        assert!((radius.to_f64() - REACH_PX * camera.span / 300.0).abs() < 1e-12);
1422    }
1423
1424    #[test]
1425    fn a_placed_part_is_framed_and_drawn_in_its_colour_and_what_is_lit_lit() {
1426        let component = Rc::new(block([0.0, 0.0, 0.0]).finish());
1427        let at_x = v3([10.0, 0.0, 0.0]);
1428        let mut part = block([0.0, 0.0, 0.0]);
1429        part.instances.push(ViewInstance {
1430            name: "bolt".into(),
1431            component: "block".into(),
1432            frame: CoordinateSystem::world_at(at_x),
1433            pose: Pose::identity().with_position(at_x),
1434            view: component,
1435            state: None,
1436            fixed: true,
1437            color: None,
1438        });
1439        let mut scene = scene(part);
1440        scene.instances = vec![ViewInstance {
1441            color: Some("#ff0000".into()),
1442            ..scene.part.instances[0].clone()
1443        }];
1444        let (look, up) = (v3([0.0, 0.0, -1.0]), v3([0.0, 1.0, 0.0]));
1445        let camera = Camera::fitting(&scene, look, up, 400, 300).unwrap();
1446        let lit = Lighting::from([(
1447            String::new(),
1448            Lit {
1449                faces: BTreeSet::from(["z+".to_string()]),
1450                ..Lit::default()
1451            },
1452        )]);
1453        let pixels = decoded(&picture(&scene, Some(&lit), &[], &camera).unwrap());
1454        let [r, g, b] = at(&pixels, camera.pixel(&v3([10.5, 0.5, 1.0])));
1455        assert!(r > 60 && g < 20 && b < 20, "{:?}", [r, g, b]);
1456        // Clear of the origin frame, drawn over the corner of the block.
1457        let [r, g, b] = at(&pixels, camera.pixel(&v3([0.8, 0.25, 1.0])));
1458        assert!(r > b + 60 && g > b + 30, "{:?}", [r, g, b]);
1459        // Both blocks are in the picture, with room round them.
1460        for x in [0.0, 11.0] {
1461            let [px, _] = camera.pixel(&v3([x, 0.5, 1.0]));
1462            assert!(px > 10.0 && px < 390.0, "{px}");
1463        }
1464    }
1465
1466    /// A face in a colour of its own is drawn in it, over the part's; a
1467    /// colour its instance is drawn in is over them all, as over the part's:
1468    /// the same view placed twice, once in a colour of the user's.
1469    #[test]
1470    fn a_face_is_drawn_in_its_colour_and_a_placed_parts_colour_is_over_it() {
1471        let mut own = block([0.0, 0.0, 0.0]);
1472        own.color = Some("#0000ff".into());
1473        own.face_colors = BTreeMap::from([("z+".to_string(), "#00ff00".to_string())]);
1474        let component = Rc::new(own.finish());
1475        let placed = |name: &str, x: f64, color: Option<&str>| {
1476            let at = v3([x, 0.0, 0.0]);
1477            ViewInstance {
1478                name: name.into(),
1479                component: "block".into(),
1480                frame: CoordinateSystem::world_at(at),
1481                pose: Pose::identity().with_position(at),
1482                view: component.clone(),
1483                state: None,
1484                fixed: true,
1485                color: color.map(str::to_string),
1486            }
1487        };
1488        let mut part = block([0.0, 0.0, 0.0]);
1489        part.face_colors = BTreeMap::from([("z+".to_string(), "#00ff00".to_string())]);
1490        part.instances = vec![
1491            placed("own", 3.0, None),
1492            placed("over", 6.0, Some("#ff0000")),
1493        ];
1494        let mut scene = scene(part);
1495        scene.instances = scene.part.instances.clone();
1496        let (look, up) = (v3([0.0, 0.0, -1.0]), v3([0.0, 1.0, 0.0]));
1497        let camera = Camera::fitting(&scene, look, up, 400, 300).unwrap();
1498        let pixels = decoded(&picture(&scene, None, &[], &camera).unwrap());
1499        let seen = |x: f64| at(&pixels, camera.pixel(&v3([x + 0.8, 0.25, 1.0])));
1500        for x in [0.0, 3.0] {
1501            let [r, g, b] = seen(x);
1502            assert!(g > r + 60 && g > b + 60, "{:?}", [r, g, b]);
1503        }
1504        // The part's blue times the placed part's red, as without a colour
1505        // of the face: no green of the face's left.
1506        let [_, g, _] = seen(6.0);
1507        assert!(g < 20, "{g}");
1508    }
1509
1510    #[test]
1511    fn a_look_along_up_takes_another_up() {
1512        let scene = scene(block([0.0, 0.0, 0.0]));
1513        let (look, up) = (v3([0.0, 0.0, -1.0]), v3([0.0, 0.0, 1.0]));
1514        let camera = Camera::fitting(&scene, look, up, 80, 60).unwrap();
1515        assert_eq!(camera.up.to_f64_array(), [1.0, 0.0, 0.0]);
1516        assert!(Camera::fitting(&scene, Vector3::zero(), up, 80, 60).is_err());
1517    }
1518}