Skip to main content

wisp_storybook/stories/
s_graphics_polygon.rs

1//! Story: convex polygon SDF primitive (M-VEC.21 / AUT-225).
2//!
3//! Six convex shapes that exercise the fan-triangulated polygon path —
4//! the chart-side polygons (area fills, sankey ribbons, funnel bands,
5//! ternary triangle, parallel-coord polylines-as-polygons) are all
6//! convex by construction in v1.
7
8use std::f32::consts::{FRAC_PI_2, TAU};
9
10use glam::Vec2;
11use wisp::application::Application;
12use wisp::{Color, Fill, Graphics, Stage};
13
14use crate::story::Story;
15
16pub fn story() -> Story {
17    Story {
18        id: "graphics-polygon",
19        category: "Graphics",
20        title: "Convex polygon shapes",
21        milestone: "M-VEC.21",
22        writeup: include_str!("writeups/graphics_polygon.md"),
23        build,
24        tick: None,
25    }
26}
27
28fn build(_app: &Application, stage: &mut Stage) {
29    let mut g = Graphics::new();
30
31    let col_x = [-0.66, 0.0, 0.66];
32    let row_y = [0.5, -0.5];
33
34    // (0, 0): square — minimal 4-vertex baseline.
35    g.fill(Fill::Solid(Color::rgba_u8(80, 200, 255, 255)));
36    g.draw_polygon(&square_centred(col_x[0], row_y[0], 0.22));
37
38    // (1, 0): equilateral triangle (CCW from top).
39    g.fill(Fill::Solid(Color::rgba_u8(255, 200, 80, 255)));
40    g.draw_polygon(&regular_ngon(col_x[1], row_y[0], 0.22, 3));
41
42    // (2, 0): regular pentagon.
43    g.fill(Fill::Solid(Color::rgba_u8(160, 100, 220, 255)));
44    g.draw_polygon(&regular_ngon(col_x[2], row_y[0], 0.22, 5));
45
46    // (0, 1): regular hexagon.
47    g.fill(Fill::Solid(Color::rgba_u8(120, 220, 140, 255)));
48    g.draw_polygon(&regular_ngon(col_x[0], row_y[1], 0.22, 6));
49
50    // (1, 1): funnel-area-style trapezoid (wide bottom).
51    g.fill(Fill::Solid(Color::rgba_u8(255, 100, 80, 255)));
52    g.draw_polygon(&[
53        Vec2::new(col_x[1] - 0.15, row_y[1] - 0.22),
54        Vec2::new(col_x[1] + 0.15, row_y[1] - 0.22),
55        Vec2::new(col_x[1] + 0.28, row_y[1] + 0.22),
56        Vec2::new(col_x[1] - 0.28, row_y[1] + 0.22),
57    ]);
58
59    // (2, 1): elongated octagon — exercises a higher vertex count.
60    g.fill(Fill::Solid(Color::rgba_u8(80, 220, 200, 255)));
61    g.draw_polygon(&regular_ngon(col_x[2], row_y[1], 0.22, 8));
62
63    let _ = stage.add_child(stage.root(), g);
64}
65
66fn square_centred(cx: f32, cy: f32, half: f32) -> [Vec2; 4] {
67    [
68        Vec2::new(cx - half, cy - half),
69        Vec2::new(cx + half, cy - half),
70        Vec2::new(cx + half, cy + half),
71        Vec2::new(cx - half, cy + half),
72    ]
73}
74
75#[allow(
76    clippy::cast_precision_loss,
77    reason = "n is a tiny ngon vertex count (<=8 in this story) — well below f32 precision"
78)]
79fn regular_ngon(cx: f32, cy: f32, radius: f32, n: usize) -> Vec<Vec2> {
80    (0..n)
81        .map(|i| {
82            // Start at top (+y); fix winding so it stays CCW for the
83            // renderer's `+Y` up NDC.
84            let theta = FRAC_PI_2 + (i as f32) * TAU / (n as f32);
85            Vec2::new(cx + radius * theta.cos(), cy + radius * theta.sin())
86        })
87        .collect()
88}