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}