@jayf0x/fluidity-js 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +141 -55
- package/CONTRIBUTING.md +32 -1
- package/README.md +55 -40
- package/dist/globals.d.ts +20 -19
- package/dist/index.js +818 -763
- package/llms.txt +32 -38
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# fluidity-js — Agent Guide
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> Claude Code users: see [CLAUDE.md](./CLAUDE.md) for deeper architecture notes.
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## What this repo is
|
|
9
|
-
|
|
10
|
-
`@jayf0x/fluidity-js` — a WebGPU-first fluid simulation library for React. Implements a real-time Navier-Stokes solver (advection, divergence, pressure, vorticity confinement) that renders onto a `<canvas>` element. Falls back automatically to WebGL2 → WebGL1. Runs the heavy simulation loop in a Web Worker via OffscreenCanvas by default.
|
|
3
|
+
WebGPU-first fluid simulation React library. Real-time Navier-Stokes solver (advection, divergence, pressure, vorticity confinement) on a `<canvas>`. Automatic fallback: WebGPU → WebGL2 → WebGL1. Simulation runs in a Web Worker via OffscreenCanvas by default.
|
|
11
4
|
|
|
12
5
|
Two components: `<FluidText>` (text as obstacle/background) and `<FluidImage>` (bitmap as obstacle/background).
|
|
13
6
|
|
|
@@ -15,29 +8,32 @@ Two components: `<FluidText>` (text as obstacle/background) and `<FluidImage>` (
|
|
|
15
8
|
|
|
16
9
|
---
|
|
17
10
|
|
|
18
|
-
## Repo layout
|
|
11
|
+
## Repo layout
|
|
19
12
|
|
|
20
13
|
```
|
|
21
14
|
src/
|
|
22
15
|
index.ts ← public exports
|
|
23
|
-
globals.d.ts ← ambient global types (FluidConfig, FluidHandle…)
|
|
16
|
+
globals.d.ts ← ambient global types (FluidConfig, FluidHandle, FluidBaseProps…) — no import needed
|
|
17
|
+
index.d.ts ← public module declarations for consumers
|
|
24
18
|
core/
|
|
25
|
-
config.ts ← DEFAULT_CONFIG,
|
|
26
|
-
|
|
19
|
+
config.ts ← DEFAULT_CONFIG, DEFAULT_CONFIG_TEXT, DEFAULT_PROPS_*, DEFAULT_QUALITY,
|
|
20
|
+
PRESETS, PROP_RANGES, mergeConfig, normalizeConfig
|
|
21
|
+
gl-utils.ts ← WebGL context + FBO helpers, Program class, createBlit
|
|
27
22
|
gpu-utils.ts ← WebGPU helpers
|
|
28
23
|
shaders.ts ← GLSL shader strings
|
|
29
24
|
wgsl-shaders.ts ← WGSL shader strings
|
|
30
|
-
simulation.ts ← FluidSimulation class (dual WebGPU/WebGL)
|
|
25
|
+
simulation.ts ← FluidSimulation class (dual WebGPU/WebGL); use FluidSimulation.create() for WebGPU-first
|
|
31
26
|
textures.ts ← texture creation for text + image modes
|
|
32
27
|
worker/index.ts ← Web Worker message handler
|
|
33
28
|
fluid-controller.ts ← worker vs main-thread abstraction
|
|
34
29
|
react/
|
|
35
|
-
FluidText.tsx ← React component
|
|
36
|
-
FluidImage.tsx ← React component
|
|
30
|
+
FluidText.tsx ← React component (forwardRef)
|
|
31
|
+
FluidImage.tsx ← React component (forwardRef), imageSize prop
|
|
37
32
|
useFluid.ts ← core hook: canvas lifecycle + controller
|
|
38
|
-
tests/ ← Vitest + jsdom
|
|
39
|
-
demo/ ← standalone Vite 5 demo site (NOT the library)
|
|
33
|
+
tests/ ← Vitest + jsdom
|
|
34
|
+
demo/ ← standalone Vite 5 demo site (NOT the library; uses alias to src/)
|
|
40
35
|
dist/ ← built output (do not edit)
|
|
36
|
+
backlog.md ← prioritised bug/feature backlog
|
|
41
37
|
```
|
|
42
38
|
|
|
43
39
|
---
|
|
@@ -45,19 +41,21 @@ dist/ ← built output (do not edit)
|
|
|
45
41
|
## Commands
|
|
46
42
|
|
|
47
43
|
```bash
|
|
48
|
-
# Run tests (library root
|
|
44
|
+
# Run tests (library root)
|
|
49
45
|
bun test:claude # preferred: runs vitest, tails last 8 lines
|
|
50
46
|
|
|
51
47
|
# Build library
|
|
52
|
-
bun build
|
|
48
|
+
bun build # → dist/
|
|
53
49
|
|
|
54
50
|
# Demo (requires Node 20)
|
|
55
51
|
cd demo
|
|
56
52
|
PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun dev
|
|
57
53
|
PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun build
|
|
58
|
-
PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun deploy
|
|
54
|
+
PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun deploy # → gh-pages
|
|
59
55
|
```
|
|
60
56
|
|
|
57
|
+
**Node version:** library root uses Node 16+ deps (vite@4, vitest@0.34); demo requires Node 20 (Vite 5).
|
|
58
|
+
|
|
61
59
|
---
|
|
62
60
|
|
|
63
61
|
## Public API
|
|
@@ -66,20 +64,28 @@ PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun deploy # → gh-pages
|
|
|
66
64
|
|
|
67
65
|
```tsx
|
|
68
66
|
<FluidText text="Hello" fontSize={120} color="#fff" algorithm="glass" preset="neon" />
|
|
69
|
-
<FluidImage src="/hero.jpg" algorithm="aurora"
|
|
67
|
+
<FluidImage src="/hero.jpg" algorithm="aurora" pixelRatio={1} simResolution={0.5} />
|
|
70
68
|
```
|
|
71
69
|
|
|
70
|
+
All `FluidConfig` fields are **flat optional props** on both components — there is no nested `config` object. `FluidBaseProps extends Partial<FluidConfig>` to avoid duplicate field declarations.
|
|
71
|
+
|
|
72
72
|
### FluidHandle ref
|
|
73
73
|
|
|
74
74
|
```ts
|
|
75
75
|
interface FluidHandle {
|
|
76
76
|
reset(): void;
|
|
77
|
-
move(
|
|
78
|
-
splat(x: number, y: number,
|
|
77
|
+
move(x: number, y: number, strength?: number): void;
|
|
78
|
+
splat(x: number, y: number, velocityX: number, velocityY: number, strength?: number): void;
|
|
79
79
|
updateConfig(config: Partial<FluidConfig>): void;
|
|
80
80
|
}
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
+
`splat()` writes directly to velocity+density FBOs — safe to call multiple times per frame. `move()` goes through the mouse-state machine (one splat per sim step, last-write-wins).
|
|
84
|
+
|
|
85
|
+
### Quality props
|
|
86
|
+
|
|
87
|
+
`pixelRatio` and `simResolution` are flat top-level props (both `number`, range `0.1–1`). They map to internal `FluidQuality = { dpr, sim }` at the controller boundary — `FluidQuality` is an internal type, not a component prop.
|
|
88
|
+
|
|
83
89
|
### Algorithms
|
|
84
90
|
`'standard'` · `'glass'` · `'ink'` · `'aurora'` · `'ripple'`
|
|
85
91
|
|
|
@@ -88,15 +94,24 @@ interface FluidHandle {
|
|
|
88
94
|
|
|
89
95
|
---
|
|
90
96
|
|
|
91
|
-
##
|
|
97
|
+
## Normalized prop API
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
Seven simulation props accept a **normalized `0–1` value** instead of raw physics units. `normalizeConfig` (in `src/core/config.ts`) maps them to physics range before the sim receives them. Values outside `[0, 1]` pass through unchanged as raw physics overrides.
|
|
100
|
+
|
|
101
|
+
Normalized fields: `densityDissipation`, `velocityDissipation`, `splatRadius`, `splatForce`, `specularExp`, `shine`, `warpStrength`.
|
|
102
|
+
|
|
103
|
+
`DEFAULT_CONFIG` and presets store normalized values; `normalizeConfig` converts them to physics. See `PROP_RANGES` in `src/core/config.ts` for the exact min/max mappings.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Simulation pipeline (per frame, `simulation.ts#step`)
|
|
108
|
+
|
|
109
|
+
1. Advect velocity (obstacle mask zeroes inside text/image)
|
|
110
|
+
2. Advect density
|
|
111
|
+
3. Curl → vorticity confinement
|
|
112
|
+
4. Splat — mouse move OR direct `splat()` calls → velocity + density FBOs
|
|
113
|
+
5. Divergence → pressure solve (N iterations) → gradient subtract
|
|
114
|
+
6. Display pass: 5 texture units bound (`uTexture`, `uObstacle`, `uBackground`, `uCoverage`, `uVelocity`); uniforms: `uAlgorithm`, `uWarpStrength`, `uWaterColor`, `uGlowColor`, `uRefraction`, `uSpecularExp`, `uShine`
|
|
100
115
|
|
|
101
116
|
---
|
|
102
117
|
|
|
@@ -104,45 +119,116 @@ interface FluidHandle {
|
|
|
104
119
|
|
|
105
120
|
| Rule | Reason |
|
|
106
121
|
|------|--------|
|
|
107
|
-
| `transferControlToOffscreen` called at most once per canvas | Irreversible — double-mount in StrictMode
|
|
108
|
-
| `useFluid` creates a **fresh** `<canvas>`
|
|
122
|
+
| `transferControlToOffscreen` called at most once per canvas | Irreversible — double-mount in React StrictMode crashes |
|
|
123
|
+
| `useFluid` creates a **fresh** `<canvas>` each mount inside a container `<div>`, removes on cleanup | StrictMode safety |
|
|
109
124
|
| `createBlit` sets up vertex buffers **once** | Performance — never call `vertexAttribPointer` per draw |
|
|
110
|
-
| Worker `.terminate()` always deferred
|
|
111
|
-
| Display shader outputs **premultiplied alpha** | Transparent canvas compositing correctness |
|
|
112
|
-
|
|
|
125
|
+
| Worker `.terminate()` always deferred 50 ms after `postMessage({type:'destroy'})` | Lets the worker flush its destroy sequence |
|
|
126
|
+
| Display shader outputs **premultiplied alpha** (`vec4(color * alpha, alpha)`) | Transparent canvas compositing correctness |
|
|
127
|
+
| Text mode: `coverageTex === obstacleTex` (same ref); guard against double-delete in `#disposeTextures` | Memory safety |
|
|
128
|
+
| Background colour samples masked by coverage: `mix(uWaterColor, texture2D(uBackground, uv).rgb, coverage)` | Prevents CSS `backgroundColor` contamination in transparent areas |
|
|
113
129
|
|
|
114
130
|
---
|
|
115
131
|
|
|
116
|
-
##
|
|
132
|
+
## Critical implementation notes
|
|
133
|
+
|
|
134
|
+
### React StrictMode + OffscreenCanvas
|
|
135
|
+
|
|
136
|
+
`transferControlToOffscreen` is irreversible. StrictMode double-mounts → double transfer → crash. Fix: `useFluid.ts` creates a fresh `<canvas>` per mount; `FluidController` has a try/catch fallback to main-thread mode.
|
|
137
|
+
|
|
138
|
+
### Worker destroy pattern
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
destroy() {
|
|
142
|
+
const worker = this.#worker;
|
|
143
|
+
this.#worker = null; // null first — prevents double-use
|
|
144
|
+
worker.postMessage({ type: 'destroy' });
|
|
145
|
+
setTimeout(() => worker.terminate(), 50); // captured ref is safe
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Transparent canvas
|
|
150
|
+
|
|
151
|
+
WebGL context: `alpha: true`. `gl.clearColor(0,0,0,0)`. Coverage texture (`uCoverage`) = binary mask of content area → empty space is transparent → CSS `backgroundColor` prop shows through.
|
|
152
|
+
|
|
153
|
+
- Text mode: `coverageTex === obstacleTex`
|
|
154
|
+
- Image mode: separate white-rect coverage texture
|
|
117
155
|
|
|
118
|
-
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
156
|
+
### DPR-aware sizing
|
|
157
|
+
|
|
158
|
+
All canvas sizing multiplies `clientWidth/clientHeight` by `window.devicePixelRatio * clampedDprRef.current`. The ResizeObserver reads a ref (not a closure) so it always uses the current `pixelRatio`. Mouse/splat CSS-pixel coordinates are multiplied by `#dpr` before normalising to UV space.
|
|
159
|
+
|
|
160
|
+
### Preset + config reactivity
|
|
161
|
+
|
|
162
|
+
`useEffect([preset, configKey])` calls `updateConfig(mergeConfig(configProps, preset))` whenever any flat config prop or preset changes. `configKey = JSON.stringify(configProps)` is the effect dep.
|
|
163
|
+
|
|
164
|
+
### Quality reactivity
|
|
165
|
+
|
|
166
|
+
`useEffect([pixelRatio, simResolution])` updates `clampedDprRef`, calls `controller.updateQuality(...)`, then `controller.resize(w * newDpr, h * newDpr)`. Compares against `prevQualityRef` to skip first mount and unchanged values.
|
|
167
|
+
|
|
168
|
+
### backgroundSrc bitmap lifecycle
|
|
169
|
+
|
|
170
|
+
Main thread loads bitmap via `loadImageBitmap()`, transfers to worker zero-copy via `postMessage([bitmap])`. Worker stores in `#backgroundBitmap`. Old bitmap `.close()`d on change; `.close()` again on destroy.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Code style
|
|
175
|
+
|
|
176
|
+
- **TypeScript strict** — no `any`, no non-null assertions without an explanatory comment
|
|
177
|
+
- **No file extensions** on imports within `src/` (e.g. `from './config'`)
|
|
178
|
+
Exception: worker import keeps `.js?worker&inline` (Vite query string must be adjacent to the path)
|
|
179
|
+
- **Named exports only** from library modules; no default exports
|
|
180
|
+
- **No comments** unless the *why* is non-obvious
|
|
181
|
+
- **Formatting:** Prettier with `@trivago/prettier-plugin-sort-imports`
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## What agents must not do
|
|
186
|
+
|
|
187
|
+
- Edit files under `dist/` — build artefacts only
|
|
188
|
+
- Add `// eslint-disable` or `@ts-ignore` without a comment explaining why
|
|
189
|
+
- Introduce new peer dependencies without updating `peerDependencies` in `package.json`
|
|
190
|
+
- Call `vertexAttribPointer` inside the render loop
|
|
191
|
+
- Call `transferControlToOffscreen` more than once per canvas element
|
|
192
|
+
- Use `window.*` APIs inside `worker/index.ts` (worker context has no `window`)
|
|
193
|
+
- Commit if `bun test:claude` fails — all tests must pass
|
|
194
|
+
- Change the module format from ESM to CJS — consumers depend on tree-shaking
|
|
195
|
+
- Import types from `globals.d.ts` — they are globally ambient after install
|
|
127
196
|
|
|
128
197
|
---
|
|
129
198
|
|
|
130
199
|
## Testing notes
|
|
131
200
|
|
|
132
|
-
|
|
133
|
-
|
|
201
|
+
Tests run under jsdom with a WebGL mock (`tests/setup.js`). `navigator.gpu` is absent — all tests exercise the WebGL path.
|
|
202
|
+
|
|
203
|
+
- `FluidText`/`FluidImage` are `forwardRef` ExoticComponents — check `$$typeof`, not `typeof`
|
|
134
204
|
- React component mocks require `.ts` extension: `vi.mock('../../src/fluid-controller.ts', ...)`
|
|
135
205
|
- Worker mock: `vi.mock('../src/worker/index.ts?worker&inline', ...)`
|
|
206
|
+
- Run with `bun test:claude`; add tests for any new behaviour
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## GitHub issue labels
|
|
211
|
+
|
|
212
|
+
Every issue carries three label groups. Use all three when filing or picking up work.
|
|
213
|
+
|
|
214
|
+
**`type:*`** — `type:bug` · `type:feature` · `type:improvement` · `type:docs` · `type:refactor`
|
|
215
|
+
|
|
216
|
+
**`domain:*`** — `domain:core` · `domain:render` · `domain:physics` · `domain:react` · `domain:dx`
|
|
217
|
+
|
|
218
|
+
**`effort:*`** — `effort:1` (trivial, <30 min) · `effort:2` (few hours) · `effort:3` (half–full day) · `effort:4` (multi-day) · `effort:5` (architectural)
|
|
219
|
+
|
|
220
|
+
Full label reference: [CONTRIBUTING.md § Labels](./CONTRIBUTING.md#labels)
|
|
136
221
|
|
|
137
222
|
---
|
|
138
223
|
|
|
139
224
|
## Where to find more
|
|
140
225
|
|
|
141
|
-
| Resource | Path |
|
|
142
|
-
|
|
143
|
-
|
|
|
144
|
-
|
|
|
226
|
+
| Resource | Path / URL |
|
|
227
|
+
|----------|------------|
|
|
228
|
+
| Simulation config defaults + presets | [src/core/config.ts](./src/core/config.ts) |
|
|
229
|
+
| Ambient type declarations | [src/globals.d.ts](./src/globals.d.ts) |
|
|
230
|
+
| Bug/feature backlog | [backlog.md](./backlog.md) |
|
|
145
231
|
| Version history | [changelog.md](./changelog.md) |
|
|
146
|
-
|
|
|
147
|
-
| npm
|
|
232
|
+
| Demo examples | [demo/src/examples/](./demo/src/examples/) |
|
|
233
|
+
| npm | https://www.npmjs.com/package/@jayf0x/fluidity-js |
|
|
148
234
|
| Live demo | https://jayf0x.github.io/fluidity |
|
package/CONTRIBUTING.md
CHANGED
|
@@ -40,6 +40,37 @@ PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun dev
|
|
|
40
40
|
| Fix React lifecycle | `src/react/useFluid.ts`, `src/fluid-controller.ts` |
|
|
41
41
|
| Fix worker communication | `src/worker/index.ts`, `src/fluid-controller.ts` |
|
|
42
42
|
|
|
43
|
+
## Labels
|
|
44
|
+
|
|
45
|
+
Every issue carries three label groups:
|
|
46
|
+
|
|
47
|
+
### `type:*`
|
|
48
|
+
| Label | Use for |
|
|
49
|
+
|-------|---------|
|
|
50
|
+
| `type:bug` | Something broken |
|
|
51
|
+
| `type:feature` | New prop, algorithm, or API |
|
|
52
|
+
| `type:improvement` | Perf, visual quality, or correctness enhancement |
|
|
53
|
+
| `type:docs` | Documentation fix |
|
|
54
|
+
| `type:refactor` | Code restructure, no behaviour change |
|
|
55
|
+
|
|
56
|
+
### `domain:*`
|
|
57
|
+
| Label | Scope |
|
|
58
|
+
|-------|-------|
|
|
59
|
+
| `domain:core` | Props, config, public API |
|
|
60
|
+
| `domain:render` | Shaders, FBOs, display pass |
|
|
61
|
+
| `domain:physics` | Fluid solver, pressure, advection |
|
|
62
|
+
| `domain:react` | React components and hooks |
|
|
63
|
+
| `domain:dx` | Build, packaging, DX |
|
|
64
|
+
|
|
65
|
+
### `effort:*`
|
|
66
|
+
| Label | Meaning |
|
|
67
|
+
|-------|---------|
|
|
68
|
+
| `effort:1` | Trivial — under 30 min |
|
|
69
|
+
| `effort:2` | Small — a few hours |
|
|
70
|
+
| `effort:3` | Medium — half to full day |
|
|
71
|
+
| `effort:4` | Large — multi-day |
|
|
72
|
+
| `effort:5` | Major — architectural change |
|
|
73
|
+
|
|
43
74
|
## Reporting issues
|
|
44
75
|
|
|
45
|
-
Use the GitHub issue templates
|
|
76
|
+
Use the GitHub issue templates. Pick the template that matches your issue type and add the appropriate `type:*`, `domain:*`, and `effort:*` labels.
|
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ export const Interactive = () => {
|
|
|
74
74
|
| -------------------------------- | --------------------------------------------------------- |
|
|
75
75
|
| `reset()` | Restart the simulation |
|
|
76
76
|
| `updateConfig(patch)` | Change any config value live |
|
|
77
|
-
| `move(
|
|
77
|
+
| `move(x, y, strength?)` | Simulate a pointer move |
|
|
78
78
|
| `splat(x, y, vx, vy, strength?)` | Inject fluid directly — safe to call many times per frame |
|
|
79
79
|
|
|
80
80
|
---
|
|
@@ -101,20 +101,33 @@ export const Interactive = () => {
|
|
|
101
101
|
|
|
102
102
|
### Shared props
|
|
103
103
|
|
|
104
|
-
| Prop
|
|
105
|
-
|
|
|
106
|
-
| `
|
|
107
|
-
| `preset`
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
104
|
+
| Prop | Type | Default |
|
|
105
|
+
| --------------------- | ------------------ | ------------ |
|
|
106
|
+
| `algorithm` | `FluidAlgorithm` | `'aurora'` |
|
|
107
|
+
| `preset` | `PresetKey` | — |
|
|
108
|
+
| `pixelRatio` | `number` | `1` |
|
|
109
|
+
| `simResolution` | `number` | `0.5` |
|
|
110
|
+
| `densityDissipation` | `number` | `0.83` |
|
|
111
|
+
| `velocityDissipation` | `number` | `0.91` |
|
|
112
|
+
| `pressureIterations` | `number` | `1` |
|
|
113
|
+
| `curl` | `number` | `0` |
|
|
114
|
+
| `splatRadius` | `number` | `0.1` |
|
|
115
|
+
| `splatForce` | `number` | `0.08` |
|
|
116
|
+
| `refraction` | `number` | `1.0` |
|
|
117
|
+
| `specularExp` | `number` | `0` |
|
|
118
|
+
| `shine` | `number` | `0` |
|
|
119
|
+
| `waterColor` | `FluidColor` | `'#000000'` |
|
|
120
|
+
| `glowColor` | `FluidColor` | `'#b3d9ff'` |
|
|
121
|
+
| `warpStrength` | `number` | `0.04` |
|
|
122
|
+
| `backgroundColor` | `string` | `'#0a0a0a'` |
|
|
123
|
+
| `backgroundSrc` | `string` | — |
|
|
124
|
+
| `backgroundSize` | `string \| number` | `'cover'` |
|
|
125
|
+
| `mouseEnabled` | `boolean` | `true` |
|
|
126
|
+
| `workerEnabled` | `boolean` | `true` |
|
|
127
|
+
| `webGPUEnabled` | `boolean` | `true` |
|
|
128
|
+
| `alphaEnabled` | `boolean` | `true` |
|
|
129
|
+
| `className` | `string` | — |
|
|
130
|
+
| `style` | `CSSProperties` | — |
|
|
118
131
|
|
|
119
132
|
---
|
|
120
133
|
|
|
@@ -122,15 +135,15 @@ export const Interactive = () => {
|
|
|
122
135
|
|
|
123
136
|
| Value | Vibe |
|
|
124
137
|
| ------------ | ------------------------------------------------ |
|
|
125
|
-
| `'
|
|
138
|
+
| `'aurora'` | Liquid metal / lava-lamp (default) |
|
|
139
|
+
| `'standard'` | Colour overlay + gentle refraction |
|
|
126
140
|
| `'glass'` | Bent-glass distortion, no colour |
|
|
127
141
|
| `'ink'` | Dense opaque pigment that accumulates and stains |
|
|
128
|
-
| `'aurora'` | Liquid metal / lava-lamp |
|
|
129
142
|
| `'ripple'` | Still water surface with Fresnel rim |
|
|
130
143
|
|
|
131
144
|
```tsx
|
|
132
145
|
<FluidImage src="/photo.jpg" algorithm="aurora" />
|
|
133
|
-
<FluidText text="fluid" algorithm="ripple"
|
|
146
|
+
<FluidText text="fluid" algorithm="ripple" warpStrength={0.03} />
|
|
134
147
|
```
|
|
135
148
|
|
|
136
149
|
---
|
|
@@ -139,37 +152,39 @@ export const Interactive = () => {
|
|
|
139
152
|
|
|
140
153
|
Control rendering resolution on two independent axes — both reactive at runtime.
|
|
141
154
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
155
|
+
| Prop | Range | Default | What it does |
|
|
156
|
+
| --------------- | ----- | ------- | ----------------------------------------------------------------------------------------- |
|
|
157
|
+
| `pixelRatio` | 0.1–1 | `1` | Canvas resolution as fraction of devicePixelRatio. `0.5` on Retina saves ~75% GPU fill. |
|
|
158
|
+
| `simResolution` | 0.1–1 | `0.5` | Simulation FBO size. Lower = cheaper, less detail. |
|
|
146
159
|
|
|
147
160
|
```tsx
|
|
148
161
|
// Sharp canvas, cheap simulation
|
|
149
|
-
<FluidImage src="/hero.jpg"
|
|
162
|
+
<FluidImage src="/hero.jpg" pixelRatio={1} simResolution={0.2} />
|
|
150
163
|
|
|
151
164
|
// Lower canvas res, full simulation quality
|
|
152
|
-
<FluidImage src="/hero.jpg"
|
|
165
|
+
<FluidImage src="/hero.jpg" pixelRatio={0.5} simResolution={1} />
|
|
153
166
|
```
|
|
154
167
|
|
|
155
168
|
---
|
|
156
169
|
|
|
157
|
-
##
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `warpStrength` | `0.
|
|
170
|
+
## Simulation props reference
|
|
171
|
+
|
|
172
|
+
Simulation props that have a physics range accept a **normalized `0–1` value** — no need to know the raw shader units. Values outside `[0, 1]` are passed through as raw physics values for advanced overrides.
|
|
173
|
+
|
|
174
|
+
| Prop | Default | Range | Physics range | Description |
|
|
175
|
+
| --------------------- | ------- | ------- | --------------- | ----------------------------------------------- |
|
|
176
|
+
| `densityDissipation` | `0.83` | `0–1` | `0.94–1.0` | How long ink lingers |
|
|
177
|
+
| `velocityDissipation` | `0.91` | `0–1` | `0.9–0.999` | How fast fluid slows down |
|
|
178
|
+
| `pressureIterations` | `1` | `1–50` | — | Pressure solve quality vs. cost |
|
|
179
|
+
| `curl` | `0` | `0–1` | — | Swirl intensity |
|
|
180
|
+
| `splatRadius` | `0.1` | `0–1` | `0.001–0.04` | Brush radius |
|
|
181
|
+
| `splatForce` | `0.08` | `0–1` | `0.1–5.0` | Force applied by brush |
|
|
182
|
+
| `refraction` | `1.0` | `0–1` | — | Background warp strength |
|
|
183
|
+
| `specularExp` | `0` | `0–1` | `0.1–10` | Specular highlight sharpness |
|
|
184
|
+
| `shine` | `0` | `0–1` | `0–0.15` | Highlight intensity |
|
|
185
|
+
| `warpStrength` | `0.04` | `0–1` | `0.001–0.1` | UV warp intensity (`aurora` algorithm) |
|
|
186
|
+
| `waterColor` | `#000` | — | — | Base fluid colour (hex or `[R, G, B]` 0–1) |
|
|
187
|
+
| `glowColor` | `#b3d9ff`| — | — | Glow / specular colour (hex or `[R, G, B]` 0–1) |
|
|
173
188
|
|
|
174
189
|
---
|
|
175
190
|
|
package/dist/globals.d.ts
CHANGED
|
@@ -5,15 +5,9 @@ type FluidAlgorithm = 'standard' | 'glass' | 'ink' | 'aurora' | 'ripple';
|
|
|
5
5
|
/** RGB tuple (values 0–1) or a CSS hex string (#RGB, #RRGGBB, #RRGGBBAA — alpha stripped). */
|
|
6
6
|
type FluidColor = [number, number, number] | `#${string}`;
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Granular performance/quality controls. Both axes are independent — you can
|
|
10
|
-
* run a sharp display at a coarser simulation, or vice versa.
|
|
11
|
-
* Reactive: changes after mount are applied on the next animation frame.
|
|
12
|
-
*/
|
|
8
|
+
/** Internal quality object used by FluidController and FluidSimulation. */
|
|
13
9
|
interface FluidQuality {
|
|
14
|
-
/** devicePixelRatio multiplier for canvas backing resolution. Range [0.1, 1]. Default 1 (native). On Retina, 0.5 → 1× pixels (75% less fill). */
|
|
15
10
|
dpr?: number;
|
|
16
|
-
/** Simulation FBO size as a fraction of canvas size. Range [0.1, 1]. Default 0.5 (current behavior). Higher = more fluid detail, more GPU. */
|
|
17
11
|
sim?: number;
|
|
18
12
|
}
|
|
19
13
|
|
|
@@ -42,24 +36,31 @@ interface FluidHandle {
|
|
|
42
36
|
updateConfig(config: Partial<FluidConfig>): void;
|
|
43
37
|
}
|
|
44
38
|
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
/**
|
|
40
|
+
* All FluidConfig fields are inherited as optional props.
|
|
41
|
+
* Set any simulation knob directly on the component — no config object needed.
|
|
42
|
+
*/
|
|
43
|
+
interface FluidBaseProps extends Partial<FluidConfig> {
|
|
44
|
+
/** Applied to the canvas container element. */
|
|
47
45
|
className?: string;
|
|
48
|
-
/**
|
|
46
|
+
/** Applied to the canvas container element. */
|
|
49
47
|
style?: React.CSSProperties;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
/** Canvas backing resolution as a fraction of devicePixelRatio. Range [0.1, 1]. Default 1 (native DPR). */
|
|
49
|
+
pixelRatio?: number;
|
|
50
|
+
/** Simulation FBO size as a fraction of canvas size. Range [0.1, 1]. Default 0.5. */
|
|
51
|
+
simResolution?: number;
|
|
54
52
|
preset?: PresetKey;
|
|
55
|
-
algorithm?: FluidAlgorithm;
|
|
56
53
|
backgroundColor?: string;
|
|
57
54
|
backgroundSrc?: string;
|
|
58
55
|
backgroundSize?: string | number;
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
|
|
56
|
+
/** Forward pointer events to the simulation. Default true. */
|
|
57
|
+
mouseEnabled?: boolean;
|
|
58
|
+
/** Run simulation in a Web Worker via OffscreenCanvas. Default true. */
|
|
59
|
+
workerEnabled?: boolean;
|
|
60
|
+
/** Prefer WebGPU renderer; falls back to WebGL. Default true. */
|
|
61
|
+
webGPUEnabled?: boolean;
|
|
62
|
+
/** Enable transparent canvas. Default true. Set false for a minor perf gain when transparency is not needed. */
|
|
63
|
+
alphaEnabled?: boolean;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
interface FluidTextProps extends FluidBaseProps {
|