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 circle
  • frame — fluid around a rectangular cutout (picture frame)
  • roundedRect — fluid inside a rounded rectangle
  • annulus — fluid inside a circular ring
  • svgPath — fluid inside an SVG path or text glyph

circle

Fluid contained inside a circle. Everything outside is zeroed.

FieldTypeDescription
type'circle'Discriminant
cxnumberHorizontal center (0 = left, 1 = right)
cynumberVertical center (0 = bottom, 1 = top)
radiusnumberCircle 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.

FieldTypeDescription
type'frame'Discriminant
cxnumberHorizontal center of the inner cutout
cynumberVertical center of the inner cutout
halfWnumberHalf-width of the inner rectangle in UV space (0–1)
halfHnumberHalf-height of the inner rectangle in UV space (0–1)
innerCornerRadiusnumber?Corner radius for the inner cutout. Default 0 (sharp corners).
outerHalfWnumber?Half-width of the outer boundary. Default 0.5 (full canvas).
outerHalfHnumber?Half-height of the outer boundary. Default 0.5 (full canvas).
outerCornerRadiusnumber?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.

FieldTypeDescription
type'roundedRect'Discriminant
cxnumberHorizontal center
cynumberVertical center
halfWnumberHalf-width in UV space
halfHnumberHalf-height in UV space
cornerRadiusnumberCorner 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.

FieldTypeDescription
type'annulus'Discriminant
cxnumberHorizontal center
cynumberVertical center
innerRadiusnumberRadius of the inner circle (hole), normalized by canvas height
outerRadiusnumberRadius 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.

FieldTypeDescription
type'svgPath'Discriminant
dstring?SVG path data string (path mode). Uses Path2D(d) with viewBox mapping.
textstring?Text to rasterize (text mode). Uses ctx.fillText(). Centered in the mask.
fontstring?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'.
maskResolutionnumber?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.