mod support;
use macroquad::prelude::*;
use rae::{MorphCacheKey, MorphProfile, MorphShape, Palette, Rect, Rgba, Vec2};
use support::*;
fn window_conf() -> Conf {
Conf {
window_title: "rae morph surfaces".to_string(),
window_width: 1440,
window_height: 900,
high_dpi: true,
sample_count: 4,
..Default::default()
}
}
#[macroquad::main(window_conf)]
async fn main() {
let palette = Palette::midnight();
let transitions = transitions();
let max_frames = max_frames_from_env();
let mut frames = 0u64;
loop {
frames = frames.saturating_add(1);
if should_stop(frames, max_frames) {
break;
}
let time = get_time() as f32;
let viewport = Rect::new(0.0, 0.0, screen_width(), screen_height());
draw_scene_backdrop(&palette, time);
draw_header(viewport, &palette);
draw_morph_grid(viewport, &palette, &transitions, time);
next_frame().await;
}
}
fn transitions() -> [(MorphShape, MorphShape, Rgba); 6] {
[
(
MorphShape::RoundedCard,
MorphShape::Blob,
Rgba::rgb8(122, 185, 255),
),
(
MorphShape::Capsule,
MorphShape::Ticket,
Rgba::rgb8(83, 225, 147),
),
(
MorphShape::DockIsland,
MorphShape::CommandLens,
Rgba::rgb8(255, 190, 94),
),
(
MorphShape::NotchedPanel,
MorphShape::RoundedCard,
Rgba::rgb8(205, 130, 255),
),
(
MorphShape::Ticket,
MorphShape::DockIsland,
Rgba::rgb8(255, 126, 171),
),
(
MorphShape::Blob,
MorphShape::Capsule,
Rgba::rgb8(91, 255, 211),
),
]
}
fn draw_header(viewport: Rect, palette: &Palette) {
let header = Rect::new(28.0, 24.0, viewport.width - 56.0, 74.0);
draw_glass_panel(header, 26.0, palette, palette.accent, true);
draw_text_line(
"Morph Surfaces",
header.x + 26.0,
header.y + 42.0,
31.0,
to_mq(palette.text),
);
draw_text_line(
"topology descriptors animated into card-like surface approximations",
header.x + 294.0,
header.y + 40.0,
17.0,
to_mq(palette.muted),
);
draw_pill(
Rect::new(header.right() - 236.0, header.y + 20.0, 214.0, 32.0),
"SDF data model",
palette.accent,
palette.text,
);
}
fn draw_morph_grid(
viewport: Rect,
palette: &Palette,
transitions: &[(MorphShape, MorphShape, Rgba)],
time: f32,
) {
let grid = Rect::new(28.0, 126.0, viewport.width - 56.0, viewport.height - 154.0);
let columns = if viewport.width < 900.0 { 2 } else { 3 };
let rows = transitions.len().div_ceil(columns);
let gap = 20.0;
let card_w = (grid.width - gap * (columns - 1) as f32) / columns as f32;
let card_h = (grid.height - gap * (rows - 1) as f32) / rows as f32;
for (index, (from, to, accent)) in transitions.iter().enumerate() {
let rect = Rect::new(
grid.x + (index % columns) as f32 * (card_w + gap),
grid.y + (index / columns) as f32 * (card_h + gap),
card_w,
card_h,
);
draw_morph_card(
rect,
palette,
*from,
*to,
*accent,
time + index as f32 * 0.22,
);
}
}
fn draw_morph_card(
rect: Rect,
palette: &Palette,
from: MorphShape,
to: MorphShape,
accent: Rgba,
time: f32,
) {
draw_glass_panel(rect, 28.0, palette, accent, true);
let profile = MorphProfile::material_default(from, to);
let elapsed = ((time * 420.0).sin() * 0.5 + 0.5) * 680.0;
let state = profile.sample(elapsed.round() as u32);
let key = MorphCacheKey::new(from, to, Vec2::new(rect.width, rect.height), state.progress);
draw_text_line(
&format!("{from:?} -> {to:?}"),
rect.x + 20.0,
rect.y + 30.0,
17.0,
to_mq(palette.text),
);
draw_text_line(
&format!(
"bucket {} anchor {:?}",
key.frame_bucket, state.topology.anchor
),
rect.x + 20.0,
rect.y + 53.0,
13.0,
to_mq(palette.muted),
);
let stage = Rect::new(
rect.x + 28.0,
rect.y + 82.0,
rect.width - 56.0,
rect.height * 0.42,
);
draw_rounded_rect(stage, 24.0, rgba8(1, 5, 16, 150));
draw_topology_shape(stage, palette, accent, state.topology, state.progress);
let metric_y = stage.bottom() + 22.0;
draw_bar(
rect.x + 24.0,
metric_y,
rect.width - 48.0,
"progress",
state.progress,
accent,
palette,
);
draw_bar(
rect.x + 24.0,
metric_y + 34.0,
rect.width - 48.0,
"corner",
(state.topology.corner_radius / 80.0).min(1.0),
palette.accent,
palette,
);
draw_bar(
rect.x + 24.0,
metric_y + 68.0,
rect.width - 48.0,
"blob",
state.topology.blob_strength,
palette.success,
palette,
);
}
fn draw_topology_shape(
stage: Rect,
palette: &Palette,
accent: Rgba,
topology: rae::CardTopology,
progress: f32,
) {
let pulse = 0.94 + progress * 0.06;
let w = stage.width * (0.66 + topology.aspect_pull * 0.18) * pulse;
let h = stage.height * (0.48 + topology.blob_strength * 0.10);
let cx = stage.x + stage.width * 0.5;
let cy = stage.y + stage.height * 0.5;
let shape = Rect::new(cx - w * 0.5, cy - h * 0.5, w, h);
let radius = topology.corner_radius.min(h * 0.5).max(8.0);
draw_glow(
cx,
cy,
120.0 + topology.edge_wave * 3.0,
accent.with_alpha(0.15 + topology.blob_strength * 0.08),
7,
);
draw_rounded_rect(
shape,
radius,
to_mq(accent.with_alpha(0.22 + progress * 0.16)),
);
draw_rectangle_lines(
shape.x,
shape.y,
shape.width,
shape.height,
1.4,
to_mq(accent.with_alpha(0.72)),
);
if topology.notch_depth > 1.0 {
let notch_r = topology.notch_depth.min(22.0);
draw_circle(
shape.x,
shape.y + shape.height * 0.5,
notch_r,
to_mq(palette.background.with_alpha(0.92)),
);
draw_circle(
shape.right(),
shape.y + shape.height * 0.5,
notch_r,
to_mq(palette.background.with_alpha(0.92)),
);
}
if topology.edge_wave > 1.0 || topology.blob_strength > 0.1 {
for index in 0..9 {
let t = index as f32 / 8.0;
let x = shape.x + shape.width * t;
let offset = (t * std::f32::consts::TAU * 2.0 + progress * 4.0).sin()
* topology.edge_wave
* 0.32;
draw_circle(
x,
shape.y + offset,
3.0 + topology.blob_strength * 4.0,
to_mq(accent.with_alpha(0.42)),
);
draw_circle(
x,
shape.bottom() - offset,
3.0 + topology.blob_strength * 4.0,
to_mq(accent.with_alpha(0.32)),
);
}
}
}
fn draw_bar(x: f32, y: f32, width: f32, label: &str, value: f32, color: Rgba, palette: &Palette) {
draw_text_line(label, x, y + 12.0, 12.0, to_mq(palette.muted));
let track = Rect::new(x + 72.0, y, width - 72.0, 16.0);
draw_rounded_rect(track, 8.0, rgba8(1, 5, 16, 150));
draw_rounded_rect(
Rect::new(
track.x,
track.y,
track.width * value.clamp(0.0, 1.0),
track.height,
),
8.0,
to_mq(color.with_alpha(0.58)),
);
}