@fundamental-engine/three 0.4.0
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/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/agents.d.ts +73 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +114 -0
- package/dist/agents.js.map +1 -0
- package/dist/backend.d.ts +33 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +120 -0
- package/dist/backend.js.map +1 -0
- package/dist/bodies.d.ts +79 -0
- package/dist/bodies.d.ts.map +1 -0
- package/dist/bodies.js +137 -0
- package/dist/bodies.js.map +1 -0
- package/dist/host.d.ts +23 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +62 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/layer.d.ts +115 -0
- package/dist/layer.d.ts.map +1 -0
- package/dist/layer.js +173 -0
- package/dist/layer.js.map +1 -0
- package/dist/particles.d.ts +58 -0
- package/dist/particles.d.ts.map +1 -0
- package/dist/particles.js +127 -0
- package/dist/particles.js.map +1 -0
- package/dist/project.d.ts +111 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +95 -0
- package/dist/project.js.map +1 -0
- package/dist/samplers.d.ts +81 -0
- package/dist/samplers.d.ts.map +1 -0
- package/dist/samplers.js +143 -0
- package/dist/samplers.js.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zach Shallbetter
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# @fundamental-engine/three
|
|
2
|
+
|
|
3
|
+
**The Three.js door to [`@fundamental-engine/core`](../core)** — run the reciprocal field engine headless and
|
|
4
|
+
render its swarm as a `THREE.Points` layer in your own WebGL scene. The same physics the
|
|
5
|
+
`<field-root>` element and `@fundamental-engine/{vanilla,react}` wrap, bound to a 3D scene instead of the DOM.
|
|
6
|
+
|
|
7
|
+
→ Live manual, Lab, and gallery at **[fundamental-engine.com](https://fundamental-engine.com)**.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm i @fundamental-engine/three three
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`three` is a **peer dependency** — you bring your own version (**≥ 0.147**, the tested floor).
|
|
16
|
+
The package touches only long-stable three APIs (the newest are `InstancedMesh`, r109, and
|
|
17
|
+
`Object3D.clear()`, r123); it is built against modern three (0.169) and r147 is verified live in
|
|
18
|
+
a real integration.
|
|
19
|
+
|
|
20
|
+
### No build step? (CDN / single-file pages)
|
|
21
|
+
|
|
22
|
+
The package is plain ESM, so a page with no bundler consumes it straight from a CDN. Pin the
|
|
23
|
+
`three` peer to **the same revision your page already uses** so the library and your scene share
|
|
24
|
+
one Three.js:
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<script type="module">
|
|
28
|
+
// ?deps pins the peer; match it to your page's three version
|
|
29
|
+
import * as FieldUI from "https://esm.sh/@fundamental-engine/three@0.3.1?deps=three@0.147.0";
|
|
30
|
+
window.FieldUI = FieldUI; // hand it to classic scripts
|
|
31
|
+
window.dispatchEvent(new Event("fieldui-ready")); // module scripts are deferred — signal readiness
|
|
32
|
+
</script>
|
|
33
|
+
<script>
|
|
34
|
+
// a classic script can't await the module — start on the ready signal
|
|
35
|
+
function startField() {
|
|
36
|
+
const layer = window.FieldUI.createFieldLayer({ /* … */ });
|
|
37
|
+
// scene.add(layer.object); tick in your render loop
|
|
38
|
+
}
|
|
39
|
+
if (window.FieldUI) startField();
|
|
40
|
+
else window.addEventListener("fieldui-ready", startField, { once: true });
|
|
41
|
+
</script>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Prefer a fully offline page? Bundle once with esbuild and commit the artifact, mapping the peer
|
|
45
|
+
onto your page's global `THREE`:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
echo "module.exports = window.THREE" > three-shim.cjs
|
|
49
|
+
npx esbuild node_modules/@fundamental-engine/three/dist/index.js --bundle --format=iife \
|
|
50
|
+
--global-name=FieldUI --alias:three=./three-shim.cjs --outfile=vendor/field-ui-three.js
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## The particle bridge
|
|
54
|
+
|
|
55
|
+
The engine runs in signals-only mode (`render: 'none'`) and exposes its particle pool through
|
|
56
|
+
`FieldHandle.readParticles()`. `FieldLayer` pulls that each frame and writes it onto a `THREE.Points`
|
|
57
|
+
geometry via a `FieldProjection`.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import * as THREE from 'three';
|
|
61
|
+
import { createFieldLayer, PlaneProjection } from '@fundamental-engine/three';
|
|
62
|
+
|
|
63
|
+
const scene = new THREE.Scene();
|
|
64
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
65
|
+
|
|
66
|
+
const layer = createFieldLayer({
|
|
67
|
+
projection: new PlaneProjection({ relief: 2 }), // the field on a plane, z lifted from heat
|
|
68
|
+
renderer, // reads the device-pixel ratio
|
|
69
|
+
accent: '#4da3ff',
|
|
70
|
+
});
|
|
71
|
+
scene.add(layer.object);
|
|
72
|
+
|
|
73
|
+
renderer.setAnimationLoop(() => {
|
|
74
|
+
layer.tick(); // the engine self-steps; tick() pulls the latest swarm
|
|
75
|
+
renderer.render(scene, camera);
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`FieldLayer` implements the full `FieldHandle` surface, so `layer.burst()`, `layer.flowTo()`,
|
|
80
|
+
`layer.setFormation()`, and `layer.seed()` drive the 3D layer exactly as they drive the DOM field.
|
|
81
|
+
|
|
82
|
+
## Flat plane or real volume
|
|
83
|
+
|
|
84
|
+
`FieldProjection` maps the engine's CSS-pixel field space to 3D world space:
|
|
85
|
+
|
|
86
|
+
- **`PlaneProjection`** — the field on a quad; `z` is lifted stylistically from per-particle `heat`.
|
|
87
|
+
The right choice for a flat field.
|
|
88
|
+
- **`VolumeProjection`** — maps the engine's real **depth lane** (`z ∈ [0, depth)`, the opt-in z axis)
|
|
89
|
+
onto a world depth range, for a genuinely volumetric swarm.
|
|
90
|
+
|
|
91
|
+
Pass `depth` and the layer defaults to a `VolumeProjection` automatically — bodies stay on the page
|
|
92
|
+
plane (`z = 0`) while free matter drifts through the volume and is pulled gently back, reading as depth
|
|
93
|
+
and parallax around the content:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const layer = createFieldLayer({ depth: 300, renderer, accent: '#4da3ff' });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Both projections implement one interface, so `FieldLayer` and `threeBackend` are unchanged by the choice.
|
|
100
|
+
|
|
101
|
+
## RenderBackend (diagnostic overlays)
|
|
102
|
+
|
|
103
|
+
`threeBackend` implements the engine's structural drawing seam (`RenderBackend`), so the diagnostic
|
|
104
|
+
overlays — streamlines, field-lines, grid, contours, force-vector arrows — draw as scene geometry.
|
|
105
|
+
Inject it via the lower-level `createThreeField`:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { createThreeField, threeBackend, PlaneProjection } from '@fundamental-engine/three';
|
|
109
|
+
|
|
110
|
+
const projection = new PlaneProjection();
|
|
111
|
+
const overlay = threeBackend({ projection });
|
|
112
|
+
scene.add(overlay.object);
|
|
113
|
+
|
|
114
|
+
const field = createThreeField({
|
|
115
|
+
viewport: () => ({ ...projection.size(), dpr: renderer.getPixelRatio() }),
|
|
116
|
+
overlayBackend: overlay,
|
|
117
|
+
overlay: 'streamlines',
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The line overlays render fully; numeric label sprites (the `data` reading) are a tracked follow-up.
|
|
122
|
+
|
|
123
|
+
## Building your own field visuals
|
|
124
|
+
|
|
125
|
+
The package re-exports the engine's field samplers — `forceAt` and `netField` — so you can drive
|
|
126
|
+
your own 3D visuals (streamline tubes, vector grids, density volumes) from the live field without a
|
|
127
|
+
second import.
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT © Zach Shallbetter
|
package/dist/agents.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `FieldAgent` — an `Object3D` that rides the field: the creatures primitive (#426). The swarm
|
|
3
|
+
* bridge renders free matter; an agent is a *specific* scene object (a bee, a fish, a drone) that
|
|
4
|
+
* samples the live field at its own position each frame, steers along the force, and writes its
|
|
5
|
+
* world position through the shared `FieldProjection`.
|
|
6
|
+
*
|
|
7
|
+
* Agents are **consumers, not bodies** — they feel the field but exert nothing back unless you also
|
|
8
|
+
* register them with `layer.addBody(...)`. The `sampler` is the small `FieldSampler` interface, so
|
|
9
|
+
* an agent can follow a whole layer, a raw handle, or any custom blend (a wind-only stub, a scaled
|
|
10
|
+
* mix). Pure math + `Object3D` writes: renderer-free and unit-testable.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const bee = new FieldAgent(beeMesh, { projection, sampler: layer, maxSpeed: 90 });
|
|
14
|
+
* // render loop:
|
|
15
|
+
* bee.update(dt);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import type { Object3D } from 'three';
|
|
19
|
+
import type { FieldProjection } from './project.ts';
|
|
20
|
+
import type { FieldSampler } from './samplers.ts';
|
|
21
|
+
export interface FieldAgentOptions {
|
|
22
|
+
/** the 2D↔3D mapping (share the layer's so agents and swarm agree). */
|
|
23
|
+
projection: FieldProjection;
|
|
24
|
+
/** what to follow — anything with `sample(x, y)`: a `FieldLayer`, a `FieldHandle`, a stub. */
|
|
25
|
+
sampler: FieldSampler;
|
|
26
|
+
/** top speed in field px/s (default 80). */
|
|
27
|
+
maxSpeed?: number;
|
|
28
|
+
/** acceleration along the sampled force, px/s² per unit force (default 240). */
|
|
29
|
+
accel?: number;
|
|
30
|
+
/** velocity damping per second — higher coasts to rest sooner in a dead field (default 1.4). */
|
|
31
|
+
drag?: number;
|
|
32
|
+
/** random wander acceleration in px/s² so agents don't railroad one streamline (default 0). */
|
|
33
|
+
wander?: number;
|
|
34
|
+
/** optional world-y hover bob layered on the projected position. */
|
|
35
|
+
hover?: {
|
|
36
|
+
amp: number;
|
|
37
|
+
freq: number;
|
|
38
|
+
};
|
|
39
|
+
/** orient the object along its travel direction each update (default true). */
|
|
40
|
+
faceVelocity?: boolean;
|
|
41
|
+
/** deterministic random source for `wander` (default `Math.random`). */
|
|
42
|
+
rng?: () => number;
|
|
43
|
+
}
|
|
44
|
+
export declare class FieldAgent {
|
|
45
|
+
/** the scene object this agent drives. */
|
|
46
|
+
readonly object: Object3D;
|
|
47
|
+
/** position in field pixels — read/write (set it to spawn or teleport). */
|
|
48
|
+
fieldPosition: {
|
|
49
|
+
x: number;
|
|
50
|
+
y: number;
|
|
51
|
+
};
|
|
52
|
+
/** velocity in field px/s (read/write). */
|
|
53
|
+
velocity: {
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
};
|
|
57
|
+
private readonly projection;
|
|
58
|
+
private readonly sampler;
|
|
59
|
+
private readonly maxSpeed;
|
|
60
|
+
private readonly accel;
|
|
61
|
+
private readonly drag;
|
|
62
|
+
private readonly wander;
|
|
63
|
+
private readonly hover?;
|
|
64
|
+
private readonly faceVelocity;
|
|
65
|
+
private readonly rng;
|
|
66
|
+
private t;
|
|
67
|
+
private readonly phase;
|
|
68
|
+
private readonly look;
|
|
69
|
+
constructor(object: Object3D, opts: FieldAgentOptions);
|
|
70
|
+
/** sample → steer → integrate → write the object's position. Call once per frame with dt in s. */
|
|
71
|
+
update(dt: number): void;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=agents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAIlD,MAAM,WAAW,iBAAiB;IAChC,uEAAuE;IACvE,UAAU,EAAE,eAAe,CAAC;IAC5B,8FAA8F;IAC9F,OAAO,EAAE,YAAY,CAAC;IACtB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gGAAgG;IAChG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wEAAwE;IACxE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,UAAU;IACrB,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,2EAA2E;IAC3E,aAAa,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,2CAA2C;IAC3C,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAEnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAgC;IACvD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,CAAC,CAAK;IACd,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiB;gBAE1B,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,iBAAiB;IAuBrD,kGAAkG;IAClG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;CAyCzB"}
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `FieldAgent` — an `Object3D` that rides the field: the creatures primitive (#426). The swarm
|
|
3
|
+
* bridge renders free matter; an agent is a *specific* scene object (a bee, a fish, a drone) that
|
|
4
|
+
* samples the live field at its own position each frame, steers along the force, and writes its
|
|
5
|
+
* world position through the shared `FieldProjection`.
|
|
6
|
+
*
|
|
7
|
+
* Agents are **consumers, not bodies** — they feel the field but exert nothing back unless you also
|
|
8
|
+
* register them with `layer.addBody(...)`. The `sampler` is the small `FieldSampler` interface, so
|
|
9
|
+
* an agent can follow a whole layer, a raw handle, or any custom blend (a wind-only stub, a scaled
|
|
10
|
+
* mix). Pure math + `Object3D` writes: renderer-free and unit-testable.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const bee = new FieldAgent(beeMesh, { projection, sampler: layer, maxSpeed: 90 });
|
|
14
|
+
* // render loop:
|
|
15
|
+
* bee.update(dt);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import { Vector3 } from 'three';
|
|
19
|
+
const _w = new Vector3();
|
|
20
|
+
export class FieldAgent {
|
|
21
|
+
/** the scene object this agent drives. */
|
|
22
|
+
object;
|
|
23
|
+
/** position in field pixels — read/write (set it to spawn or teleport). */
|
|
24
|
+
fieldPosition;
|
|
25
|
+
/** velocity in field px/s (read/write). */
|
|
26
|
+
velocity;
|
|
27
|
+
projection;
|
|
28
|
+
sampler;
|
|
29
|
+
maxSpeed;
|
|
30
|
+
accel;
|
|
31
|
+
drag;
|
|
32
|
+
wander;
|
|
33
|
+
hover;
|
|
34
|
+
faceVelocity;
|
|
35
|
+
rng;
|
|
36
|
+
t = 0;
|
|
37
|
+
phase;
|
|
38
|
+
look = new Vector3();
|
|
39
|
+
constructor(object, opts) {
|
|
40
|
+
this.object = object;
|
|
41
|
+
this.projection = opts.projection;
|
|
42
|
+
this.sampler = opts.sampler;
|
|
43
|
+
this.maxSpeed = opts.maxSpeed ?? 80;
|
|
44
|
+
this.accel = opts.accel ?? 240;
|
|
45
|
+
this.drag = opts.drag ?? 1.4;
|
|
46
|
+
this.wander = opts.wander ?? 0;
|
|
47
|
+
this.hover = opts.hover;
|
|
48
|
+
this.faceVelocity = opts.faceVelocity ?? true;
|
|
49
|
+
this.rng = opts.rng ?? Math.random;
|
|
50
|
+
this.phase = this.rng() * Math.PI * 2;
|
|
51
|
+
// spawn where the object currently sits (world → field); centre if it has no position yet
|
|
52
|
+
object.getWorldPosition(_w);
|
|
53
|
+
this.fieldPosition = this.projection.toField(_w);
|
|
54
|
+
if (!Number.isFinite(this.fieldPosition.x) || !Number.isFinite(this.fieldPosition.y)) {
|
|
55
|
+
const { width, height } = this.projection.size();
|
|
56
|
+
this.fieldPosition = { x: width / 2, y: height / 2 };
|
|
57
|
+
}
|
|
58
|
+
this.velocity = { x: 0, y: 0 };
|
|
59
|
+
}
|
|
60
|
+
/** sample → steer → integrate → write the object's position. Call once per frame with dt in s. */
|
|
61
|
+
update(dt) {
|
|
62
|
+
if (!(dt > 0))
|
|
63
|
+
return;
|
|
64
|
+
this.t += dt;
|
|
65
|
+
const p = this.fieldPosition;
|
|
66
|
+
const v = this.velocity;
|
|
67
|
+
const f = this.sampler.sample(p.x, p.y);
|
|
68
|
+
v.x += f.x * this.accel * dt;
|
|
69
|
+
v.y += f.y * this.accel * dt;
|
|
70
|
+
if (this.wander > 0) {
|
|
71
|
+
v.x += (this.rng() * 2 - 1) * this.wander * dt;
|
|
72
|
+
v.y += (this.rng() * 2 - 1) * this.wander * dt;
|
|
73
|
+
}
|
|
74
|
+
// drag, then clamp to top speed
|
|
75
|
+
const damp = Math.max(0, 1 - this.drag * dt);
|
|
76
|
+
v.x *= damp;
|
|
77
|
+
v.y *= damp;
|
|
78
|
+
const speed = Math.hypot(v.x, v.y);
|
|
79
|
+
if (speed > this.maxSpeed) {
|
|
80
|
+
v.x = (v.x / speed) * this.maxSpeed;
|
|
81
|
+
v.y = (v.y / speed) * this.maxSpeed;
|
|
82
|
+
}
|
|
83
|
+
p.x += v.x * dt;
|
|
84
|
+
p.y += v.y * dt;
|
|
85
|
+
// keep agents inside the field with a soft edge bounce
|
|
86
|
+
const { width, height } = this.projection.size();
|
|
87
|
+
if (p.x < 0) {
|
|
88
|
+
p.x = 0;
|
|
89
|
+
v.x = Math.abs(v.x);
|
|
90
|
+
}
|
|
91
|
+
if (p.y < 0) {
|
|
92
|
+
p.y = 0;
|
|
93
|
+
v.y = Math.abs(v.y);
|
|
94
|
+
}
|
|
95
|
+
if (p.x > width) {
|
|
96
|
+
p.x = width;
|
|
97
|
+
v.x = -Math.abs(v.x);
|
|
98
|
+
}
|
|
99
|
+
if (p.y > height) {
|
|
100
|
+
p.y = height;
|
|
101
|
+
v.y = -Math.abs(v.y);
|
|
102
|
+
}
|
|
103
|
+
this.projection.toWorld(p.x, p.y, 0, 0, 0, this.object.position);
|
|
104
|
+
if (this.hover)
|
|
105
|
+
this.object.position.y += Math.sin(this.t * this.hover.freq + this.phase) * this.hover.amp;
|
|
106
|
+
if (this.faceVelocity && speed > 1e-3) {
|
|
107
|
+
// look one step ahead along the travel direction, in world space
|
|
108
|
+
this.projection.toWorld(p.x + v.x * 0.1, p.y + v.y * 0.1, 0, 0, 0, this.look);
|
|
109
|
+
this.look.y = this.object.position.y;
|
|
110
|
+
this.object.lookAt(this.look);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=agents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `threeBackend` — a `RenderBackend` (the engine's structural drawing seam, #373) that draws the
|
|
3
|
+
* overlay readings as Three.js geometry instead of a 2D canvas. Inject it via
|
|
4
|
+
* `createField({ overlayBackend })` (or `createThreeField`) and add `backend.object` to your scene.
|
|
5
|
+
*
|
|
6
|
+
* The engine describes WHAT to draw in CSS-pixel primitives; this backend owns HOW — mapping those
|
|
7
|
+
* pixels through the `FieldProjection` onto an overlay plane and batching them into a `LineSegments`
|
|
8
|
+
* (arrows, field-lines, grid, contours, traced paths) and a triangle `Mesh` (the data-chip plates).
|
|
9
|
+
* Per-stroke alpha is premultiplied into the vertex color and composited additively, so magnitude
|
|
10
|
+
* still reads. Geometry is rebuilt on each `clear()` (the engine clears once at the top of every
|
|
11
|
+
* overlay frame), so the scene always shows the latest readings.
|
|
12
|
+
*
|
|
13
|
+
* Scope: the line + rect primitives — i.e. every field-structure overlay — render fully. Text
|
|
14
|
+
* labels (the `data` reading's numeric chips) are not yet drawn; `measureText` is honored so the
|
|
15
|
+
* plates size correctly, and label sprites are a tracked follow-up. The line overlays need no text.
|
|
16
|
+
*/
|
|
17
|
+
import { Group } from 'three';
|
|
18
|
+
import type { RenderBackend } from '@fundamental-engine/core';
|
|
19
|
+
import type { FieldProjection } from './project.ts';
|
|
20
|
+
export interface ThreeBackendOptions {
|
|
21
|
+
/** the 2D↔3D mapping (share the one your `FieldLayer` uses so overlay and swarm align). */
|
|
22
|
+
projection: FieldProjection;
|
|
23
|
+
/** world-space `z` the overlay draws at — put it just in front of a flat field (default 0.01). */
|
|
24
|
+
z?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface ThreeBackend extends RenderBackend {
|
|
27
|
+
/** add this to your scene to show the overlay readings. */
|
|
28
|
+
readonly object: Group;
|
|
29
|
+
/** release GPU resources. */
|
|
30
|
+
dispose(): void;
|
|
31
|
+
}
|
|
32
|
+
export declare function threeBackend(opts: ThreeBackendOptions): ThreeBackend;
|
|
33
|
+
//# sourceMappingURL=backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAIL,KAAK,EAMN,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,aAAa,EAAU,MAAM,0BAA0B,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAIpD,MAAM,WAAW,mBAAmB;IAClC,2FAA2F;IAC3F,UAAU,EAAE,eAAe,CAAC;IAC5B,kGAAkG;IAClG,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;IACvB,6BAA6B;IAC7B,OAAO,IAAI,IAAI,CAAC;CACjB;AAQD,wBAAgB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,YAAY,CA2GpE"}
|
package/dist/backend.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `threeBackend` — a `RenderBackend` (the engine's structural drawing seam, #373) that draws the
|
|
3
|
+
* overlay readings as Three.js geometry instead of a 2D canvas. Inject it via
|
|
4
|
+
* `createField({ overlayBackend })` (or `createThreeField`) and add `backend.object` to your scene.
|
|
5
|
+
*
|
|
6
|
+
* The engine describes WHAT to draw in CSS-pixel primitives; this backend owns HOW — mapping those
|
|
7
|
+
* pixels through the `FieldProjection` onto an overlay plane and batching them into a `LineSegments`
|
|
8
|
+
* (arrows, field-lines, grid, contours, traced paths) and a triangle `Mesh` (the data-chip plates).
|
|
9
|
+
* Per-stroke alpha is premultiplied into the vertex color and composited additively, so magnitude
|
|
10
|
+
* still reads. Geometry is rebuilt on each `clear()` (the engine clears once at the top of every
|
|
11
|
+
* overlay frame), so the scene always shows the latest readings.
|
|
12
|
+
*
|
|
13
|
+
* Scope: the line + rect primitives — i.e. every field-structure overlay — render fully. Text
|
|
14
|
+
* labels (the `data` reading's numeric chips) are not yet drawn; `measureText` is honored so the
|
|
15
|
+
* plates size correctly, and label sprites are a tracked follow-up. The line overlays need no text.
|
|
16
|
+
*/
|
|
17
|
+
import { AdditiveBlending, BufferGeometry, Float32BufferAttribute, Group, LineBasicMaterial, LineSegments, Mesh, MeshBasicMaterial, Vector3, } from 'three';
|
|
18
|
+
const _v = new Vector3();
|
|
19
|
+
/** A lazily-created 2D context purely for text metrics — never rendered to. */
|
|
20
|
+
function metricsContext() {
|
|
21
|
+
if (typeof document === 'undefined')
|
|
22
|
+
return null;
|
|
23
|
+
return document.createElement('canvas').getContext('2d');
|
|
24
|
+
}
|
|
25
|
+
export function threeBackend(opts) {
|
|
26
|
+
const projection = opts.projection;
|
|
27
|
+
const z = opts.z ?? 0.01;
|
|
28
|
+
const linePos = [];
|
|
29
|
+
const lineCol = [];
|
|
30
|
+
const rectPos = [];
|
|
31
|
+
const rectCol = [];
|
|
32
|
+
const lineGeom = new BufferGeometry();
|
|
33
|
+
const lines = new LineSegments(lineGeom, new LineBasicMaterial({ vertexColors: true, transparent: true, depthWrite: false, blending: AdditiveBlending }));
|
|
34
|
+
lines.frustumCulled = false;
|
|
35
|
+
const rectGeom = new BufferGeometry();
|
|
36
|
+
const rects = new Mesh(rectGeom, new MeshBasicMaterial({ vertexColors: true, transparent: true, depthWrite: false, blending: AdditiveBlending }));
|
|
37
|
+
rects.frustumCulled = false;
|
|
38
|
+
const object = new Group();
|
|
39
|
+
object.add(lines, rects);
|
|
40
|
+
const metrics = metricsContext();
|
|
41
|
+
/** map a CSS-pixel point to the overlay plane and push xyz onto `arr` (overlay owns its own z). */
|
|
42
|
+
function pushPoint(arr, x, y) {
|
|
43
|
+
projection.toWorld(x, y, 0, 0, 0, _v); // z/heat/size irrelevant — the overlay draws at a fixed plane
|
|
44
|
+
arr.push(_v.x, _v.y, z);
|
|
45
|
+
}
|
|
46
|
+
/** premultiplied 0..1 rgb (additive compositing turns alpha into intensity). */
|
|
47
|
+
function pushColor(arr, r, g, b, a) {
|
|
48
|
+
arr.push((r / 255) * a, (g / 255) * a, (b / 255) * a);
|
|
49
|
+
}
|
|
50
|
+
function flush() {
|
|
51
|
+
lineGeom.setAttribute('position', new Float32BufferAttribute(Float32Array.from(linePos), 3));
|
|
52
|
+
lineGeom.setAttribute('color', new Float32BufferAttribute(Float32Array.from(lineCol), 3));
|
|
53
|
+
lineGeom.setDrawRange(0, linePos.length / 3);
|
|
54
|
+
rectGeom.setAttribute('position', new Float32BufferAttribute(Float32Array.from(rectPos), 3));
|
|
55
|
+
rectGeom.setAttribute('color', new Float32BufferAttribute(Float32Array.from(rectCol), 3));
|
|
56
|
+
rectGeom.setDrawRange(0, rectPos.length / 3);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
object,
|
|
60
|
+
size() {
|
|
61
|
+
// the projection owns the field→world transform; nothing to size on the GPU here.
|
|
62
|
+
},
|
|
63
|
+
clear() {
|
|
64
|
+
// finalize the frame just drawn, then reset the accumulators for the next one.
|
|
65
|
+
flush();
|
|
66
|
+
linePos.length = 0;
|
|
67
|
+
lineCol.length = 0;
|
|
68
|
+
rectPos.length = 0;
|
|
69
|
+
rectCol.length = 0;
|
|
70
|
+
},
|
|
71
|
+
segments(packed, stroke) {
|
|
72
|
+
for (let i = 0; i + 3 < packed.length; i += 4) {
|
|
73
|
+
pushPoint(linePos, packed[i], packed[i + 1]);
|
|
74
|
+
pushPoint(linePos, packed[i + 2], packed[i + 3]);
|
|
75
|
+
pushColor(lineCol, stroke.r, stroke.g, stroke.b, stroke.alpha);
|
|
76
|
+
pushColor(lineCol, stroke.r, stroke.g, stroke.b, stroke.alpha);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
polyline(points, stroke) {
|
|
80
|
+
// expand the connected polyline into the disjoint-segment buffer (pairs of consecutive points)
|
|
81
|
+
for (let i = 0; i + 3 < points.length; i += 2) {
|
|
82
|
+
pushPoint(linePos, points[i], points[i + 1]);
|
|
83
|
+
pushPoint(linePos, points[i + 2], points[i + 3]);
|
|
84
|
+
pushColor(lineCol, stroke.r, stroke.g, stroke.b, stroke.alpha);
|
|
85
|
+
pushColor(lineCol, stroke.r, stroke.g, stroke.b, stroke.alpha);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
rect(x, y, w, h, r, g, b, alpha) {
|
|
89
|
+
// two triangles (x,y)-(x+w,y+h); the data-chip plate
|
|
90
|
+
const x2 = x + w;
|
|
91
|
+
const y2 = y + h;
|
|
92
|
+
pushPoint(rectPos, x, y);
|
|
93
|
+
pushPoint(rectPos, x2, y);
|
|
94
|
+
pushPoint(rectPos, x2, y2);
|
|
95
|
+
pushPoint(rectPos, x, y);
|
|
96
|
+
pushPoint(rectPos, x2, y2);
|
|
97
|
+
pushPoint(rectPos, x, y2);
|
|
98
|
+
for (let k = 0; k < 6; k++)
|
|
99
|
+
pushColor(rectCol, r, g, b, alpha);
|
|
100
|
+
},
|
|
101
|
+
text() {
|
|
102
|
+
// label sprites are a tracked follow-up; the line overlays need no text, and the chip plate
|
|
103
|
+
// (rect) still renders. measureText is honored so plate sizing stays correct.
|
|
104
|
+
},
|
|
105
|
+
measureText(value) {
|
|
106
|
+
if (metrics) {
|
|
107
|
+
metrics.font = '10px ui-monospace, SFMono-Regular, Menlo, monospace';
|
|
108
|
+
return metrics.measureText(value).width;
|
|
109
|
+
}
|
|
110
|
+
return value.length * 6; // coarse fallback when no 2D context is available
|
|
111
|
+
},
|
|
112
|
+
dispose() {
|
|
113
|
+
lineGeom.dispose();
|
|
114
|
+
rectGeom.dispose();
|
|
115
|
+
lines.material.dispose();
|
|
116
|
+
rects.material.dispose();
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
|
package/dist/bodies.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meshes as bodies — the 3D form of field-ui's core idea ("every element is a body"). A
|
|
3
|
+
* `THREE.Object3D` registers as a field body: it bends the field (bodies are force sources), the
|
|
4
|
+
* swarm responds to it, and the field's local density bends *it* back through feedback. And because
|
|
5
|
+
* a body **carries a data record**, a mesh can be a meaningful agent — a bloom carrying its genome,
|
|
6
|
+
* a hive accumulating honey — not just a force.
|
|
7
|
+
*
|
|
8
|
+
* It needs no core change: the engine builds a body from anything implementing the small element
|
|
9
|
+
* contract (`getAttribute` / `hasAttribute` / `dataset` / `getBoundingClientRect`), so each body is a
|
|
10
|
+
* lightweight non-DOM "virtual element" whose rect is the mesh's position projected onto the field
|
|
11
|
+
* plane, refreshed live as the mesh moves. Feedback is routed to the body via a custom sink, so
|
|
12
|
+
* `density` / `load` / `lit` land on the mesh (drive a uniform, accumulate a value) instead of CSS.
|
|
13
|
+
*/
|
|
14
|
+
import type { Object3D } from 'three';
|
|
15
|
+
import type { FeedbackChannels, FeedbackSink } from '@fundamental-engine/core';
|
|
16
|
+
import type { FieldProjection } from './project.ts';
|
|
17
|
+
export interface FieldBodySpec {
|
|
18
|
+
/** force token(s) the body radiates — e.g. `'attract'`, `'gravity'`, `['charge', 'swirl']`. */
|
|
19
|
+
tokens: string | readonly string[];
|
|
20
|
+
/** force magnitude (`data-strength`). */
|
|
21
|
+
strength?: number;
|
|
22
|
+
/** influence radius in field pixels (`data-range`). */
|
|
23
|
+
range?: number;
|
|
24
|
+
/** swirl / charge sign (`data-spin`). */
|
|
25
|
+
spin?: number;
|
|
26
|
+
/** heading in radians for directional forces (`data-angle`). */
|
|
27
|
+
angle?: number;
|
|
28
|
+
/** the body's tint (`data-color`) — the `pigment` force dyes passing matter with it, so the
|
|
29
|
+
* swarm visibly carries this body's color to wherever it drifts next (conserved color transport). */
|
|
30
|
+
color?: string;
|
|
31
|
+
/** opt into density/load feedback back onto the mesh (default `true`). */
|
|
32
|
+
feedback?: boolean;
|
|
33
|
+
/** the body's box half-size in field pixels — its footprint, not its range (default 20). */
|
|
34
|
+
sizePx?: number;
|
|
35
|
+
/** the carried record: a genome, an inventory, anything. Opaque to the engine; yours to read. */
|
|
36
|
+
data?: unknown;
|
|
37
|
+
/** raw `data-*` passthrough (without the `data-` prefix) for tokens this spec doesn't model. */
|
|
38
|
+
attrs?: Record<string, string>;
|
|
39
|
+
/** called each frame the body has feedback, with its live channels — drive a uniform from here. */
|
|
40
|
+
onFeedback?: (channels: FeedbackChannels, body: FieldBody) => void;
|
|
41
|
+
}
|
|
42
|
+
export interface FieldBody {
|
|
43
|
+
/** the registered scene object. */
|
|
44
|
+
readonly object: Object3D;
|
|
45
|
+
/** the carried record — read it when a pollinator reaches this bloom, mutate it freely. */
|
|
46
|
+
data: unknown;
|
|
47
|
+
/** the body's most recent feedback (density/load/lit/…); `{}` until the field writes. */
|
|
48
|
+
readonly channels: Readonly<FeedbackChannels>;
|
|
49
|
+
/** unregister this body from the field. */
|
|
50
|
+
remove(): void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Manages the mesh-bodies for one field. Provides the `root` the host scans and the `sink` that
|
|
54
|
+
* routes feedback to meshes; a `FieldLayer` wires both. Add via `layer.addBody(...)`.
|
|
55
|
+
*/
|
|
56
|
+
export declare class FieldBodyRegistry {
|
|
57
|
+
private bodies;
|
|
58
|
+
private projection;
|
|
59
|
+
private onChange;
|
|
60
|
+
constructor(projection: FieldProjection);
|
|
61
|
+
/** project a scene object's world position onto the field plane (field pixels). */
|
|
62
|
+
projectToField(object: Object3D): {
|
|
63
|
+
x: number;
|
|
64
|
+
y: number;
|
|
65
|
+
};
|
|
66
|
+
/** internal — the FieldLayer re-points this if the projection changes. */
|
|
67
|
+
setProjection(projection: FieldProjection): void;
|
|
68
|
+
/** internal — the FieldLayer wires this to `field.scan()` so adds/removes re-register. */
|
|
69
|
+
setOnChange(fn: () => void): void;
|
|
70
|
+
/** the scan root the host hands the engine: `[data-body]` queries return the live mesh-bodies. */
|
|
71
|
+
get root(): ParentNode;
|
|
72
|
+
/** the feedback sink the field writes through — lands density/load/lit on the body's mesh. */
|
|
73
|
+
get sink(): FeedbackSink;
|
|
74
|
+
add(object: Object3D, spec: FieldBodySpec): FieldBody;
|
|
75
|
+
remove(body: FieldBody): void;
|
|
76
|
+
/** every registered body (read-only). */
|
|
77
|
+
all(): readonly FieldBody[];
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=bodies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bodies.d.ts","sourceRoot":"","sources":["../src/bodies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAIpD,MAAM,WAAW,aAAa;IAC5B,+FAA+F;IAC/F,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACnC,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;0GACsG;IACtG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4FAA4F;IAC5F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iGAAiG;IACjG,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gGAAgG;IAChG,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,mGAAmG;IACnG,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;CACpE;AAED,MAAM,WAAW,SAAS;IACxB,mCAAmC;IACnC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,2FAA2F;IAC3F,IAAI,EAAE,OAAO,CAAC;IACd,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC9C,2CAA2C;IAC3C,MAAM,IAAI,IAAI,CAAC;CAChB;AAsFD;;;GAGG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,QAAQ,CAAwB;gBAE5B,UAAU,EAAE,eAAe;IAIvC,mFAAmF;IACnF,cAAc,CAAC,MAAM,EAAE,QAAQ,GAAG;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;IAK1D,0EAA0E;IAC1E,aAAa,CAAC,UAAU,EAAE,eAAe,GAAG,IAAI;IAIhD,0FAA0F;IAC1F,WAAW,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAIjC,kGAAkG;IAClG,IAAI,IAAI,IAAI,UAAU,CAMrB;IAED,8FAA8F;IAC9F,IAAI,IAAI,IAAI,YAAY,CAIvB;IAED,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,GAAG,SAAS;IAOrD,MAAM,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAQ7B,yCAAyC;IACzC,GAAG,IAAI,SAAS,SAAS,EAAE;CAG5B"}
|