particle field planning
All checks were successful
deploy / build-and-deploy (push) Successful in 31s

This commit is contained in:
Josh Bairstow 2026-07-23 23:14:04 +10:00
parent ae60a7c856
commit f4b8e1a7d8
5 changed files with 1685 additions and 0 deletions

View file

@ -0,0 +1,161 @@
# particle-network
A canvas animation of drifting particles connected by proximity lines, with
opacity modulated by a slowly-rolling fBm (fractal Brownian motion) noise field.
No external dependencies. Pure ES module.
---
## Files
| File | Purpose |
|------|---------|
| `particleNetwork.js` | The module — import this into your project |
| `demo.html` | Minimal working example (requires a local server for ES module import) |
---
## Quick start
Serve the folder with any static server, e.g.:
```bash
npx serve .
# or
python3 -m http.server
```
Then open `demo.html`.
---
## Integration
### 1. Minimal — full-page background
```html
<canvas id="net"></canvas>
<script type="module">
import { ParticleNetwork } from './particleNetwork.js';
const canvas = document.getElementById('net');
// Size the canvas to the viewport yourself, or let the module
// read the parent element's bounding rect (default behaviour).
canvas.style.display = 'block';
canvas.style.width = '100%';
const net = new ParticleNetwork(canvas, null);
net.start();
</script>
```
### 2. With field preview (debug / design mode)
```html
<canvas id="net"></canvas>
<canvas id="field"></canvas>
<script type="module">
import { ParticleNetwork } from './particleNetwork.js';
const net = new ParticleNetwork(
document.getElementById('net'),
document.getElementById('field'),
{ count: 60, dimAtMin: 0.5 }
);
net.start();
</script>
```
### 3. Bundler / framework
Copy `particleNetwork.js` anywhere in your `src/` tree and import normally:
```js
import { ParticleNetwork } from '@/lib/particleNetwork.js';
```
No build config changes needed — it uses only standard browser APIs.
---
## Constructor
```js
new ParticleNetwork(mainCanvas, fieldCanvas, options)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `mainCanvas` | `HTMLCanvasElement` | The particle animation canvas |
| `fieldCanvas` | `HTMLCanvasElement \| null` | Optional debug field view |
| `options` | `object` | See below |
---
## Options
All options can be passed at construction time and updated at runtime via
`setOption(key, value)`.
| Key | Default | Description |
|-----|---------|-------------|
| `count` | `55` | Number of particles |
| `reach` | `140` | Max connection distance in canvas px |
| `flowSpeed` | `2` | Field drift speed on a 110 scale. At 1, motion takes several minutes to traverse the field — clearly present but takes time to perceive. |
| `fieldScale` | `4` | Noise zoom on a 110 scale. Lower = larger rolling blobs. |
| `dimAtMin` | `0.60` | Opacity reduction at field minimum. `0` = field has no opacity effect. `1` = particles in troughs are fully invisible. |
| `particleSpeed` | `0.35` | Base particle velocity (px/frame) |
| `particleMinR` | `1.4` | Minimum particle radius (px) |
| `particleMaxR` | `2.8` | Maximum particle radius (px) |
| `fieldUpdateMs` | `50` | How often the noise field recalculates (ms). Lower = smoother but more CPU. |
| `fieldOctaves` | `4` | fBm octaves. More = finer detail, more CPU. |
| `bgColor` | `'#040e1f'` | Canvas background colour |
| `lineColor` | `'80,160,240'` | RGB string for particleparticle lines |
| `lineColorMouse` | `'160,220,255'` | RGB string for mouse-proximity lines |
| `canvasAspect` | `0.65` | Canvas height = width × aspect |
| `timeConstantX` | `0.00084` | Primary axis time advance rate. Halve to slow further. |
| `timeConstantY` | `0.0003145` | Secondary axis rate — kept at a different ratio for the swirling character. |
---
## Methods
```js
net.start() // begin animation loop
net.stop() // pause (preserves state)
net.setOption(key, value) // update any option at runtime
net.sampleField(x, y) // returns [0,1] field value at canvas coords
net.destroy() // stop loop and remove all event listeners
```
---
## How it works
**Noise field** — a 4-octave fBm Perlin noise function is evaluated on a tiny
80×52 offscreen canvas every 50ms, then scaled up via `drawImage`. This is
~100× cheaper than evaluating at display resolution.
**Opacity mapping** — each particle's field sample `fv ∈ [0,1]` maps to an
opacity multiplier via:
```
multiplier = (1 - dimAtMin) + dimAtMin * fv
```
At `fv = 1` (field peak): multiplier = 1.0 — full brightness.
At `fv = 0` (field trough): multiplier = 1 - dimAtMin.
For connecting lines the multiplier is the geometric mean of both endpoint
values, so a line straddling a bright and dark zone stays dim.
**Distance falloff** — cubic: `(1 - dist/reach)³`, giving a sharp fade near
the connection threshold rather than a linear fade across the full range.
**Glow** — each particle renders a `createRadialGradient` halo sized to
`r × (2.5 + fieldValue × 1.5)`, so dots in bright field regions bloom larger.