mirror of
https://github.com/storytold/cadcraft.git
synced 2026-11-10 12:30:16 -06:00
211 lines
8.8 KiB
Rust
211 lines
8.8 KiB
Rust
use super::{Bounds2, Handle, Pen, Rgb, Sheet, Vec2, outline};
|
|
|
|
/// Handle carried by model-space geometry drawn inside a paper-space viewport (it belongs to no
|
|
/// single paper-space entity, so selection highlighting never matches it).
|
|
pub const VIEWPORT_CONTENT: Handle = Handle(0);
|
|
|
|
/// [`DisplayList::prim_layers`] of a primitive whose layer isn't in the layer table.
|
|
pub const NO_LAYER: u32 = u32::MAX;
|
|
|
|
/// What a display-list primitive draws.
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub enum Kind {
|
|
/// `verts[start..start+len]` as a connected polyline.
|
|
Polyline,
|
|
/// `tris[start..start+len]`, three vertices per triangle.
|
|
Tris,
|
|
/// A wipeout: triangles laid out like [`Kind::Tris`], filled with the background colour
|
|
/// (paper white when plotting, the canvas colour on screen) so they hide what was drawn
|
|
/// before them.
|
|
Mask,
|
|
/// A single point marker at `verts[start]`.
|
|
Point,
|
|
/// An infinite line or ray: `verts[start]` = base, `verts[start+1]` = unit direction.
|
|
Infinite { ray: bool },
|
|
/// A raster image, [`DisplayList::images`]`[index]`; `verts[start..start+4]` are its corners
|
|
/// (top left, top right, bottom right, bottom left of the pixel grid).
|
|
Image { index: u32 },
|
|
}
|
|
|
|
/// A raster image placed in the world.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct DImage {
|
|
pub raster: std::sync::Arc<cadcraft_doc::Raster>,
|
|
/// Where the top-left corner of the top-left pixel is.
|
|
pub origin: Vec2,
|
|
/// One pixel to the right and one pixel down, in world units.
|
|
pub right: Vec2,
|
|
pub down: Vec2,
|
|
/// Closed outlines the image is cut to (all of them: its own clip boundary, then the
|
|
/// viewports it is seen through). Empty: the whole image.
|
|
pub clips: Vec<Vec<Vec2>>,
|
|
/// Fade towards the background, percent (0..=100).
|
|
pub fade: u8,
|
|
}
|
|
|
|
impl DImage {
|
|
/// The world position of pixel-grid point (`x`, `y`), `y` down from the top.
|
|
pub fn at(&self, x: f64, y: f64) -> Vec2 {
|
|
self.origin + self.right * x + self.down * y
|
|
}
|
|
/// The pixel-grid point at world position `q` (the inverse of [`DImage::at`]); `None` for an
|
|
/// image squashed to a line or a point.
|
|
pub fn pixel_of(&self, q: Vec2) -> Option<Vec2> {
|
|
let det = self.right.x * self.down.y - self.right.y * self.down.x;
|
|
if !(det.is_finite() && det.abs() > 0.0) {
|
|
return None;
|
|
}
|
|
let d = q - self.origin;
|
|
Some(Vec2::new((d.x * self.down.y - d.y * self.down.x) / det, (self.right.x * d.y - self.right.y * d.x) / det))
|
|
}
|
|
/// How opaque the image is drawn, 0..=1: the object's opacity `alpha` (0..=255) less the fade.
|
|
pub fn opacity(&self, alpha: u8) -> f32 {
|
|
f32::from(alpha) / 255.0 * (1.0 - f32::from(self.fade.min(100)) / 100.0)
|
|
}
|
|
/// The corners: top left, top right, bottom right, bottom left.
|
|
pub fn corners(&self) -> [Vec2; 4] {
|
|
let (w, h) = (f64::from(self.raster.width()), f64::from(self.raster.height()));
|
|
[self.at(0.0, 0.0), self.at(w, 0.0), self.at(w, h), self.at(0.0, h)]
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct DPrim {
|
|
pub handle: Handle,
|
|
pub color: Rgb,
|
|
/// Colour 7 (directly, by layer or by block): drawn white on a dark background and black on
|
|
/// a light one, see [`DPrim::display_rgb`]. Other colours keep their RGB.
|
|
pub aci7: bool,
|
|
/// Lineweight in mm (0 = thinnest).
|
|
pub lw: f32,
|
|
/// Transparency in percent (0 = opaque, at most 90): the object's own, its layer's when
|
|
/// ByLayer, the block reference's when ByBlock. Always 0 unless [`Options::transparency`].
|
|
pub transparency: u8,
|
|
pub kind: Kind,
|
|
pub start: u32,
|
|
pub len: u32,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct DisplayList {
|
|
pub prims: Vec<DPrim>,
|
|
pub verts: Vec<Vec2>,
|
|
pub tris: Vec<Vec2>,
|
|
/// The raster images drawn by [`Kind::Image`] primitives.
|
|
pub images: Vec<DImage>,
|
|
/// Plots ([`build_plot`]): for each primitive, the index in [`Drawing::layers`] of the layer
|
|
/// it is drawn on (block contents on layer 0: the block reference's; [`NO_LAYER`] when the
|
|
/// layer isn't in the table), for PDF layers. Empty for other lists.
|
|
pub prim_layers: Vec<u32>,
|
|
/// Plots: outline-font glyphs that a plotter can write as text instead of drawing the
|
|
/// filled outline primitive they stand for ([`PlotText::prim`]), in drawing order.
|
|
pub texts: Vec<PlotText>,
|
|
pub bounds: Bounds2,
|
|
/// The paper sheet when the list was built for a layout (paper space); the UI draws the
|
|
/// white sheet, its shadow and the printable-area outline from it.
|
|
pub sheet: Option<Sheet>,
|
|
/// Block contents expanded while building (see [`MAX_BLOCK_EXPANSION`]).
|
|
pub expanded: usize,
|
|
/// The expansion budget was spent: block contents past it are not in the list. The screen,
|
|
/// plots and exports draw what is here; the CLI and the control channel report this, so an
|
|
/// output that leaves contents out is never silent (#1070).
|
|
pub truncated: bool,
|
|
/// Plot style end, join and fill style per primitive (parallel to `prims`; missing entries
|
|
/// are "use object"), turned into geometry by [`pen::apply`].
|
|
pub pens: Vec<Pen>,
|
|
}
|
|
|
|
/// A glyph of plotted text: the font glyph and, in world coordinates, where it sits. It is
|
|
/// drawn by primitive [`PlotText::prim`] (a filled outline in the primitive's colour); output
|
|
/// with real text writes the glyph instead of that primitive.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct PlotText {
|
|
/// The filled outline primitive (index in [`DisplayList::prims`]); [`PlotText::NONE`] when
|
|
/// the glyph has none in the list (clipped by a viewport).
|
|
pub prim: u32,
|
|
/// Triangle vertices of the outline when it was drawn, and their bounding box (a clipped
|
|
/// outline has others).
|
|
pub verts: u32,
|
|
pub bounds: Bounds2,
|
|
/// The glyph; its origin and em axes are in world coordinates.
|
|
pub glyph: cadcraft_fonts::TextGlyph,
|
|
}
|
|
|
|
impl PlotText {
|
|
pub const NONE: u32 = u32::MAX;
|
|
/// [`PlotText::prim`] of a glyph cut by a viewport edge.
|
|
pub const CLIPPED: u32 = u32::MAX - 1;
|
|
}
|
|
|
|
impl DPrim {
|
|
/// The colour to draw with on `background`.
|
|
pub fn display_rgb(&self, background: Rgb) -> Rgb {
|
|
cadcraft_color::display_rgb(self.color, self.aci7, background)
|
|
}
|
|
/// The opacity to draw with: 255 when opaque, down to 26 at 90 % transparency.
|
|
pub fn alpha(&self) -> u8 {
|
|
alpha(self.transparency)
|
|
}
|
|
}
|
|
|
|
/// The opacity (0..=255) of a transparency percentage, which is clamped to 0..=90 first.
|
|
pub fn alpha(transparency: u8) -> u8 {
|
|
let t = u32::from(transparency.min(MAX_TRANSPARENCY));
|
|
u8::try_from((255 * (100 - t) + 50) / 100).unwrap_or(u8::MAX)
|
|
}
|
|
|
|
/// The most transparent an object can be, in percent.
|
|
pub const MAX_TRANSPARENCY: u8 = 90;
|
|
|
|
impl DisplayList {
|
|
pub fn segment_count(&self) -> usize {
|
|
self.prims.iter().filter(|p| p.kind == Kind::Polyline).map(|p| p.len.saturating_sub(1) as usize).sum()
|
|
}
|
|
/// The polyline points of a primitive.
|
|
pub fn points(&self, p: &DPrim) -> &[Vec2] {
|
|
match p.kind {
|
|
Kind::Tris | Kind::Mask => self.tris.get(p.start as usize..(p.start + p.len) as usize).unwrap_or(&[]),
|
|
_ => self.verts.get(p.start as usize..(p.start + p.len) as usize).unwrap_or(&[]),
|
|
}
|
|
}
|
|
/// The boundary of a filled primitive ([`Kind::Tris`], [`Kind::Mask`]) as closed polylines,
|
|
/// for highlighting it; empty for other kinds.
|
|
pub fn fill_outlines(&self, p: &DPrim) -> Vec<Vec<Vec2>> {
|
|
match p.kind {
|
|
Kind::Tris | Kind::Mask => outline::fill_outlines(self.points(p)),
|
|
_ => Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Append `src` to `dst`: its primitives index into `dst`'s vertex and triangle arrays.
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
pub(crate) fn append(dst: &mut DisplayList, src: DisplayList) {
|
|
let (verts, tris, prims) = (dst.verts.len() as u32, dst.tris.len() as u32, dst.prims.len() as u32);
|
|
let images = dst.images.len() as u32;
|
|
// Pens are parallel to the primitives, with missing entries meaning "use object".
|
|
if !src.pens.is_empty() {
|
|
dst.pens.resize(dst.prims.len(), Pen::default());
|
|
}
|
|
dst.prims.extend(src.prims.into_iter().map(|mut p| {
|
|
p.start += if matches!(p.kind, Kind::Tris | Kind::Mask) { tris } else { verts };
|
|
if let Kind::Image { index } = &mut p.kind {
|
|
*index = index.saturating_add(images);
|
|
}
|
|
p
|
|
}));
|
|
dst.images.extend(src.images);
|
|
dst.verts.extend(src.verts);
|
|
dst.tris.extend(src.tris);
|
|
dst.prim_layers.extend(src.prim_layers);
|
|
dst.texts.extend(src.texts.into_iter().map(|mut t| {
|
|
if t.prim < PlotText::CLIPPED {
|
|
t.prim += prims;
|
|
}
|
|
t
|
|
}));
|
|
dst.pens.extend(src.pens);
|
|
dst.bounds = dst.bounds.union(&src.bounds);
|
|
dst.expanded = dst.expanded.saturating_add(src.expanded);
|
|
}
|