Skip to main content

wisp_storybook/stories/
s_ellipse.rs

1//! Story: ellipse mask (M-MASK.9 / AUT-34).
2//!
3//! Three side-by-side ellipse cutouts — wide, tall, square (which is
4//! a circle) — over a textured webcam-style backdrop, demonstrating
5//! the new anisotropic SDF branch.
6
7use glam::Vec2;
8use wisp::application::Application;
9use wisp::math::Rect;
10use wisp::render::Renderer;
11use wisp::{Color, Fill, Graphics, MaskShape, RenderTexture, Sprite, Stage, Texture};
12// Note: the storybook exporter clears to BLACK before render_stage,
13// so masked sprites pop against black. We do NOT add a Graphics
14// backdrop here — Graphics paints AFTER Sprites in `render_stage`'s
15// pipeline-batched submission order, so a full-canvas backdrop would
16// overlay the masked sprites (CLAUDE.md "Renderer batching / draw
17// order" note).
18
19use crate::story::Story;
20
21pub fn story() -> Story {
22    Story {
23        id: "ellipse-mask",
24        category: "Mask",
25        title: "Ellipse mask",
26        milestone: "M-MASK.9 / AUT-34",
27        writeup: include_str!("writeups/ellipse_mask.md"),
28        build,
29        tick: None,
30    }
31}
32
33fn build(app: &Application, stage: &mut Stage) {
34    let format = wgpu::TextureFormat::Rgba8Unorm;
35    let renderer = Renderer::new(app, format).expect("renderer");
36
37    let frame_rt = render_textured_frame(&renderer, app, format);
38
39    let variants = [
40        // (half_extents, position)
41        (Vec2::new(0.85, 0.4), Vec2::new(-0.62, 0.0)), // wide
42        (Vec2::new(0.4, 0.85), Vec2::new(0.0, 0.0)),   // tall
43        (Vec2::new(0.7, 0.7), Vec2::new(0.62, 0.0)),   // round
44    ];
45
46    for (half, pos) in variants {
47        let clipped = RenderTexture::with_format(app, 256, 256, format);
48        let shape = MaskShape::ellipse(Vec2::ZERO, half);
49        renderer.apply_clip(app, shape, &frame_rt, &clipped);
50        let bytes = clipped.read_pixels(app);
51        let tex = Texture::from_rgba(app, 256, 256, &bytes);
52        let mut sprite = Sprite::from_texture(tex).with_anchor(Vec2::splat(0.5));
53        sprite.container.transform.position = pos;
54        sprite.container.transform.scale = Vec2::splat(0.28);
55        let _ = stage.add_child(stage.root(), sprite);
56    }
57}
58
59fn render_textured_frame(
60    renderer: &Renderer,
61    app: &Application,
62    format: wgpu::TextureFormat,
63) -> RenderTexture {
64    let mut s = Stage::new();
65    let mut tone = Graphics::new();
66    tone.fill(Fill::LinearGradient {
67        start: Vec2::new(-1.0, 1.0),
68        end: Vec2::new(1.0, -1.0),
69        color_a: Color::rgba_u8(255, 195, 150, 255),
70        color_b: Color::rgba_u8(150, 100, 80, 255),
71    });
72    tone.draw_rect(Rect::new(-1.0, -1.0, 2.0, 2.0));
73    let _ = s.add_child(s.root(), tone);
74    for i in -3i16..=3 {
75        let x = f32::from(i) * 0.25;
76        let mut line = Graphics::new();
77        line.fill(Fill::Solid(Color::rgba_u8(255, 255, 255, 180)));
78        line.draw_rect(Rect::new(x - 0.01, -1.0, 0.02, 2.0));
79        let _ = s.add_child(s.root(), line);
80    }
81    for j in -3i16..=3 {
82        let y = f32::from(j) * 0.25;
83        let mut line = Graphics::new();
84        line.fill(Fill::Solid(Color::rgba_u8(255, 255, 255, 180)));
85        line.draw_rect(Rect::new(-1.0, y - 0.01, 2.0, 0.02));
86        let _ = s.add_child(s.root(), line);
87    }
88    let rt = RenderTexture::with_format(app, 256, 256, format);
89    let _ = renderer.render_stage(app, rt.view(), Color::TRANSPARENT, &s);
90    rt
91}