Skip to main content

wisp_storybook/stories/
s_mask_texture.rs

1//! Story: dynamic alpha-mask textures (M-DYN.1 / AUT-43).
2//!
3//! Contact sheet of the four SDF shapes plus the path variant, each
4//! generated as a standalone alpha-mask texture and displayed as a
5//! grayscale tile against the renderer's clear color. Demonstrates
6//! that the mask primitive is decoupled from foreground sampling —
7//! the texture itself is the deliverable.
8
9use glam::Vec2;
10use wisp::application::Application;
11use wisp::math::Rect;
12use wisp::render::Renderer;
13use wisp::{MaskShape, Sprite, Stage, Texture};
14
15use crate::story::Story;
16
17pub fn story() -> Story {
18    Story {
19        id: "mask-texture",
20        category: "Mask",
21        title: "Dynamic mask textures",
22        milestone: "M-DYN.1 / AUT-43",
23        writeup: include_str!("writeups/mask_texture.md"),
24        build,
25        tick: None,
26    }
27}
28
29fn build(app: &Application, stage: &mut Stage) {
30    let format = wgpu::TextureFormat::Rgba8Unorm;
31    let renderer = Renderer::new(app, format).expect("renderer");
32
33    // No backdrop — Graphics primitives draw AFTER Sprites in the
34    // batched renderer, so a full-canvas Graphics backdrop would
35    // cover the mask tiles. We rely on the clear color instead.
36
37    let tile_size: u32 = 192;
38    let scale = 0.30;
39    let positions = [-0.7, -0.35, 0.0, 0.35, 0.7];
40
41    let bounds = Rect::new(-0.85, -0.85, 1.7, 1.7);
42    let shapes = [
43        MaskShape::rect(bounds),
44        MaskShape::rounded_rect(bounds, 0.25),
45        MaskShape::circle(Vec2::ZERO, 0.85),
46        MaskShape::ellipse(Vec2::ZERO, Vec2::new(0.85, 0.5)),
47    ];
48
49    for (shape, x) in shapes.into_iter().zip(positions.iter().copied()) {
50        let mask_rt = renderer.generate_mask_texture(app, shape, tile_size, tile_size);
51        let bytes = mask_rt.read_pixels(app);
52        let tex = Texture::from_rgba(app, tile_size, tile_size, &bytes);
53        let mut sprite = Sprite::from_texture(tex).with_anchor(Vec2::splat(0.5));
54        sprite.container.transform.position = Vec2::new(x, 0.05);
55        sprite.container.transform.scale = Vec2::splat(scale);
56        let _ = stage.add_child(stage.root(), sprite);
57    }
58
59    // Path variant — five-pointed star at the rightmost slot.
60    let star: Vec<Vec2> = (0i16..10)
61        .map(|i| {
62            let r = if i % 2 == 0 { 0.85 } else { 0.35 };
63            let theta = f32::from(i) * std::f32::consts::PI / 5.0 - std::f32::consts::FRAC_PI_2;
64            Vec2::new(theta.cos() * r, theta.sin() * r)
65        })
66        .collect();
67    let star_rt = renderer.generate_path_mask_texture(app, &star, tile_size, tile_size);
68    let star_bytes = star_rt.read_pixels(app);
69    let star_tex = Texture::from_rgba(app, tile_size, tile_size, &star_bytes);
70    let mut sprite = Sprite::from_texture(star_tex).with_anchor(Vec2::splat(0.5));
71    sprite.container.transform.position = Vec2::new(positions[4], 0.05);
72    sprite.container.transform.scale = Vec2::splat(scale);
73    let _ = stage.add_child(stage.root(), sprite);
74}