@chromatic-coherence/generative-engine 1.2.0 → 1.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/README.md CHANGED
@@ -1,130 +1,145 @@
1
- <div align="center">
2
-
3
- # generative-engine
4
-
5
- ### **THE THIRD ENGINE** — the [Generative Charts](https://chromaticcoherence.ai/generative-charts) family grows into a full **WebGL2 graphics engine**.
6
-
7
- [![licence: source-available](https://img.shields.io/badge/licence-source--available-1e88e5)](./LICENSE)
8
- [![zero dependencies](https://img.shields.io/badge/dependencies-0-success)](#)
9
-
10
- **[Live scenes →](https://chromaticcoherence.ai/generative-engine)** · **[The charts sibling →](https://www.npmjs.com/package/@chromatic-coherence/generative-charts)**
11
-
12
- </div>
13
-
14
- ---
15
-
16
- The charts library stays what it is — small, stable, chart-first. **generative-engine** is where
17
- the family expands: the same API (`createWorld` · `face` · `line` · `glow` · `grow` · `draw*` ·
18
- `fade`), so chart recipes port forward by changing one import — and underneath it, a real
19
- graphics engine. Zero dependencies; the browser is the only runtime.
20
-
21
- ```bash
22
- npm install @chromatic-coherence/generative-engine
23
- ```
24
-
25
- ```js
26
- import { createWorld, v3, icosphere, heightfield } from "@chromatic-coherence/generative-engine";
27
-
28
- const w = createWorld({ canvas, controls: true, shadows: true });
29
- w.lights.push({ p: v3(-160, 320, 120), intensity: 1.2, falloff: 500 });
30
- w.mesh(heightfield((x, z) => Math.sin(x * 0.01) * 20, 800, 800), { hue: 262, sat: 38, light: 26 });
31
- w.meshMorph(icosphere(70, 3), myBlob, { hue: 350, sat: 30, light: 42, gloss: 0.5, group: "blob" });
32
- w.grow((t) => { w.morph("blob", 0.5 + 0.5 * Math.sin(t)); w.setTransform("blob", { rotateY: t * 0.4 }); });
33
- w.start();
34
- ```
35
-
36
- ## What the engine adds over the chart editions
37
-
38
- | | |
39
- |---|---|
40
- | **WebGL2 + HDR post chain** | The scene renders to a floating-point target, then: **bloom** (soft-knee bright pass, half-res gaussian), **SSAO** (depth-based contact shading), one composite pass (ACES filmic, colour grade, vignette), and **FXAA**. All art-directable (`w.bloom`, `w.ssao`, `w.fxaa`), all on by default. `post: false` opts out. |
41
- | **Real shadow maps** | A depth **texture** with hardware-comparison PCF (`sampler2DShadow`) — soft, exact, 2048px by default. `shadows: { size, softness, bias }`. |
42
- | **Geometry stdlib** | `plane` · `box` · `icosphere` · `tube` (parallel-transport sweep, taperable) · `lathe` · `extrude` · `heightfield` · `transformMesh` · `mergeMesh` — all pure math, all unit-tested, uploaded in one `w.mesh(geo, opts)` call with smooth normals. |
43
- | **Ribbons** | `w.ribbon(points, { width })` / `w.drawRibbon(...)` — screen-space-expanded strokes with **real pixel width**, feathered edges, depth-tested. The stroke aesthetic `gl.LINES` never allowed. |
44
- | **Group transforms** | `w.setTransform(group, { translate, rotateY, scale })` — geometry moves rigidly in the vertex stage, zero CPU re-tessellation, shadow pass included. |
45
- | **Morph targets** | `w.meshMorph(geoA, geoB, opts)` + `w.morph(group, t)` — blend between two vertex sets in the shader (breathing, becoming). Eased like `fade`. |
46
- | **8 point lights** | Up from 4; `lights[0]` casts the shadows. HDR intensities welcome — bloom catches what exceeds 1.0. |
47
- | **The carried light model** | Everything the chart edition learned rides along: GGX + Fresnel specular, procedural micro-relief, the perfusion term (`blood`, `bloodPulse`), pore noise, subsurface rim, coloured hemispheric ambient, fog — now resolving toward a real background colour the engine owns. |
48
-
49
- **The ink edition** (`theme: "light"`) still renders any recipe on paper instead of night —
50
- lightness inverts, strokes blend normally, SSAO becomes pencil shading. Bloom stays dark-only.
51
-
52
- ## Objectives — scenes that know where they're going
53
-
54
- A scene gets a **goal** and the engine drives motion toward it; a live **movement meter**
55
- tells you whether the scene is moving or settled — and names what is moving.
56
-
57
- ```ts
58
- import { createWorld, attachObjectives } from "@chromatic-coherence/generative-engine";
59
-
60
- const w = createWorld({ canvas });
61
- const obj = attachObjectives(w);
62
-
63
- obj.define("reveal", {
64
- morph: { group: "terrain", to: 1 }, // blend the terrain to its B shape
65
- camera: { dist: 420, pitch: 0.62 }, // settle the camera in close
66
- fade: { group: "veil", to: 0 }, // dissolve the veil
67
- pace: 3, // seconds to ~95% of the way there
68
- });
69
-
70
- obj.onReading(r => {
71
- // { name, progress, level, floor, moving, settled, driving: ["morph:terrain","camera",...] }
72
- if (r.settled) console.log(`${r.name}: settled`);
73
- });
74
-
75
- // later — the goal itself moves; motion continues from the current state, no snap,
76
- // and the meter lights up again:
77
- obj.retarget("reveal", { camera: { dist: 300 } });
78
- ```
79
-
80
- Channels: `transform` (group rigid-body spec) · `morph` · `fade` · `camera` · `custom`
81
- (an `apply(progress, dt)` escape hatch). `readings()` is the pull form of `onReading`.
82
-
83
- **The meter's semantics** come from the objective-movement measurement work this engine's
84
- org ran first as science: movement is a **level sustained above a settled floor**, not a
85
- discrete event. `driving` names the elevated channels — movement you can name; `settled`
86
- is the sustained fall to the floor. Options: `attachObjectives(w, { floor, settleAfter })`.
87
-
88
- The layer rides the frame loop as a `grow()` hook and drives the World through its public
89
- verbs only — the renderer is untouched, and everything composes with hand-written `grow()`
90
- animation.
91
-
92
- ## Demo
93
-
94
- `demo/index.html` — serve the package folder (`python -m http.server`) and open
95
- `/demo/`. Drag to orbit, ctrl+wheel to zoom; `?theme=light`, `?post=0`, `?still`.
96
-
97
- ## Falling back
98
-
99
- `createWorld` **throws** (never a blank page) when WebGL2 is missing. Catch it and build the
100
- same recipe through the chart editions — the API is a subset, so the fallback chain
101
- engine → charts-3d → charts-2d is three imports of the same code. Browsers cap live GL
102
- contexts per page: on teardown call `dispose(true)` to release the context immediately, and
103
- mount a **fresh** canvas if you rebuild (a lost context is dead on its canvas).
104
-
105
- ## Instancing, depth of field, motion blur (v1.2)
106
-
107
- - **`w.meshInstanced(geo, transforms, opts)`** — upload a mesh once, draw it at every
108
- transform in a single instanced call, shadow pass included. A forest is one draw.
109
- Instances ride their group's fade / morph / transform like any other geometry.
110
- - **`w.dof = { on, focus, range, maxBlur }`** — depth of field: blur grows with distance
111
- from the focus plane (world units), sharp subjects protected per-tap.
112
- - **`w.motionBlur = { on, strength }`** — camera motion blur by depth reprojection into
113
- the previous frame; streaks clamp to 4 % of the screen.
114
-
115
- ## Roadmap
116
-
117
- Planar mirrors. The chart editions stay lean.
118
-
119
- ## Licence
120
-
121
- **Source-available, not open source.** Free for individuals, charities and small businesses
122
- (fewer than 25 people and under £1M annual revenue); a one-off **£100** covers any larger
123
- organisation. Full terms in [LICENSE](./LICENSE); commercial licensing:
124
- [hello@chromaticcoherence.ai](mailto:hello@chromaticcoherence.ai).
125
-
126
- ---
127
-
128
- <div align="center">
129
- <sub>© 2026 Chromatic Coherence. All rights reserved.</sub>
130
- </div>
1
+ <div align="center">
2
+
3
+ # generative-engine
4
+
5
+ ### **THE THIRD ENGINE** — the [Generative Charts](https://chromaticcoherence.ai/generative-charts) family grows into a full **WebGL2 graphics engine**.
6
+
7
+ [![licence: source-available](https://img.shields.io/badge/licence-source--available-1e88e5)](./LICENSE)
8
+ [![zero dependencies](https://img.shields.io/badge/dependencies-0-success)](#)
9
+
10
+ **[Live scenes →](https://chromaticcoherence.ai/generative-engine)** · **[The charts sibling →](https://www.npmjs.com/package/@chromatic-coherence/generative-charts)**
11
+
12
+ </div>
13
+
14
+ ---
15
+
16
+ The charts library stays what it is — small, stable, chart-first. **generative-engine** is where
17
+ the family expands: the same API (`createWorld` · `face` · `line` · `glow` · `grow` · `draw*` ·
18
+ `fade`), so chart recipes port forward by changing one import — and underneath it, a real
19
+ graphics engine. Zero dependencies; the browser is the only runtime.
20
+
21
+ ```bash
22
+ npm install @chromatic-coherence/generative-engine
23
+ ```
24
+
25
+ ```js
26
+ import { createWorld, v3, icosphere, heightfield } from "@chromatic-coherence/generative-engine";
27
+
28
+ const w = createWorld({ canvas, controls: true, shadows: true });
29
+ w.lights.push({ p: v3(-160, 320, 120), intensity: 1.2, falloff: 500 });
30
+ w.mesh(heightfield((x, z) => Math.sin(x * 0.01) * 20, 800, 800), { hue: 262, sat: 38, light: 26 });
31
+ w.meshMorph(icosphere(70, 3), myBlob, { hue: 350, sat: 30, light: 42, gloss: 0.5, group: "blob" });
32
+ w.grow((t) => { w.morph("blob", 0.5 + 0.5 * Math.sin(t)); w.setTransform("blob", { rotateY: t * 0.4 }); });
33
+ w.start();
34
+ ```
35
+
36
+ ## What the engine adds over the chart editions
37
+
38
+ | | |
39
+ |---|---|
40
+ | **WebGL2 + HDR post chain** | The scene renders to a floating-point target, then: **bloom** (soft-knee bright pass, half-res gaussian), **SSAO** (depth-based contact shading), one composite pass (ACES filmic, colour grade, vignette), and **FXAA**. All art-directable (`w.bloom`, `w.ssao`, `w.fxaa`), all on by default. `post: false` opts out. |
41
+ | **Real shadow maps** | A depth **texture** with hardware-comparison PCF (`sampler2DShadow`) — soft, exact, 2048px by default. `shadows: { size, softness, bias }`. |
42
+ | **Geometry stdlib** | `plane` · `box` · `icosphere` · `tube` (parallel-transport sweep, taperable) · `lathe` · `extrude` · `heightfield` · `transformMesh` · `mergeMesh` — all pure math, all unit-tested, uploaded in one `w.mesh(geo, opts)` call with smooth normals. |
43
+ | **Ribbons** | `w.ribbon(points, { width })` / `w.drawRibbon(...)` — screen-space-expanded strokes with **real pixel width**, feathered edges, depth-tested. The stroke aesthetic `gl.LINES` never allowed. |
44
+ | **Group transforms** | `w.setTransform(group, { translate, rotateY, scale })` — geometry moves rigidly in the vertex stage, zero CPU re-tessellation, shadow pass included. |
45
+ | **Morph targets** | `w.meshMorph(geoA, geoB, opts)` + `w.morph(group, t)` — blend between two vertex sets in the shader (breathing, becoming). Eased like `fade`. |
46
+ | **8 point lights** | Up from 4; `lights[0]` casts the shadows. HDR intensities welcome — bloom catches what exceeds 1.0. |
47
+ | **The carried light model** | Everything the chart edition learned rides along: GGX + Fresnel specular, procedural micro-relief, the perfusion term (`blood`, `bloodPulse`), pore noise, subsurface rim, coloured hemispheric ambient, fog — now resolving toward a real background colour the engine owns. |
48
+
49
+ **The ink edition** (`theme: "light"`) still renders any recipe on paper instead of night —
50
+ lightness inverts, strokes blend normally, SSAO becomes pencil shading. Bloom stays dark-only.
51
+
52
+ ## Objectives — scenes that know where they're going
53
+
54
+ A scene gets a **goal** and the engine drives motion toward it; a live **movement meter**
55
+ tells you whether the scene is moving or settled — and names what is moving.
56
+
57
+ ```ts
58
+ import { createWorld, attachObjectives } from "@chromatic-coherence/generative-engine";
59
+
60
+ const w = createWorld({ canvas });
61
+ const obj = attachObjectives(w);
62
+
63
+ obj.define("reveal", {
64
+ morph: { group: "terrain", to: 1 }, // blend the terrain to its B shape
65
+ camera: { dist: 420, pitch: 0.62 }, // settle the camera in close
66
+ fade: { group: "veil", to: 0 }, // dissolve the veil
67
+ pace: 3, // seconds to ~95% of the way there
68
+ });
69
+
70
+ obj.onReading(r => {
71
+ // { name, progress, level, floor, moving, settled, driving: ["morph:terrain","camera",...] }
72
+ if (r.settled) console.log(`${r.name}: settled`);
73
+ });
74
+
75
+ // later — the goal itself moves; motion continues from the current state, no snap,
76
+ // and the meter lights up again:
77
+ obj.retarget("reveal", { camera: { dist: 300 } });
78
+ ```
79
+
80
+ Channels: `transform` (group rigid-body spec) · `morph` · `fade` · `camera` · `custom`
81
+ (an `apply(progress, dt)` escape hatch). `readings()` is the pull form of `onReading`.
82
+
83
+ **The meter's semantics** come from the objective-movement measurement work this engine's
84
+ org ran first as science: movement is a **level sustained above a settled floor**, not a
85
+ discrete event. `driving` names the elevated channels — movement you can name; `settled`
86
+ is the sustained fall to the floor. Options: `attachObjectives(w, { floor, settleAfter })`.
87
+
88
+ The layer rides the frame loop as a `grow()` hook and drives the World through its public
89
+ verbs only — the renderer is untouched, and everything composes with hand-written `grow()`
90
+ animation.
91
+
92
+ ## Demo
93
+
94
+ `demo/index.html` — serve the package folder (`python -m http.server`) and open
95
+ `/demo/`. Drag to orbit, ctrl+wheel to zoom; `?theme=light`, `?post=0`, `?still`.
96
+
97
+ ## Falling back
98
+
99
+ `createWorld` **throws** (never a blank page) when WebGL2 is missing. Catch it and build the
100
+ same recipe through the chart editions — the API is a subset, so the fallback chain
101
+ engine → charts-3d → charts-2d is three imports of the same code. Browsers cap live GL
102
+ contexts per page: on teardown call `dispose(true)` to release the context immediately, and
103
+ mount a **fresh** canvas if you rebuild (a lost context is dead on its canvas).
104
+
105
+ ## Instancing, depth of field, motion blur (v1.2)
106
+
107
+ - **`w.meshInstanced(geo, transforms, opts)`** — upload a mesh once, draw it at every
108
+ transform in a single instanced call, shadow pass included. A forest is one draw.
109
+ Instances ride their group's fade / morph / transform like any other geometry.
110
+ - **`w.dof = { on, focus, range, maxBlur }`** — depth of field: blur grows with distance
111
+ from the focus plane (world units), sharp subjects protected per-tap.
112
+ - **`w.motionBlur = { on, strength }`** — camera motion blur by depth reprojection into
113
+ the previous frame; streaks clamp to 4 % of the screen.
114
+
115
+ ## GPU wind (v1.3)
116
+
117
+ `meshInstanced(geo, transforms, { ..., sway: { amp, freq? } })` — every instance sways
118
+ individually in the vertex stage (shadow pass included): per-instance scattered phase,
119
+ height-scaled displacement, a second harmonic so the motion breathes. `w.wind = { dir,
120
+ speed }` is the one wind that blows through every swaying batch. Zero CPU per frame.
121
+
122
+ ## GPU flight (v1.4)
123
+
124
+ `meshInstanced(wingMesh, Array.from({ length: 10000 }, () => ({})), { flight: true })` —
125
+ the batch IS a murmuration: per-bird orbit parameters ride the instance buffer, and the
126
+ whole flock (a wandering heart, lobes that split and merge, a ripple phase, flapping
127
+ wings) is evaluated in the vertex stage from time alone. Ten thousand birds, zero CPU.
128
+ The instance matrices are the shared flock frame — place, scale and steer it whole.
129
+
130
+ ## Roadmap
131
+
132
+ Planar mirrors · real water · skeletal figures. The chart editions stay lean.
133
+
134
+ ## Licence
135
+
136
+ **Source-available, not open source.** Free for individuals, charities and small businesses
137
+ (fewer than 25 people and under £1M annual revenue); a one-off **£100** covers any larger
138
+ organisation. Full terms in [LICENSE](./LICENSE); commercial licensing:
139
+ [hello@chromaticcoherence.ai](mailto:hello@chromaticcoherence.ai).
140
+
141
+ ---
142
+
143
+ <div align="center">
144
+ <sub>© 2026 Chromatic Coherence. All rights reserved.</sub>
145
+ </div>
package/dist/shaders.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export declare const FACE_VS = "#version 300 es\nin vec3 aPos; in vec3 aNorm; in vec4 aCol; in float aBlood; in float aGrp;\nin vec3 aPos2; in vec3 aNorm2;\nuniform mat4 uVP; uniform mat4 uSVP;\nuniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];\nout vec3 vPos; out vec3 vNorm; out vec4 vCol; out float vZ; out vec4 vSh;\nout float vGA; out float vBlood;\nvoid main(){\n int g = int(aGrp + 0.5);\n float w = uMW[g];\n vec3 p = mix(aPos, aPos2, w), n = mix(aNorm, aNorm2, w);\n vec4 wp = uGM[g] * vec4(p, 1.0);\n vPos = wp.xyz; vNorm = uNM[g] * n; vCol = aCol; vGA = uGA[g]; vBlood = aBlood;\n vec4 cp = uVP * wp; vZ = cp.w; vSh = uSVP * wp; gl_Position = cp;\n}";
2
2
  export declare const FACE_FS = "#version 300 es\nprecision highp float;\nin vec3 vPos; in vec3 vNorm; in vec4 vCol; in float vZ; in vec4 vSh;\nin float vGA; in float vBlood;\nuniform vec3 uLdir; uniform float uAmb; uniform float uDif; uniform float uSigned;\nuniform vec3 uLp[8]; uniform float uLi[8]; uniform float uLf[8]; uniform int uLn;\nuniform vec3 uCam; uniform float uFogK; uniform float uSpec; uniform vec3 uBg;\nuniform vec3 uSkyC; uniform vec3 uGroundC; uniform float uPulse;\nuniform float uShadowOn; uniform float uShadowSoft; uniform float uShadowBias;\nuniform lowp sampler2DShadow uShadow; uniform float uShadowPx;\nout vec4 fragColor;\nfloat h3(vec3 p){ return fract(sin(dot(p, vec3(12.9898, 78.233, 37.719))) * 43758.5453); }\nfloat vnoise(vec3 p){ vec3 i = floor(p), f = fract(p); f = f * f * (3.0 - 2.0 * f);\n return mix(mix(mix(h3(i), h3(i + vec3(1,0,0)), f.x), mix(h3(i + vec3(0,1,0)), h3(i + vec3(1,1,0)), f.x), f.y),\n mix(mix(h3(i + vec3(0,0,1)), h3(i + vec3(1,0,1)), f.x), mix(h3(i + vec3(0,1,1)), h3(i + vec3(1,1,1)), f.x), f.y), f.z); }\nfloat shadowFactor(){\n vec3 pr = vSh.xyz / vSh.w * 0.5 + 0.5;\n if (pr.x < 0.0 || pr.x > 1.0 || pr.y < 0.0 || pr.y > 1.0 || pr.z > 1.0) return 1.0;\n float ref = pr.z - uShadowBias;\n // 3x3 taps over a hardware-compared (LINEAR sampler2DShadow => free 2x2 PCF each) map\n float s = 0.0;\n for (int sy = -1; sy <= 1; sy++)\n for (int sx = -1; sx <= 1; sx++)\n s += texture(uShadow, vec3(pr.xy + vec2(float(sx), float(sy)) * uShadowPx * uShadowSoft, ref));\n s /= 9.0;\n return 0.35 + 0.65 * s;\n}\nvoid main(){\n vec3 n = normalize(vNorm);\n vec3 V = normalize(uCam - vPos);\n float skin = clamp(vBlood * 5.0, 0.0, 1.0);\n { // procedural micro-relief: perturb the shading normal by a fine noise gradient\n float bs = 5.5, e = 0.12, n0 = vnoise(vPos * bs);\n vec3 g = vec3(vnoise(vPos * bs + vec3(e, 0.0, 0.0)) - n0,\n vnoise(vPos * bs + vec3(0.0, e, 0.0)) - n0,\n vnoise(vPos * bs + vec3(0.0, 0.0, e)) - n0) / e;\n n = normalize(n - g * (0.10 + 0.30 * skin));\n }\n float d = dot(n, normalize(uLdir));\n float shade = uAmb + uDif * mix(abs(d), max(d, 0.0), uSigned);\n float spec = 0.0;\n float sh = uShadowOn > 0.5 ? shadowFactor() : 1.0;\n for (int i = 0; i < 8; i++) { if (i >= uLn) break;\n vec3 v = uLp[i] - vPos; float d2 = dot(v, v); vec3 L = v * inversesqrt(d2 + 1e-4);\n float lam = dot(n, L); lam = mix(abs(lam), max(lam, 0.0), uSigned);\n float fall = 1.0 / (1.0 + d2 / (uLf[i] * uLf[i]));\n float k = (i == 0) ? sh : 1.0;\n shade += uLi[i] * lam * fall * k;\n // GGX microfacet + Fresnel \u2014 roughness from the face's gloss (colour alpha slot)\n vec3 H = normalize(L + V);\n float nh = max(dot(n, H), 0.0);\n float rough = clamp(1.0 - vCol.a, 0.08, 1.0), a2 = rough * rough; a2 = a2 * a2;\n float dnm = nh * nh * (a2 - 1.0) + 1.0;\n float ggx = a2 / (3.14159 * dnm * dnm);\n float fres = 0.04 + 0.96 * pow(1.0 - max(dot(H, V), 0.0), 5.0);\n spec += uLi[i] * min(ggx, 6.0) * fres * fall * k * 0.16;\n }\n // coloured hemispheric ambient: cool from above, warm from below\n vec3 tint = mix(uGroundC, uSkyC, n.y * 0.5 + 0.5);\n vec3 lit = vCol.rgb * (uAmb * tint + vec3(shade - uAmb));\n // the perfusion term: blood near the surface changes how it meets the light\n float b = clamp(vBlood + uPulse * vBlood * 6.0, 0.0, 1.0);\n lit *= mix(vec3(1.0), vec3(1.07, 0.95, 0.94), b);\n lit += vec3(0.62, 0.06, 0.08) * (b * 0.20 * (1.0 - abs(d)));\n // pore noise, only where there is blood (skin, not cloth)\n float pore = vnoise(vPos * 2.4) * 0.6 + vnoise(vPos * 6.0) * 0.4;\n lit *= 1.0 + (pore - 0.5) * 0.09 * skin;\n // subsurface rim: grazing light bleeds through thin tissue, warm\n float fres = pow(1.0 - clamp(dot(n, V), 0.0, 1.0), 3.0);\n lit += vec3(0.55, 0.09, 0.10) * fres * skin * (0.28 + 0.5 * b);\n float fog = clamp(uFogK / vZ, 0.08, 1.0);\n vec3 outC = lit + vec3(1.0) * spec * uSpec * vCol.a;\n // fog and fades resolve toward the REAL background colour (the engine owns its ground)\n fragColor = vec4(mix(uBg, outC, fog * vGA), 1.0);\n}";
3
- export declare const FACE_INST_VS = "#version 300 es\nin vec3 aPos; in vec3 aNorm; in vec4 aCol; in float aBlood; in float aGrp;\nin vec3 aPos2; in vec3 aNorm2;\nin vec4 aI0; in vec4 aI1; in vec4 aI2; in vec4 aI3;\nuniform mat4 uVP; uniform mat4 uSVP;\nuniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];\nout vec3 vPos; out vec3 vNorm; out vec4 vCol; out float vZ; out vec4 vSh;\nout float vGA; out float vBlood;\nvoid main(){\n int g = int(aGrp + 0.5);\n float w = uMW[g];\n mat4 inst = mat4(aI0, aI1, aI2, aI3);\n vec3 p = mix(aPos, aPos2, w), n = mix(aNorm, aNorm2, w);\n vec4 wp = uGM[g] * (inst * vec4(p, 1.0));\n vPos = wp.xyz; vNorm = uNM[g] * (mat3(inst) * n); vCol = aCol; vGA = uGA[g]; vBlood = aBlood;\n vec4 cp = uVP * wp; vZ = cp.w; vSh = uSVP * wp; gl_Position = cp;\n}";
4
- export declare const DEPTH_INST_VS = "#version 300 es\nin vec3 aPos; in float aGrp; in vec3 aPos2;\nin vec4 aI0; in vec4 aI1; in vec4 aI2; in vec4 aI3;\nuniform mat4 uVP;\nuniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];\nvoid main(){\n int g = int(aGrp + 0.5);\n vec3 p = mix(aPos, aPos2, uMW[g]);\n gl_Position = uVP * (uGM[g] * (mat4(aI0, aI1, aI2, aI3) * vec4(p, 1.0)));\n}";
3
+ export declare const FACE_INST_VS = "#version 300 es\nin vec3 aPos; in vec3 aNorm; in vec4 aCol; in float aBlood; in float aGrp;\nin vec3 aPos2; in vec3 aNorm2;\nin vec4 aI0; in vec4 aI1; in vec4 aI2; in vec4 aI3; in vec4 aIP;\nuniform mat4 uVP; uniform mat4 uSVP; uniform float uT; uniform vec3 uWindDir;\nuniform float uFlight;\nuniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];\nout vec3 vPos; out vec3 vNorm; out vec4 vCol; out float vZ; out vec4 vSh;\nout float vGA; out float vBlood;\n// W10 FLIGHT: aIP = (a0, r0, ph, lobe). The flock's heart wanders, three lobes orbit\n// it breathing apart and back, each bird orbits its lobe with a ripple phase \u2014 the\n// murmuration, evaluated per vertex from uT alone. The instance matrix is the flock\n// frame (shared), so one batch = one flock you can place, scale and steer.\nvec3 flightPos(vec4 ip, float t){\n float a0 = ip.x, r0 = ip.y, ph = ip.z, lobe = ip.w;\n float cx = sin(t * 0.11) * 700.0, cz = -500.0 + cos(t * 0.07) * 380.0;\n float cy = 430.0 + sin(t * 0.16) * 110.0;\n float spread = 180.0 + 140.0 * sin(t * 0.21 + 1.3);\n float la = t * 0.34 + lobe * 2.0944;\n vec3 lc = vec3(cx + cos(la) * spread, cy + sin(la * 2.0 + lobe) * 60.0, cz + sin(la) * spread * 0.7);\n float aa = a0 + t * (0.5 + 0.25 * sin(ph));\n float wob = sin(t * 1.1 + ph) * 0.35;\n float rr = r0 * (0.75 + 0.25 * sin(t * 0.5 + ph + lobe));\n return lc + vec3(cos(aa) * rr, sin(aa * 1.7 + ph) * rr * 0.32 + wob * 24.0, sin(aa) * rr * (0.55 + wob * 0.2));\n}\nvec3 flightLocal(vec3 pl, vec4 ip, float t){\n float aa = ip.x + t * (0.5 + 0.25 * sin(ip.z));\n pl.y += sin(t * 11.0 + ip.z * 3.0) * abs(pl.z) * 0.9; // the flap, growing to the wingtips\n float ca = cos(aa + 1.5708), sa = sin(aa + 1.5708); // heading = the orbit tangent\n return vec3(pl.x * ca - pl.z * sa, pl.y, pl.x * sa + pl.z * ca);\n}\nvoid main(){\n int g = int(aGrp + 0.5);\n float w = uMW[g];\n mat4 inst = mat4(aI0, aI1, aI2, aI3);\n vec3 p = mix(aPos, aPos2, w), n = mix(aNorm, aNorm2, w);\n if (uFlight > 0.5) {\n p = flightPos(aIP, uT) + flightLocal(p, aIP, uT);\n } else {\n // W9 GPU WIND: per-instance (phase, freq, amp); displacement grows with height,\n // plus a second harmonic so the sway breathes instead of ticking\n float sw = sin(uT * aIP.y + aIP.x) + 0.5 * sin(uT * aIP.y * 2.3 + aIP.x * 1.7);\n p += uWindDir * (sw * aIP.z * max(p.y, 0.0));\n }\n vec4 wp = uGM[g] * (inst * vec4(p, 1.0));\n vPos = wp.xyz; vNorm = uNM[g] * (mat3(inst) * n); vCol = aCol; vGA = uGA[g]; vBlood = aBlood;\n vec4 cp = uVP * wp; vZ = cp.w; vSh = uSVP * wp; gl_Position = cp;\n}";
4
+ export declare const DEPTH_INST_VS = "#version 300 es\nin vec3 aPos; in float aGrp; in vec3 aPos2;\nin vec4 aI0; in vec4 aI1; in vec4 aI2; in vec4 aI3; in vec4 aIP;\nuniform mat4 uVP; uniform float uT; uniform vec3 uWindDir;\nuniform float uFlight;\nuniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];\n// W10 FLIGHT: aIP = (a0, r0, ph, lobe). The flock's heart wanders, three lobes orbit\n// it breathing apart and back, each bird orbits its lobe with a ripple phase \u2014 the\n// murmuration, evaluated per vertex from uT alone. The instance matrix is the flock\n// frame (shared), so one batch = one flock you can place, scale and steer.\nvec3 flightPos(vec4 ip, float t){\n float a0 = ip.x, r0 = ip.y, ph = ip.z, lobe = ip.w;\n float cx = sin(t * 0.11) * 700.0, cz = -500.0 + cos(t * 0.07) * 380.0;\n float cy = 430.0 + sin(t * 0.16) * 110.0;\n float spread = 180.0 + 140.0 * sin(t * 0.21 + 1.3);\n float la = t * 0.34 + lobe * 2.0944;\n vec3 lc = vec3(cx + cos(la) * spread, cy + sin(la * 2.0 + lobe) * 60.0, cz + sin(la) * spread * 0.7);\n float aa = a0 + t * (0.5 + 0.25 * sin(ph));\n float wob = sin(t * 1.1 + ph) * 0.35;\n float rr = r0 * (0.75 + 0.25 * sin(t * 0.5 + ph + lobe));\n return lc + vec3(cos(aa) * rr, sin(aa * 1.7 + ph) * rr * 0.32 + wob * 24.0, sin(aa) * rr * (0.55 + wob * 0.2));\n}\nvec3 flightLocal(vec3 pl, vec4 ip, float t){\n float aa = ip.x + t * (0.5 + 0.25 * sin(ip.z));\n pl.y += sin(t * 11.0 + ip.z * 3.0) * abs(pl.z) * 0.9; // the flap, growing to the wingtips\n float ca = cos(aa + 1.5708), sa = sin(aa + 1.5708); // heading = the orbit tangent\n return vec3(pl.x * ca - pl.z * sa, pl.y, pl.x * sa + pl.z * ca);\n}\nvoid main(){\n int g = int(aGrp + 0.5);\n vec3 p = mix(aPos, aPos2, uMW[g]);\n if (uFlight > 0.5) {\n p = flightPos(aIP, uT) + flightLocal(p, aIP, uT);\n } else {\n float sw = sin(uT * aIP.y + aIP.x) + 0.5 * sin(uT * aIP.y * 2.3 + aIP.x * 1.7);\n p += uWindDir * (sw * aIP.z * max(p.y, 0.0));\n }\n gl_Position = uVP * (uGM[g] * (mat4(aI0, aI1, aI2, aI3) * vec4(p, 1.0)));\n}";
5
5
  export declare const DEPTH_VS = "#version 300 es\nin vec3 aPos; in float aGrp; in vec3 aPos2;\nuniform mat4 uVP;\nuniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];\nvoid main(){\n int g = int(aGrp + 0.5);\n vec3 p = mix(aPos, aPos2, uMW[g]);\n gl_Position = uVP * (uGM[g] * vec4(p, 1.0));\n}";
6
6
  export declare const DEPTH_FS = "#version 300 es\nprecision mediump float;\nvoid main(){}";
7
7
  export declare const LINE_VS = "#version 300 es\nin vec3 aPos; in vec4 aCol; in float aGrp;\nuniform mat4 uVP;\nuniform mat4 uGM[8]; uniform mat3 uNM[8]; uniform float uMW[8]; uniform float uGA[8];\nout vec4 vCol; out float vZ; out float vGA;\nvoid main(){\n int g = int(aGrp + 0.5);\n vCol = aCol; vGA = uGA[g];\n vec4 p = uVP * (uGM[g] * vec4(aPos, 1.0)); vZ = p.w; gl_Position = p;\n}";