Container Shapes
Confine the fluid to a geometric boundary. The simulation physically enforces the wall — velocity bounces and dye cannot escape.
The containerShape prop defines the boundary geometry. When set, the engine zeroes
velocity outside the shape after every physics pass and masks dye after advection. The fluid
is physically contained, not merely clipped visually.
Coordinates follow a normalized convention: cx/cy are in the range [0, 1]
(left-to-right, bottom-to-top). Radius values are normalized by canvas height, so radius: 0.45 gives a physical radius of 45% of the canvas height.
Five shape types are available:
circle— fluid inside a circleframe— fluid around a rectangular cutout (picture frame)roundedRect— fluid inside a rounded rectangleannulus— fluid inside a circular ringsvgPath— fluid inside an SVG path or text glyph
circle
Fluid contained inside a circle. Everything outside is zeroed.
| Field | Type | Description |
|---|---|---|
type | 'circle' | Discriminant |
cx | number | Horizontal center (0 = left, 1 = right) |
cy | number | Vertical center (0 = bottom, 1 = top) |
radius | number | Circle radius, normalized by canvas height |
<Fluid containerShape={{ type: 'circle', cx: 0.5, cy: 0.5, radius: 0.45 }} /> The most common container shape. Aspect correction is applied so the circle appears round regardless of canvas aspect ratio. A radius of 0.45 fits comfortably inside landscape canvases.
frame
Fluid flows everywhere except inside a rectangular cutout — like a picture frame. The inner rectangle is empty, and the fluid fills the border region around it.
| Field | Type | Description |
|---|---|---|
type | 'frame' | Discriminant |
cx | number | Horizontal center of the inner cutout |
cy | number | Vertical center of the inner cutout |
halfW | number | Half-width of the inner rectangle in UV space (0–1) |
halfH | number | Half-height of the inner rectangle in UV space (0–1) |
innerCornerRadius | number? | Corner radius for the inner cutout. Default 0 (sharp corners). |
outerHalfW | number? | Half-width of the outer boundary. Default 0.5 (full canvas). |
outerHalfH | number? | Half-height of the outer boundary. Default 0.5 (full canvas). |
outerCornerRadius | number? | Corner radius for the outer boundary. Default 0 (sharp corners). |
<Fluid containerShape={{
type: 'frame',
cx: 0.5, cy: 0.5,
halfW: 0.25, halfH: 0.25,
innerCornerRadius: 0.03
}} /> Think of it as a picture frame: halfW: 0.25 means the inner rectangle extends 25%
of canvas width on each side of cx. The outer boundary defaults to the full canvas;
set outerHalfW/outerHalfH to constrain it. Both inner and outer
boundaries support rounded corners via their respective radius parameters. Rounded corners are
aspect-corrected so they appear circular (like CSS border-radius).
roundedRect
Fluid stays inside a rounded rectangle. Like frame but inverted — the rectangle is the containment region, not the cutout.
| Field | Type | Description |
|---|---|---|
type | 'roundedRect' | Discriminant |
cx | number | Horizontal center |
cy | number | Vertical center |
halfW | number | Half-width in UV space |
halfH | number | Half-height in UV space |
cornerRadius | number | Corner rounding radius in UV space |
<Fluid containerShape={{
type: 'roundedRect',
cx: 0.5, cy: 0.5,
halfW: 0.35, halfH: 0.4,
cornerRadius: 0.06
}} /> Uses the Inigo Quilez rounded-box SDF internally. Corner radius is aspect-corrected so corners
appear circular in physical space. The LavaLamp preset uses this shape with cornerRadius: 0.15 for a pill-like vessel.
annulus
Fluid contained within a circular ring between an inner and outer circle. Both the inner hole and the outer edge are physical walls.
| Field | Type | Description |
|---|---|---|
type | 'annulus' | Discriminant |
cx | number | Horizontal center |
cy | number | Vertical center |
innerRadius | number | Radius of the inner circle (hole), normalized by canvas height |
outerRadius | number | Radius of the outer circle, normalized by canvas height |
<Fluid containerShape={{
type: 'annulus',
cx: 0.5, cy: 0.5,
innerRadius: 0.15, outerRadius: 0.45
}} /> Creates a donut-shaped fluid region. Aspect correction is applied like circle. The
ToroidalTempest preset uses this shape to create a violent storm circulating in a ring. Pair with autoSplatSwirl to sustain orbital motion.
svgPath
Fluid contained within the filled region of an SVG path string or Canvas 2D text. The shape is rasterized to a mask texture at construction time.
| Field | Type | Description |
|---|---|---|
type | 'svgPath' | Discriminant |
d | string? | SVG path data string (path mode). Uses Path2D(d) with viewBox mapping. |
text | string? | Text to rasterize (text mode). Uses ctx.fillText(). Centered in the mask. |
font | string? | CSS font string for text mode. Default 'bold 72px sans-serif'. |
viewBox | [number, number, number, number]? | viewBox for path mode. Default [0, 0, 100, 100]. |
fillRule | 'nonzero' | 'evenodd'? | Fill rule for path mode. Use 'evenodd' for font outlines with counters. Default 'nonzero'. |
maskResolution | number? | Rasterization resolution (longest dimension in pixels). Default 512. |
At least one of d or text must be provided. If both are given, d takes precedence.
Text mode
<Fluid containerShape={{
type: 'svgPath',
text: '&',
font: 'bold 200px Georgia, serif',
fillRule: 'evenodd'
}} /> Text mode uses ctx.fillText() to rasterize text into the mask. The text is automatically centered. Use fillRule: 'evenodd' for glyphs with counters (holes) like "A", "O", or "&".
Path mode
<Fluid containerShape={{
type: 'svgPath',
d: 'M50 10 L90 90 L10 90 Z',
viewBox: [0, 0, 100, 100]
}} /> Path mode uses Path2D(d) with viewBox mapping. The viewBox defines the coordinate space for the path data. Any valid SVG path data string works.
Open Boundaries
By default, all boundaries are closed — fluid bounces off both the canvas edges
and the container shape walls. The openBoundary prop changes this behavior:
<Fluid
containerShape={{ type: 'circle', cx: 0.5, cy: 0.5, radius: 0.45 }}
openBoundary
/> When openBoundary is true, fluid flows freely instead of bouncing. The
divergence solver skips no-penetration enforcement at the canvas edges, and the container shape
becomes a visual crop rather than a physical wall — dye and velocity are not zeroed outside the
shape. The FluidReveal component defaults to openBoundary: true for
natural scratch behavior.
Mask Texture Approach
The circle, frame, roundedRect, and annulus types use analytical SDFs (signed distance functions) evaluated directly in the GLSL shader.
These are cheap to compute and produce perfectly smooth edges.
The svgPath type uses a mask texture approach instead. An OffscreenCanvas rasterizes the path or text at the configured maskResolution (default 512px), producing a grayscale alpha mask. This mask is
uploaded as a WebGL texture and sampled by the physics shaders to determine which regions contain fluid.
Random splat spawning uses rejection sampling against a CPU-side copy of the mask data,
ensuring splats only appear inside the shape. Increase maskResolution for finer
detail on complex paths; decrease it for better performance on simple shapes.