luvcraft/birthday/balloons.lisp
--------------------------------------------------------------------- The balloon's proportions, as knobs
As with the gnome, each number stands in the shader source by name and folds to a literal when the source is parsed, so turning one rebuilds the pipeline. The shape works in balloon units -- one unit is the balloon's radius at its widest -- and only the two amplitudes speak in world cells, because a big balloon and a small one hang from the same kind of breeze.
How much taller than wide a balloon stands, in balloon radii. One would be a ball, which is a different toy.
The strength of the latex highlight. Zero is a matte balloon, which is a balloon that has given up.
--------------------------------------------------------------------- The shape
Three primitives in balloon units: the latex is an upright ellipsoid
eased toward the knot by a small sphere so the bottom tapers the way a
filled balloon actually hangs, the knot is a nub poking out under the
taper, and the string is a thin capsule dropping from the knot. The
latex parts weld with the gnome's smooth minimum; the string joins by
plain min, because a string is tied on, not grown.
The latex: an ellipsoid eased into a teardrop, with a tied-off knot.
The taper sphere sits low inside the ellipsoid and the generous fillet between them is what turns two primitives into one skin; the knot keeps a small fillet so it still reads as a knot.
Six party colours on a ring; HUE picks one or blends between neighbours.
Saturated primaries on purpose -- the very thing the gnome's palette refuses. A balloon is a toy, and should read as one from across the meadow: red, orange, sunny yellow, green, sky blue, pink, and around again to red.
--------------------------------------------------------------------- The pipeline stages
The shape spans balloon-unit y from the string's tail up to the crown, so its bounding sphere is centred well below the balloon's own centre. Both stages state the bound the same way from the same knobs -- the vertex to size the proxy, the fragment to enter and leave the march analytically -- so they cannot drift apart. The margin of three tenths covers the string's thickness and anything a smooth union swells.
The dance, entirely here: a bob and two sways at incommensurate rates, each offset by the instance phase, so a row of balloons never marches in step. The fragment receives the moved centre and needs no clock of its own.
The bound, in balloon units, then scaled by this instance.
Balloon units: world over the instance radius. No facing frame -- a balloon is round about its string -- so the ray marches unrotated and only the camera moves into the balloon's frame.
The same bound the vertex stage stated.
Half the gnome's steps: three round primitives converge fast, and the ellipsoid's bounded estimate already understates, so the step scale stays close to one.
A feathered edge rather than the gnome's hard step: latex against sky benefits from half a pixel of mercy.
Which material owns the hit, by the nearer field.
The string dissolves over its lower half, so the balloon reads as tied to the air rather than planted in the ground.
Latex is two highlights: the tight glint that says glossy and a broad sheen that says rubber rather than glass. Neither belongs on the string.
Sunlight through the far side: the lift that makes a back-lit balloon glow its own colour instead of going dark.
Premultiplied alpha, as the scene target expects: a miss leaves no rectangular trace of the proxy.
--------------------------------------------------------------------- The overlay: one pipeline, one quad, one instanced draw.
Structured after the gnome's body overlay, minus its every-frame
heartbeat: the instance buffer is written once when the balloons are
tied on, and the vertex shader animates from the frame clock, so
encode only binds and draws.
Pack balloons into the two-vec4 instance records the shader reads.
Each record is (centre.xyz, radius) then (hue, phase, spare, spare). An unstated hue walks the palette ring by the golden ratio and an unstated phase spreads similarly, so a bag of plain (:x :y :z) plists still comes out as a mixed bunch that never bobs in unison.
Tie balloons -- plists of (:x :y :z :radius :hue :phase) -- into session.
One overlay, one instanced draw. Any balloons already tied on are taken
down first, so calling this twice redecorates rather than crowds. Returns
the overlay, or NIL when balloons is empty.
Take down every balloon overlay tied to session, releasing its GPU state.
(session balloons)Tie BALLOONS -- plists of (:x :y :z :radius :hue :phase) -- into SESSION. One overlay, one instanced draw. Any balloons already tied on are taken down first, so calling this twice redecorates rather than crowds. Returns the overlay, or NIL when BALLOONS is empty.
(session)How much taller than wide a balloon stands, in balloon radii. One would be a ball, which is a different toy.
How far the string hangs below the knot, in balloon radii.
How high a balloon rides its bob, in world cells.
How far a balloon drifts sideways, in world cells.
The strength of the latex highlight. Zero is a matte balloon, which is a balloon that has given up.
The pale outline that lifts a balloon off whatever stands behind it.
How much sunlight leaks through the latex when the sun is behind it.
Construct a meaningful literal.
Compare compatible quantities and produce dimensionless values.
((name symbol) (type (eql 'arithmetic-operator)))(name (&key label (group :grading) documentation
(class ''scalar-knob)
quantity (type 'single-float)
unit-label minimum maximum step)
place)Define NAME as a knob of CLASS over the setf-able PLACE. PLACE may refer to SESSION, so a knob can live on the session -- its sky clock, its player -- as well as in a special. QUANTITY is a quantity plist as DEFINE-QUANTITY-CONSTANT takes, (:quantity ... :unit ...); it and TYPE make the knob's declaration, which…
The minimum of compatible quantities.
(name parameters &body body)Define a reusable typed shader expression with ordinary source syntax. The body is parsed at each call site, so argument types and quantity meanings flow through the same operator protocol as handwritten shader expressions. LET* is lexical inside the function. The definition macro records source; its body does not…
The latex: an ellipsoid eased into a teardrop, with a tied-off knot. The taper sphere sits low inside the ellipsoid and the generous fillet between them is what turns two primitives into one skin; the knot keeps a small fillet so it still reads as a knot.
The usual bounded approximation to an ellipsoid's signed distance. Exact would need a quartic root; this under-estimates, which is all sphere tracing asks of it.
The polynomial smooth minimum: a union with a fillet of RADIUS. This is what makes a nose grow out of a face instead of intersecting it.
Constrain a quantity between compatible bounds.
Division of two represented quantities.
Subtraction or unary negation.
Select and reorder vector components by a designator such as :XYZ or :RGB.
Multiplication and scalar scaling.
Normalize a dimensionless vector.
Addition over compatible quantities.
Six party colours on a ring; HUE picks one or blends between neighbours. Saturated primaries on purpose -- the very thing the gnome's palette refuses. A balloon is a toy, and should read as one from across the meadow: red, orange, sunny yellow, green, sky blue, pink, and around again to red.
Interpolate compatible quantities by a scalar amount.
(generic-function name specialized-lambda-list options &body body)Define a shader-producing method with ordinary DEFMETHOD identity. Calling the method reparses its small source form so changes to source-level abstractions participate in live rebuilding. Method replacement remains the role/stage identity watched by the MOP; abstraction revisions are tracked separately by live…
(role stage)Return the current durable shader specification for ROLE and STAGE.
Expose a quantity's raw representation.
The inner product of two vectors.
The componentwise square root of a raw value.
The maximum of compatible quantities.
Test whether one compatible scalar is less than another.
Test whether one compatible scalar is greater than another.
Produce dimensionless progress across compatible edges.
Raise a dimensionless value to a dimensionless power.
(overlay)The live shader pipelines OVERLAY owns, so the session can count them among its own; none by default.
(overlay session pass surface-texture)Encode OVERLAY into SESSION's open scene PASS for SURFACE-TEXTURE.
An explicitly owned AVFrame.
(pass-encoder pipeline)(pass-encoder slot buffer &key (offset 0))(pass-encoder index bind-group)(pass-encoder vertex-count
&optional (instance-count 1) (first-vertex 0) (first-instance 0))(overlay)Release resources owned by an object attached to luvcraft.
(artifact)(handle)Logically invalidate HANDLE immediately. Native teardown may be deferred until submitted work which captured HANDLE has completed.
(balloons)Pack BALLOONS into the two-vec4 instance records the shader reads. Each record is (centre.xyz, radius) then (hue, phase, spare, spare). An unstated hue walks the palette ring by the golden ratio and an unstated phase spreads similarly, so a bag of plain (:x :y :z) plists still comes out as a mixed bunch that never…
Logical disjunction of tests and raw truth values.
The non-negative remainder of integer division.
(device descriptor)Asks the DEVICE for a handle to newly created instance of some object fulfilling the DESCRIPTOR.
(&key role (stage :fragment) vertex-role label device layout vertex-module
vertex-buffers target-format target-blend primitive depth-stencil)Create and install a live mathematical render pipeline. VERTEX-MODULE is borrowed when VERTEX-ROLE is NIL and STAGE is not :VERTEX. Every module compiled from ROLE or VERTEX-ROLE is owned by the returned artifact and retired on replacement or explicit release.
The linear HDR attachment format shared by every scene-stage pipeline.
(session overlay)Attach OVERLAY at SESSION's next native frame boundary and return it. ADD consumes a newly offered OVERLAY on both success and terminal rejection. Once SESSION begins stopping, the rejected overlay is released exactly once before APPLICATION-ATTACHMENT-CLOSED is signalled.
(session overlay &key (release-p t))Detach OVERLAY at a frame boundary and optionally release it there. The no-release path is intentionally preserved for FUSE-LUVCRAFT-OVERLAY: that path may run inside a frame whose command stream still borrows OVERLAY.
Balloons: bright SDF spheres-with-knots bobbing on their strings.
Contract: (
add-birthday-balloonssession balloons) registers one scene overlay drawing every balloon as an instanced, camera-facing, sphere-traced billboard in the manner of the gnome's body overlay. BALLOONS is a list of plists (:x :y :z :radius :hue :phase); the overlay owns its instance buffer and pipelines. Balloons bob and sway gently on the frame clock with per-instance phase, and each wears its own bright glossy colour. (remove-birthday-balloonssession) releases the overlay.Shader sections live under (in-package #:luvcraft.shaders).
A balloon is much simpler than a gnome and the shader below leans on that: it has no face to keep toward the camera -- a balloon is round about its string, so the field is marched straight in world axes -- and the whole dance is done in the vertex stage. Each frame the vertex shader reads the frame clock out of the fog vector, bobs and sways the balloon's centre by its instance phase, and hands the
*animated*centre to the fragment stage; the CPU writes the instance buffer once, when the balloons are tied on, and never again. The billboard is centred on the animated bounding sphere, so nothing can slide out of its own proxy and be sliced flat at the edge -- the bound follows the balloon around rather than trying to be wide enough for everywhere it might go.