Bouncing ball
An Earth-textured sphere bouncing in front of a starry-night
image background. The vertical motion comes from bounce(...) —
a parabolic arc per period that returns both the current
position and the list of impacts. The squash is keyed off
those impacts via on_event(...): an exponential-decay envelope
that spikes at each floor contact and relaxes between hits. Lit by
an ambient + directional pair through render3d.
image(...).as_texture(...) paints the sphere; image(...).fit(cover)
becomes the scene background. The squash is anisotropic — y
compresses by factor, x/z widen to roughly preserve volume. Field
access on a sampler-deferred value (bounce_y.impacts) lowers to a
field_of staging form that the sampler resolves at sample time.
runner "0.0.1";
use std.shapes.*;
use std.anim.*;
use std.scene3d.*;
use std.lighting.*;
scene bouncing_ball(
size: Size = size(1920px, 1080px),
duration: Duration = 6s,
) -> Frame {
let assets = {
earth: "/img/earth_4k.jpg",
night: "/img/starry_night.jpg",
};
// Full-bleed starry background. `image(...).fit(cover)` becomes the
// scene's background texture (handled in the compose translator); no
// size info on the image itself, so the viewport falls back to 16:9.
let bg = image(assets.night).fit(cover);
// Continuous y-axis spin — full turn every 6 s, looping.
let spin = animate {
0s => 0deg,
6s => 360deg,
} with { repeat: forever };
// Parabolic bounce: y swings from `floor` (impact) up to
// `floor + height` (apex). Returns a record { position, impacts },
// where impacts is the list of contact times the squash listens to.
// floor = -540px = bottom edge of the 1080-tall canvas — the ball
// bottom (pivoted) lands right on the image floor at impact.
let bounce_y = bounce(height: 760px, period: 1.2s, floor: -540px);
// Exponential decay envelope keyed off the bounce impacts: zero
// between contacts, snaps to `peak` on impact, then relaxes over
// `decay`. Squash factor below is "how much to compress" —
// 0 = no squash, 0.35 = compressed to ~65% height.
let stretch = on_event(bounce_y.impacts, decay: 0.18s, peak: 0.35);
let ball = sphere(r: 120px)
.material(fill: image(assets.earth).as_texture(projection: equirectangular))
.rotate(y: spin)
.pivot(bottom)
.squash(factor: stretch);
let scene = render3d(
ball.translate(y: bounce_y.position),
lights: [
ambient(0.35),
directional(from: vec3(2, 3, 4), intensity: 1.0),
],
);
compose [bg, scene]
}