Skip to main content

rscad_core/feature/
mod.rs

1//! Sub-object (face/edge) topology for direct modeling.
2//!
3//! CSG trees have no persistent face/edge identity: meshes are regenerated
4//! wholesale and carry no provenance. This module derives that identity
5//! *analytically* from the tree itself — faces are named per-primitive
6//! ([`FaceTag`]), leaves are addressed by structural [`LeafPath`]s, and
7//! [`geom`] evaluates rays/membership against the tree exactly, independent
8//! of the active render backend (mesh or SDF ray-march).
9
10pub mod edge;
11pub mod geom;
12pub mod lower;
13
14pub use edge::*;
15pub use geom::*;
16pub use lower::*;
17
18/// A face of a supported primitive, named in the primitive's local frame.
19///
20/// Tags are stable under parameter edits (resizing a cube does not change
21/// which face is `CubePosX`), which is what makes stored face references
22/// parametric rather than positional.
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub enum FaceTag {
26    CubePosX,
27    CubeNegX,
28    CubePosY,
29    CubeNegY,
30    CubePosZ,
31    CubeNegZ,
32    /// Curved side wall of a cylinder.
33    CylSide,
34    /// Cylinder cap at `y = height`.
35    CylTop,
36    /// Cylinder cap at `y = 0`.
37    CylBottom,
38    /// Slanted side of a cone.
39    ConeSide,
40    /// Cone base disk at `y = 0`.
41    ConeBottom,
42    /// The single surface of a sphere.
43    Sphere,
44    /// Face of an AABB proxy standing in for an unsupported subtree
45    /// (sketch/extrude/revolve based solids). Participates in boolean
46    /// occlusion but is never selectable or editable.
47    Unknown,
48}
49
50impl FaceTag {
51    /// Cube face tag for `axis` (0=X, 1=Y, 2=Z) on the positive or negative side.
52    pub fn cube(axis: usize, positive: bool) -> Self {
53        match (axis, positive) {
54            (0, true) => Self::CubePosX,
55            (0, false) => Self::CubeNegX,
56            (1, true) => Self::CubePosY,
57            (1, false) => Self::CubeNegY,
58            (2, true) => Self::CubePosZ,
59            (2, false) => Self::CubeNegZ,
60            _ => Self::Unknown,
61        }
62    }
63
64    /// Whether this face can ever be offered for selection/editing.
65    pub fn is_selectable(self) -> bool {
66        !matches!(self, Self::Unknown)
67    }
68}
69
70/// Structural address of a leaf inside a subtree, independent of node ids.
71///
72/// One index is consumed at each node with **more than one** child on the way
73/// down; single-child wrappers (transforms, colors, modifiers) are traversed
74/// implicitly. This keeps paths valid when an editing gesture lazily inserts
75/// a `Translate`/`Rotate` wrapper above a leaf — only inserting, removing, or
76/// reordering *siblings* under a boolean invalidates a path (callers must
77/// treat resolution as fallible).
78#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
80pub struct LeafPath(pub Vec<u16>);
81
82/// Which feature an `EdgeFeature` node applies to its edges.
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
84#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
85pub enum EdgeFeatureKind {
86    /// Flat cut replacing the edge.
87    Chamfer,
88    /// Round blend replacing the edge.
89    Fillet,
90}
91
92impl EdgeFeatureKind {
93    pub fn display_name(self) -> &'static str {
94        match self {
95            Self::Chamfer => "Chamfer",
96            Self::Fillet => "Fillet",
97        }
98    }
99}
100
101/// A face of a specific leaf, addressed structurally (survives node-id churn
102/// and scene save/load).
103#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
105pub struct FaceRef {
106    pub path: LeafPath,
107    pub face: FaceTag,
108}
109
110/// An edge, identified as the intersection curve of two faces. Stored in
111/// canonical (sorted) order so the same edge always compares equal.
112#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
114pub struct EdgeSel {
115    pub a: FaceRef,
116    pub b: FaceRef,
117}
118
119impl EdgeSel {
120    /// Canonicalize: the lexicographically smaller face ref becomes `a`.
121    pub fn new(x: FaceRef, y: FaceRef) -> Self {
122        if x <= y {
123            Self { a: x, b: y }
124        } else {
125            Self { a: y, b: x }
126        }
127    }
128}
129
130/// Payload of an `EdgeFeature` node: which edges to feature and how. Edges
131/// are re-resolved against the current child geometry every evaluation, so
132/// the feature follows parameter edits; unresolvable edges are skipped.
133#[derive(Clone, Debug, PartialEq)]
134#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
135pub struct EdgeFeatureData {
136    pub kind: EdgeFeatureKind,
137    /// Chamfer setback / fillet radius, in the child's local units.
138    pub size: f64,
139    /// Tessellation for revolved fillet masks (unused by chamfers).
140    pub segments: usize,
141    pub edges: Vec<EdgeSel>,
142}