Files
vectorcraft/crates/doc/src/perspective.rs
T
arhammusheer 5beac4f122 #1244: smaller objects: Node 352 -> 152 bytes, NodeKind 200 -> 80, and a cheaper undo estimate
Rarely set fields move into one optional boxed NodeExtras, and large NodeKind payloads (images,
live shapes, blends, envelopes) are boxed. Every object, and every undo step that copies it,
shrinks: a 50k-path move keeps ~24.5 MB per step instead of ~40 MB. Files stay byte-identical
(Node keeps its flat JSON through a borrowed Serialize and a from-wire Deserialize, no flatten),
and loading is slightly faster. Undo and redo reuse their step's size estimate instead of
walking the document again (8.3 -> 5.6 ms for a 50k-path move).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc

# Conflicts:
#	crates/engine/src/cmd/graph.rs
#	crates/svg/src/export.rs
2026-10-12 11:02:32 +07:00

70 lines
2.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Perspective Grid attachments (Object › Perspective).
//!
//! The grid itself is document data (`Document.unknown["perspectiveGrid"]`, modelled by the tools
//! crate). Which plane an object lies on is kept on the object, so copies, duplicates and pastes
//! stay attached and deleted objects leave nothing stale behind.
use serde::{Deserialize, Serialize};
use vectorcraft_geom::{Homography, Rect};
use crate::{Node, NodeKind};
/// The plane an object in perspective lies on.
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PerspectiveAttachment {
/// `left`, `right` or `ground`.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub plane: String,
/// Where along the plane's normal (points) the parallel plane the object lies on is: 0 is the
/// grid plane in its original place.
#[serde(default, skip_serializing_if = "crate::skip::is_default")]
pub depth: f64,
/// Type and symbol instances: the projective map (row-major 3 × 3) that draws their flat art
/// in perspective. They stay type and symbol instances; the renderer and exporters project
/// their outlines.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub projection: Option<[f64; 9]>,
/// Object › Perspective › Edit Text in progress: the type is drawn flat to be edited (not
/// saved).
#[serde(skip)]
pub editing: bool,
}
impl PerspectiveAttachment {
pub fn new(plane: &str, depth: f64) -> Self {
Self { plane: plane.to_string(), depth: if depth.is_finite() { depth } else { 0.0 }, ..Self::default() }
}
/// The projection, when it is a valid one.
pub fn homography(&self) -> Option<Homography> {
self.projection.and_then(Homography::from_array)
}
}
impl Node {
/// The projective map type or a symbol instance in perspective is drawn through (not while
/// Edit Text shows it flat).
pub fn projection(&self) -> Option<Homography> {
let a = self.extras().perspective.as_deref().filter(|a| !a.editing)?;
matches!(self.kind, NodeKind::Text(_) | NodeKind::SymbolInstance { .. }).then(|| a.homography()).flatten()
}
/// `b` (flat bounds) as drawn: the box of its projection for type and symbols in perspective.
pub(crate) fn projected(&self, b: Option<Rect>) -> Option<Rect> {
match (self.projection(), b) {
(Some(h), Some(b)) => h.map_rect_bbox(b).or(Some(b)),
(_, b) => b,
}
}
/// Keep a projection drawing the same picture moved by `a` (the flat art moves by `a` too).
pub(crate) fn transform_projection(&mut self, a: vectorcraft_geom::Affine) {
if let Some(p) = self.extras_if_any().and_then(|e| e.perspective.as_deref_mut())
&& let Some(h) = p.homography().and_then(|h| h.conjugated(a))
{
p.projection = Some(h.to_array());
}
}
}