Skip to main content

wisp/scene/
clip.rs

1//! Mask shapes used by [`Container::clip`](super::container::Container)
2//! to clip a node's rendered subtree to a region.
3//!
4//! Coordinates are NDC `[-1, +1]²` — the mask is in screen space, not
5//! container-local space. The recording-quad use case (cinematic
6//! rounded-corner crop on a fixed-position recording surface) is the
7//! primary driver. Transform-aware clipping ("clip a moving sprite to
8//! its own bounds") is a future enhancement.
9//!
10//! At render-time, a clipped container's subtree is rendered into a
11//! foreground `RenderTexture`, then the [`MaskShape`]'s SDF is sampled
12//! per-pixel and multiplied into the alpha channel before the composite
13//! is blended back onto the parent. See `render::clip` for the
14//! pipeline.
15
16use crate::math::Rect;
17
18/// Shape of a clip / mask region.
19///
20/// Variants land issue-by-issue:
21///
22/// - [`MaskShape::Rect`] — AUT-20 rectangle privacy mask.
23/// - [`MaskShape::RoundedRect`] — AUT-31 rounded crop foundation.
24/// - [`MaskShape::Circle`] — AUT-30 webcam circle mask.
25/// - [`MaskShape::Ellipse`] — AUT-34 oval / ellipse mask.
26///
27/// Later issues (`AUT-35` freehand path) extend this enum further.
28#[derive(Debug, Clone, Copy, PartialEq)]
29#[non_exhaustive]
30pub enum MaskShape {
31    /// Sharp-corner rectangle in NDC. The renderer treats this as a
32    /// `RoundedRect` with `radius = 0` and routes through the same
33    /// SDF pipeline.
34    Rect {
35        /// Axis-aligned bounding rect, NDC coords.
36        rect: Rect,
37    },
38    /// Rounded rectangle in NDC. `rect` is the axis-aligned bounding
39    /// box, `radius` is the corner radius in NDC units (clamped at
40    /// render-time to half the smaller side).
41    RoundedRect {
42        /// Axis-aligned bounding rect, NDC coords.
43        rect: Rect,
44        /// Corner radius in NDC units.
45        radius: f32,
46    },
47    /// Circle in NDC. The renderer treats this as a `RoundedRect`
48    /// with a square bounding box and a corner radius equal to the
49    /// half-extent — the rounded-rect SDF degenerates exactly to the
50    /// circle SDF in that case (`length(p) - r`).
51    Circle {
52        /// Center, NDC coords.
53        center: glam::Vec2,
54        /// Radius, NDC units.
55        radius: f32,
56    },
57    /// Axis-aligned ellipse in NDC. `half_extents` is `(a, b)` of the
58    /// implicit equation `(x/a)^2 + (y/b)^2 = 1`. The renderer uses a
59    /// scaled-quadratic pseudo-SDF that's anisotropic-correct and
60    /// cheap (one length, one multiply); not Euclidean distance, but
61    /// accurate enough for masking and AA.
62    Ellipse {
63        /// Center, NDC coords.
64        center: glam::Vec2,
65        /// Half-extents `(a, b)`, NDC units.
66        half_extents: glam::Vec2,
67    },
68}
69
70impl MaskShape {
71    /// Convenience constructor for a sharp-corner rectangle.
72    #[must_use]
73    pub fn rect(rect: Rect) -> Self {
74        Self::Rect { rect }
75    }
76
77    /// Convenience constructor for a rounded rectangle.
78    #[must_use]
79    pub fn rounded_rect(rect: Rect, radius: f32) -> Self {
80        Self::RoundedRect { rect, radius }
81    }
82
83    /// Convenience constructor for a circle.
84    #[must_use]
85    pub fn circle(center: glam::Vec2, radius: f32) -> Self {
86        Self::Circle { center, radius }
87    }
88
89    /// Convenience constructor for an axis-aligned ellipse.
90    #[must_use]
91    pub fn ellipse(center: glam::Vec2, half_extents: glam::Vec2) -> Self {
92        Self::Ellipse {
93            center,
94            half_extents,
95        }
96    }
97
98    /// The axis-aligned bounding rect of the mask.
99    #[must_use]
100    pub fn bounds(self) -> Rect {
101        match self {
102            Self::Rect { rect } | Self::RoundedRect { rect, .. } => rect,
103            Self::Circle { center, radius } => Rect::new(
104                center.x - radius,
105                center.y - radius,
106                radius * 2.0,
107                radius * 2.0,
108            ),
109            Self::Ellipse {
110                center,
111                half_extents,
112            } => Rect::new(
113                center.x - half_extents.x,
114                center.y - half_extents.y,
115                half_extents.x * 2.0,
116                half_extents.y * 2.0,
117            ),
118        }
119    }
120}