# Cassini Ovals — `cassini-ovals@1.0.0`

The glowing family of Cassini ovals flowing outwards from two foci while one bright curve morphs from two loops through a figure-eight to a single oval. Loops perfectly.

The curves of Giovanni Cassini, the points whose distances to two fixed foci multiply to a constant. As the constant grows they morph from two separate loops, through the figure-eight lemniscate, to a single peanut and finally a nearly circular oval. A whole family of them glows on a dark field and flows outwards from the foci like ripples while one bright curve breathes through every shape of the family and back. Choose the colours (electric, amber, violet or mono), how far apart the foci are, the lines' thickness, the flow and the grid.

- Kind: background; status: published; digest sha256:bd9ce5767aef8785894d6ee005394ee39b47177040527c738168d40935256eb2
- Role: Background (WebGL shader, full frame): a precise, hypnotic mathematical backdrop for science, tech and title content.
- Implementation: three; entry `src/cassini-ovals.js`
- Files: https://aipixelwizard.com/registry/cassini-ovals/1.0.0/<path> (`LICENSE`, `asset.json`, `demo.js`, `index.html`, `src/cassini-ovals.js`)
- Manifest JSON: https://aipixelwizard.com/api/v1/visuals/cassini-ovals/versions/1.0.0
- Runnable demo: https://aipixelwizard.com/registry/cassini-ovals/1.0.0/index.html (open over http; it imports sibling packages by relative path)
- Dependencies (exact): `three@0.185.1` (source) — Renderer, geometry and shader materials.; `apw-runtime@1.0.0` (demo) — Demo harness.

## Exports
- `createCassini({ palette?, spread?, lines?, width?, flow?, grid? }) => { scene, camera, setSize(width, height), update(time, loopSeconds), dispose() }` — The family of curves as one full-screen shader; render its scene with its camera, ideally through createBloomPass.
- `createBloomPass(renderer, { width, height, glow?, exposure?, grain? }) => { render(scene, camera, output?), dispose() }` — A small bloom with filmic tone mapping.
- `PALETTES: { electric, amber, violet, mono }` — Colours of the family, the moving curve and the foci (sRGB hex).

## Parameters
- `palette` (select one of electric | amber | violet | mono, default "electric") — Colours
- `spread` (number 0.6–1.4, default 1) — Foci apart
- `width` (number 0.5–2, default 1) — Line width
- `flow` (integer 0–3, default 1) — Flow: Whole rungs of the family that creep outwards per loop.
- `grid` (number 0–1.5, default 1) — Grid

## Composition
- Inputs: Time within the loop; the colours, foci spread, line width, flow and grid.
- Outputs: A full frame, drawn by one shader on a full-screen quad.
- Coordinates: The plane spans about 1.55 half-heights either way; the foci are at ±0.62 · spread on the x axis.
- Lighting: Self-lit; the bloom gives the glow.
- Timing: update(time, loopSeconds) sets the loop phase; frames render in any order.
- Works with: kinetic-title, number-reveal
- The family's levels are a geometric ladder of |z² − a²| (ratio 1.28) drawn as lines of even pixel width by dividing by the gradient's length; the bright curve is the level b² with b swinging from 0.38a to 1.62a, passing through the lemniscate at b = a.

## Output and loop
- 1080×1080, 30 fps, 12 s, procedural
- Seamless loop: yes — The bright curve's constant is b = a(1 + 0.62 sin(2π·phase)), the ladder of levels creeps a whole number of rungs per loop and everything else is static, so the last frame meets the first.
- Verified 2026-10-06 in Chrome 154.0.8037.97 headless on Linux 7.0.0-38-generic (x64), Node 22.14.0 (WebGL 2 via SwiftShader (software)); deterministic: yes; loop check: Frame 0 vs end state (progress 1): mean |Δ| 0.0000, max |Δ| 0 of 255.

## Use it
New project: Create a WebGLRenderer, createCassini() and createBloomPass(renderer, { width, height }); call cassini.setSize(width, height), then each frame cassini.update(time, loopSeconds) and bloom.render(cassini.scene, cassini.camera).

Existing project: Copy src/cassini-ovals.js; render cassini.scene with cassini.camera into a render target (or through the bloom pass) as a backdrop.

Reuse ideas:
- Maths explainer: Electric, one rung a loop.
- Warm science title: Amber, wide foci.
- Still diagram backdrop: Mono, still.

## Limitations
- The picture is a pure 2D contour plot: no depth, no 3D surface.
- The foci are always on the x axis.

## License
MIT. Copyright (c) 2026 AI Pixel Wizard. Released under the MIT License. See LICENSE in this folder.

Provenance: Adapted from the owner's threejs-sketches/geometry-math/cassini-ovals (MIT) and rebuilt for AI Pixel Wizard in October 2026 as an original shader: gradient-normalised contour lines of |z² − a²| on a creeping geometric ladder, a morphing bright oval through the lemniscate and an exact loop.

