@3driseai/3drise-viewer 0.1.15 → 0.1.17

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.
Files changed (126) hide show
  1. package/README.md +4 -3
  2. package/dist/components/ChladniGrid.d.ts +15 -0
  3. package/dist/components/ChladniGrid.d.ts.map +1 -0
  4. package/dist/components/ChladniGrid.js +62 -0
  5. package/dist/components/CustomPrimitive.d.ts +2 -0
  6. package/dist/components/CustomPrimitive.d.ts.map +1 -1
  7. package/dist/components/CustomPrimitive.js +10 -16
  8. package/dist/components/EffectsGenerator.d.ts.map +1 -1
  9. package/dist/components/EffectsGenerator.js +26 -2
  10. package/dist/components/FlowNetGrid.d.ts +15 -0
  11. package/dist/components/FlowNetGrid.d.ts.map +1 -0
  12. package/dist/components/FlowNetGrid.js +61 -0
  13. package/dist/components/GirihGrid.d.ts +15 -0
  14. package/dist/components/GirihGrid.d.ts.map +1 -0
  15. package/dist/components/GirihGrid.js +62 -0
  16. package/dist/components/GravWaveGrid.d.ts +15 -0
  17. package/dist/components/GravWaveGrid.d.ts.map +1 -0
  18. package/dist/components/GravWaveGrid.js +62 -0
  19. package/dist/components/GridsGenerator.d.ts +2 -2
  20. package/dist/components/GridsGenerator.d.ts.map +1 -1
  21. package/dist/components/GridsGenerator.js +21 -0
  22. package/dist/components/HyperbolicGrid.d.ts +15 -0
  23. package/dist/components/HyperbolicGrid.d.ts.map +1 -0
  24. package/dist/components/HyperbolicGrid.js +61 -0
  25. package/dist/components/LayoutInstances.d.ts +13 -0
  26. package/dist/components/LayoutInstances.d.ts.map +1 -0
  27. package/dist/components/LayoutInstances.js +39 -0
  28. package/dist/components/ModelBoundary.d.ts +28 -0
  29. package/dist/components/ModelBoundary.d.ts.map +1 -0
  30. package/dist/components/ModelBoundary.js +25 -0
  31. package/dist/components/ModelPrimitive.d.ts +40 -0
  32. package/dist/components/ModelPrimitive.d.ts.map +1 -0
  33. package/dist/components/ModelPrimitive.js +52 -0
  34. package/dist/components/ObjectNode.d.ts.map +1 -1
  35. package/dist/components/ObjectNode.js +36 -8
  36. package/dist/components/ObjectNodeApi.d.ts.map +1 -1
  37. package/dist/components/ObjectNodeApi.js +6 -1
  38. package/dist/components/ProjectLoader.d.ts.map +1 -1
  39. package/dist/components/ProjectLoader.js +1 -0
  40. package/dist/components/ReactionDiffusionGrid.d.ts +15 -0
  41. package/dist/components/ReactionDiffusionGrid.d.ts.map +1 -0
  42. package/dist/components/ReactionDiffusionGrid.js +376 -0
  43. package/dist/components/WallpaperGrid.d.ts +15 -0
  44. package/dist/components/WallpaperGrid.d.ts.map +1 -0
  45. package/dist/components/WallpaperGrid.js +555 -0
  46. package/dist/components/cursor/CursorController.d.ts.map +1 -1
  47. package/dist/components/effects/BlackHoleEffect.d.ts +3 -3
  48. package/dist/components/effects/BlackHoleEffect.d.ts.map +1 -1
  49. package/dist/components/effects/BlackHoleEffect.js +293 -163
  50. package/dist/components/effects/DandelionSeedsEffect.d.ts +13 -0
  51. package/dist/components/effects/DandelionSeedsEffect.d.ts.map +1 -0
  52. package/dist/components/effects/DandelionSeedsEffect.js +510 -0
  53. package/dist/components/effects/FallingLeavesEffect.d.ts +13 -0
  54. package/dist/components/effects/FallingLeavesEffect.d.ts.map +1 -0
  55. package/dist/components/effects/FallingLeavesEffect.js +446 -0
  56. package/dist/components/effects/FieldLinesEffect.d.ts +11 -0
  57. package/dist/components/effects/FieldLinesEffect.d.ts.map +1 -0
  58. package/dist/components/effects/FieldLinesEffect.js +260 -0
  59. package/dist/components/effects/FireflySwarmEffect.d.ts +13 -0
  60. package/dist/components/effects/FireflySwarmEffect.d.ts.map +1 -0
  61. package/dist/components/effects/FireflySwarmEffect.js +408 -0
  62. package/dist/components/effects/GalaxyEffect.d.ts +13 -0
  63. package/dist/components/effects/GalaxyEffect.d.ts.map +1 -0
  64. package/dist/components/effects/GalaxyEffect.js +374 -0
  65. package/dist/components/effects/HarmonicShellEffect.d.ts +11 -0
  66. package/dist/components/effects/HarmonicShellEffect.d.ts.map +1 -0
  67. package/dist/components/effects/HarmonicShellEffect.js +209 -0
  68. package/dist/components/effects/IceCrystalsEffect.d.ts +3 -3
  69. package/dist/components/effects/IceCrystalsEffect.d.ts.map +1 -1
  70. package/dist/components/effects/IceCrystalsEffect.js +286 -166
  71. package/dist/components/effects/IceHaloEffect.d.ts +13 -0
  72. package/dist/components/effects/IceHaloEffect.d.ts.map +1 -0
  73. package/dist/components/effects/IceHaloEffect.js +306 -0
  74. package/dist/components/effects/LichtenbergEffect.d.ts +13 -0
  75. package/dist/components/effects/LichtenbergEffect.d.ts.map +1 -0
  76. package/dist/components/effects/LichtenbergEffect.js +517 -0
  77. package/dist/components/effects/MoleculesEffect.d.ts +3 -3
  78. package/dist/components/effects/MoleculesEffect.d.ts.map +1 -1
  79. package/dist/components/effects/MoleculesEffect.js +322 -164
  80. package/dist/components/effects/MurmurationEffect.d.ts +13 -0
  81. package/dist/components/effects/MurmurationEffect.d.ts.map +1 -0
  82. package/dist/components/effects/MurmurationEffect.js +539 -0
  83. package/dist/components/effects/PlanetaryRingsEffect.d.ts +13 -0
  84. package/dist/components/effects/PlanetaryRingsEffect.d.ts.map +1 -0
  85. package/dist/components/effects/PlanetaryRingsEffect.js +458 -0
  86. package/dist/components/effects/PortalEffect.d.ts +3 -3
  87. package/dist/components/effects/PortalEffect.d.ts.map +1 -1
  88. package/dist/components/effects/PortalEffect.js +240 -163
  89. package/dist/components/effects/ShockwaveEffect.d.ts +5 -3
  90. package/dist/components/effects/ShockwaveEffect.d.ts.map +1 -1
  91. package/dist/components/effects/ShockwaveEffect.js +538 -142
  92. package/dist/components/effects/SmokePlumeEffect.d.ts +3 -3
  93. package/dist/components/effects/SmokePlumeEffect.d.ts.map +1 -1
  94. package/dist/components/effects/SmokePlumeEffect.js +180 -116
  95. package/dist/components/effects/SmokeRingEffect.d.ts +3 -3
  96. package/dist/components/effects/SmokeRingEffect.d.ts.map +1 -1
  97. package/dist/components/effects/SmokeRingEffect.js +260 -135
  98. package/dist/components/effects/StrangeAttractorEffect.d.ts +11 -0
  99. package/dist/components/effects/StrangeAttractorEffect.d.ts.map +1 -0
  100. package/dist/components/effects/StrangeAttractorEffect.js +229 -0
  101. package/dist/components/effects/VolumetricFogEffect.d.ts +5 -3
  102. package/dist/components/effects/VolumetricFogEffect.d.ts.map +1 -1
  103. package/dist/components/effects/VolumetricFogEffect.js +211 -93
  104. package/dist/components/effects/WaterCausticsEffect.d.ts +13 -0
  105. package/dist/components/effects/WaterCausticsEffect.d.ts.map +1 -0
  106. package/dist/components/effects/WaterCausticsEffect.js +308 -0
  107. package/dist/components/effects/index.d.ts +9 -0
  108. package/dist/components/effects/index.d.ts.map +1 -1
  109. package/dist/components/effects/index.js +9 -0
  110. package/dist/components/index.d.ts +18 -0
  111. package/dist/components/index.d.ts.map +1 -1
  112. package/dist/components/index.js +10 -0
  113. package/dist/constants.d.ts +1 -1
  114. package/dist/constants.d.ts.map +1 -1
  115. package/dist/constants.js +1 -1
  116. package/dist/hooks/index.d.ts +1 -0
  117. package/dist/hooks/index.d.ts.map +1 -1
  118. package/dist/hooks/index.js +1 -0
  119. package/dist/hooks/useModelObject.d.ts +30 -0
  120. package/dist/hooks/useModelObject.d.ts.map +1 -0
  121. package/dist/hooks/useModelObject.js +86 -0
  122. package/dist/types/gridConfigs.d.ts +98 -0
  123. package/dist/types/gridConfigs.d.ts.map +1 -1
  124. package/dist/types/index.d.ts +1 -1
  125. package/dist/types/index.d.ts.map +1 -1
  126. package/package.json +2 -2
@@ -0,0 +1,260 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
3
+ import * as THREE from 'three';
4
+ import { useFrame } from '@react-three/fiber';
5
+ import { applyGenerativeAnimations } from '../../utils/handleGenerativeEffectsAnimations.js';
6
+ import { useObjectReady } from '../../hooks/useObjectReady.js';
7
+ /**
8
+ * Magnetic dipole field lines around the host, from the analytic line equation
9
+ * r = L sin^2(theta), with trapped particles that bounce between magnetic
10
+ * mirror points and drift around the axis, like the Van Allen belts.
11
+ *
12
+ * The geometry only carries (shell, azimuth, s) per vertex; the shape is
13
+ * computed in the vertex shader, so extent, twist and tilt animate for free.
14
+ */
15
+ const FIELD_LINE_SAMPLES = 96;
16
+ function mulberry32(seed) {
17
+ return () => {
18
+ seed |= 0;
19
+ seed = (seed + 0x6d2b79f5) | 0;
20
+ let r = Math.imul(seed ^ (seed >>> 15), 1 | seed);
21
+ r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;
22
+ return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
23
+ };
24
+ }
25
+ function buildFieldLines(shells, linesPerShell) {
26
+ const S = Math.round(Math.min(Math.max(shells, 1), 8));
27
+ const P = Math.round(Math.min(Math.max(linesPerShell, 2), 32));
28
+ const n = S * P * FIELD_LINE_SAMPLES;
29
+ const aShell = new Float32Array(n), aPhi = new Float32Array(n), aS = new Float32Array(n);
30
+ const index = new Uint32Array(S * P * (FIELD_LINE_SAMPLES - 1) * 2);
31
+ let v = 0, w = 0;
32
+ for (let s = 0; s < S; s++) {
33
+ for (let p = 0; p < P; p++) {
34
+ // stagger alternate shells by half a step so the lines interleave
35
+ const phi = ((p + (s % 2) * 0.5) / P) * Math.PI * 2;
36
+ for (let i = 0; i < FIELD_LINE_SAMPLES; i++) {
37
+ aShell[v] = S > 1 ? s / (S - 1) : 1;
38
+ aPhi[v] = phi;
39
+ aS[v] = i / (FIELD_LINE_SAMPLES - 1);
40
+ if (i > 0) {
41
+ index[w++] = v - 1;
42
+ index[w++] = v;
43
+ }
44
+ v++;
45
+ }
46
+ }
47
+ }
48
+ const geo = new THREE.BufferGeometry();
49
+ geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(n * 3), 3));
50
+ geo.setAttribute('aShell', new THREE.BufferAttribute(aShell, 1));
51
+ geo.setAttribute('aPhi', new THREE.BufferAttribute(aPhi, 1));
52
+ geo.setAttribute('aS', new THREE.BufferAttribute(aS, 1));
53
+ geo.setIndex(new THREE.BufferAttribute(index, 1));
54
+ return geo;
55
+ }
56
+ function buildFieldParticles(shells, linesPerShell, perLine) {
57
+ const S = Math.round(Math.min(Math.max(shells, 1), 8));
58
+ const P = Math.round(Math.min(Math.max(linesPerShell, 2), 32));
59
+ const Q = Math.round(Math.min(Math.max(perLine, 0), 40));
60
+ const n = S * P * Q;
61
+ const rand = mulberry32(11);
62
+ const aShell = new Float32Array(n), aPhi = new Float32Array(n), aSeed = new Float32Array(n * 3);
63
+ let v = 0;
64
+ for (let s = 0; s < S; s++) {
65
+ for (let p = 0; p < P; p++) {
66
+ const phi = ((p + (s % 2) * 0.5) / P) * Math.PI * 2;
67
+ for (let q = 0; q < Q; q++) {
68
+ aShell[v] = S > 1 ? s / (S - 1) : 1;
69
+ aPhi[v] = phi;
70
+ aSeed[v * 3] = rand();
71
+ aSeed[v * 3 + 1] = rand();
72
+ aSeed[v * 3 + 2] = rand();
73
+ v++;
74
+ }
75
+ }
76
+ }
77
+ const geo = new THREE.BufferGeometry();
78
+ geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(n * 3), 3));
79
+ geo.setAttribute('aShell', new THREE.BufferAttribute(aShell, 1));
80
+ geo.setAttribute('aPhi', new THREE.BufferAttribute(aPhi, 1));
81
+ geo.setAttribute('aSeed', new THREE.BufferAttribute(aSeed, 3));
82
+ return geo;
83
+ }
84
+ const lineVert = /* glsl */ `
85
+ attribute float aShell;
86
+ attribute float aPhi;
87
+ attribute float aS;
88
+ varying float vGlow;
89
+
90
+ uniform float uTime;
91
+ uniform float uBound;
92
+ uniform float uExtent;
93
+ uniform float uTwist;
94
+ uniform float uTilt;
95
+
96
+ const float PI = 3.14159265359;
97
+ const float TAU = 6.28318530718;
98
+
99
+ // the shell parameter u in 0..1 maps to an L-shell between the surface and uExtent
100
+ float shellL(float u){
101
+ return 1.05 + (max(uExtent, 1.1) - 1.05) * pow(u, 1.25);
102
+ }
103
+
104
+ // A dipole field line obeys r = L sin^2(theta): it leaves the surface
105
+ // (r = 1) where sin^2(theta0) = 1/L, arcs out to r = L at the equator and
106
+ // comes back down at pi - theta0. s runs 0..1 between the two footpoints.
107
+ vec3 fieldPoint(float L, float phi, float s, out float r){
108
+ float th0 = asin(sqrt(1.0 / L));
109
+ float th = mix(th0, PI - th0, s);
110
+ float st = sin(th);
111
+ r = L * st * st;
112
+ float ph = phi + uTwist * (th - 0.5 * PI) + uTime * 0.04;
113
+ vec3 p = r * vec3(st * cos(ph), cos(th), st * sin(ph));
114
+ // tilt the dipole axis about X
115
+ float c = cos(radians(uTilt)), sn = sin(radians(uTilt));
116
+ return vec3(p.x, c * p.y - sn * p.z, sn * p.y + c * p.z);
117
+ }
118
+
119
+
120
+ void main(){
121
+ float r;
122
+ vec3 p = fieldPoint(shellL(aShell), aPhi, aS, r);
123
+ // |B| falls off as 1/r^3, so the lines burn brightest near the footpoints
124
+ vGlow = clamp(pow(1.0 / r, 1.5), 0.0, 1.0);
125
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(p * uBound, 1.0);
126
+ }
127
+ `;
128
+ const lineFrag = /* glsl */ `
129
+ uniform vec3 uColor;
130
+ uniform vec3 uApexColor;
131
+ uniform float uOpacity;
132
+ uniform float uIntensity;
133
+ varying float vGlow;
134
+
135
+ void main(){
136
+ vec3 col = mix(uApexColor, uColor, vGlow);
137
+ float a = (0.14 + 0.5 * vGlow) * uOpacity;
138
+ gl_FragColor = vec4(col * uIntensity, a);
139
+ }
140
+ `;
141
+ const particleVert = /* glsl */ `
142
+ attribute float aShell;
143
+ attribute float aPhi;
144
+ attribute vec3 aSeed;
145
+ uniform float uParticleSize;
146
+ uniform float uIntensity;
147
+ varying float vGlow;
148
+ varying float vSign;
149
+
150
+ uniform float uTime;
151
+ uniform float uBound;
152
+ uniform float uExtent;
153
+ uniform float uTwist;
154
+ uniform float uTilt;
155
+
156
+ const float PI = 3.14159265359;
157
+ const float TAU = 6.28318530718;
158
+
159
+ // the shell parameter u in 0..1 maps to an L-shell between the surface and uExtent
160
+ float shellL(float u){
161
+ return 1.05 + (max(uExtent, 1.1) - 1.05) * pow(u, 1.25);
162
+ }
163
+
164
+ // A dipole field line obeys r = L sin^2(theta): it leaves the surface
165
+ // (r = 1) where sin^2(theta0) = 1/L, arcs out to r = L at the equator and
166
+ // comes back down at pi - theta0. s runs 0..1 between the two footpoints.
167
+ vec3 fieldPoint(float L, float phi, float s, out float r){
168
+ float th0 = asin(sqrt(1.0 / L));
169
+ float th = mix(th0, PI - th0, s);
170
+ float st = sin(th);
171
+ r = L * st * st;
172
+ float ph = phi + uTwist * (th - 0.5 * PI) + uTime * 0.04;
173
+ vec3 p = r * vec3(st * cos(ph), cos(th), st * sin(ph));
174
+ // tilt the dipole axis about X
175
+ float c = cos(radians(uTilt)), sn = sin(radians(uTilt));
176
+ return vec3(p.x, c * p.y - sn * p.z, sn * p.y + c * p.z);
177
+ }
178
+
179
+
180
+ void main(){
181
+ // Trapped particles bounce between two magnetic mirror points, slowing to
182
+ // a stop at each end, while drifting around the planet: ions one way,
183
+ // electrons the other. The seed sets pitch angle, phase and drift.
184
+ float amp = mix(0.18, 0.49, aSeed.y);
185
+ float w = 0.9 + 0.5 * aSeed.x;
186
+ float s = 0.5 + amp * sin(uTime * w + aSeed.x * TAU);
187
+ float drift = (aSeed.z - 0.5) * 0.35;
188
+ float r;
189
+ vec3 p = fieldPoint(shellL(aShell), aPhi + drift * uTime, s, r);
190
+ vGlow = clamp(pow(1.0 / r, 1.2), 0.0, 1.0);
191
+ vSign = step(0.5, aSeed.z);
192
+ vec4 mv = modelViewMatrix * vec4(p * uBound, 1.0);
193
+ gl_PointSize = uParticleSize * uIntensity * (0.7 + 0.6 * aSeed.y) * (14.0 * uBound / max(-mv.z, 1e-3));
194
+ gl_Position = projectionMatrix * mv;
195
+ }
196
+ `;
197
+ const particleFrag = /* glsl */ `
198
+ uniform vec3 uColor;
199
+ uniform vec3 uApexColor;
200
+ uniform float uOpacity;
201
+ varying float vGlow;
202
+ varying float vSign;
203
+
204
+ void main(){
205
+ float d = length(gl_PointCoord - 0.5);
206
+ if (d > 0.5) discard;
207
+ float core = smoothstep(0.5, 0.0, d);
208
+ vec3 tint = mix(uColor, uApexColor, vSign * 0.7);
209
+ vec3 col = mix(tint, vec3(1.0), core * core * 0.6);
210
+ gl_FragColor = vec4(col, core * core * (0.5 + 0.5 * vGlow) * uOpacity);
211
+ }
212
+ `;
213
+ const FieldLinesEffect = ({ boundingRadius, config, animations, onObjectReady, onObjectDispose }) => {
214
+ const { intensity, speed, color, opacity, shells = 4, linesPerShell = 12, extent = 3.2, twist = 0, tilt = 11, particleCount = 6, particleSize = 1.2, apexColor = '#6a5cff', } = config;
215
+ const rootRef = useRef(null);
216
+ const animCache = useRef({});
217
+ useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
218
+ // one uniform set, shared by the lines and the particles
219
+ const uniforms = useMemo(() => ({
220
+ uTime: { value: 0 },
221
+ uSpeed: { value: 1 },
222
+ uIntensity: { value: 1 },
223
+ uOpacity: { value: 1 },
224
+ uColor: { value: new THREE.Color() },
225
+ uApexColor: { value: new THREE.Color() },
226
+ uBound: { value: 1 },
227
+ uExtent: { value: 3.2 },
228
+ uTwist: { value: 0 },
229
+ uTilt: { value: 11 },
230
+ uParticleSize: { value: 1.2 },
231
+ }), []);
232
+ const materials = useMemo(() => [
233
+ new THREE.ShaderMaterial({ vertexShader: lineVert, fragmentShader: lineFrag, uniforms, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending }),
234
+ new THREE.ShaderMaterial({ vertexShader: particleVert, fragmentShader: particleFrag, uniforms, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending }),
235
+ ], [uniforms]);
236
+ useEffect(() => () => materials.forEach((m) => m.dispose()), [materials]);
237
+ const lineGeometry = useMemo(() => buildFieldLines(shells, linesPerShell), [shells, linesPerShell]);
238
+ const particleGeometry = useMemo(() => buildFieldParticles(shells, linesPerShell, particleCount), [shells, linesPerShell, particleCount]);
239
+ useEffect(() => () => lineGeometry.dispose(), [lineGeometry]);
240
+ useEffect(() => () => particleGeometry.dispose(), [particleGeometry]);
241
+ useLayoutEffect(() => {
242
+ uniforms.uSpeed.value = speed;
243
+ uniforms.uIntensity.value = intensity;
244
+ uniforms.uOpacity.value = opacity;
245
+ uniforms.uColor.value.set(color);
246
+ uniforms.uApexColor.value.set(apexColor);
247
+ uniforms.uBound.value = boundingRadius;
248
+ uniforms.uExtent.value = extent;
249
+ uniforms.uTwist.value = twist;
250
+ uniforms.uTilt.value = tilt;
251
+ uniforms.uParticleSize.value = particleSize;
252
+ }, [uniforms, speed, intensity, opacity, color, apexColor, boundingRadius, extent, twist, tilt, particleSize]);
253
+ useFrame((_, delta) => {
254
+ uniforms.uTime.value += delta * uniforms.uSpeed.value;
255
+ if (animations?.length)
256
+ applyGenerativeAnimations(materials, animations, animCache.current, Date.now(), false);
257
+ });
258
+ return (_jsxs("group", { ref: rootRef, children: [_jsx("lineSegments", { geometry: lineGeometry, material: materials[0], frustumCulled: false }), _jsx("points", { geometry: particleGeometry, material: materials[1], frustumCulled: false })] }));
259
+ };
260
+ export default FieldLinesEffect;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { AnimationOptions, FireflySwarmConfig } from '@3driseai/3drise-core';
3
+ import type { ObjectReadyProps } from '../../types/objectReady';
4
+ export interface FireflySwarmEffectProps extends ObjectReadyProps {
5
+ boundingRadius: number;
6
+ config: FireflySwarmConfig;
7
+ animations?: AnimationOptions[];
8
+ /** True when the effect is attached to an object, so it can react to the host. */
9
+ hasHost?: boolean;
10
+ }
11
+ declare const FireflySwarmEffect: React.FC<FireflySwarmEffectProps>;
12
+ export default FireflySwarmEffect;
13
+ //# sourceMappingURL=FireflySwarmEffect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FireflySwarmEffect.d.ts","sourceRoot":"","sources":["../../../src/components/effects/FireflySwarmEffect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAG3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAGlF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,uBAAwB,SAAQ,gBAAgB;IAC7D,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAChC,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAwXD,QAAA,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CA+BzD,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,408 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
3
+ import * as THREE from 'three';
4
+ import { useFrame } from '@react-three/fiber';
5
+ import { applyGenerativeAnimations } from '../../utils/handleGenerativeEffectsAnimations.js';
6
+ import { useObjectReady } from '../../hooks/useObjectReady.js';
7
+ /**
8
+ * Kuramoto-coupled fireflies drifting on a divergence-free flow: random blinking slowly locks into sync, with ripples of light rolling through the swarm. Use for night, garden, forest, magical scenes.
9
+ *
10
+ * dθᵢ/dt = ωᵢ + K/Σw · Σⱼ wᵢⱼ sin(θⱼ − θᵢ), wᵢⱼ = e^(−d²/ℓ²) · flash when θ wraps · drift = ABC flow (∇·u = 0)
11
+ */
12
+ // ---------------------------------------------------------------------------
13
+ // Synchronous fireflies as Kuramoto oscillators.
14
+ // Each firefly carries a phase theta_i that advances at its own natural rate
15
+ // omega_i (spread ~8% around the mean) and is nudged toward its neighbours:
16
+ // dtheta_i/dt = omega_i + K / sum_j w_ij * sum_j w_ij sin(theta_j - theta_i)
17
+ // with w_ij = exp(-d_ij^2 / l^2). Because the coupling is local, order does not
18
+ // appear everywhere at once: synchronised patches grow, meet and leave phase
19
+ // gradients behind, which read as waves of light rolling through the swarm.
20
+ // A firefly flashes when its phase wraps (short rise, ~0.2 s decay). The
21
+ // coupling breathes slowly (and each natural rate drifts a little), so the
22
+ // swarm keeps sliding out of and back into step instead of freezing.
23
+ // The bodies drift on an ABC (Arnold-Beltrami-Childress) flow, an exactly
24
+ // divergence-free velocity field, so the swarm stirs without clumping; a soft
25
+ // ellipsoidal shell keeps them around the host.
26
+ // ---------------------------------------------------------------------------
27
+ const vert = /* glsl */ `
28
+ attribute vec3 aFly; // phase fraction (0..1, 0 = flash), seed, per-fly frequency (Hz)
29
+ uniform float uTime;
30
+ uniform float uIntensity;
31
+ uniform float uGlowSize;
32
+ uniform float uBound;
33
+ uniform float uViewH;
34
+ varying float vFlash;
35
+ varying float vSeed;
36
+ varying float vRad; // sprite radius in world units
37
+ varying float vFade;
38
+
39
+ void main(){
40
+ // time since the phase last wrapped, in seconds
41
+ float tau = aFly.x / max(aFly.z, 0.02);
42
+ // luciferin pulse: fast rise, exponential decay (~0.2 s)
43
+ float rise = smoothstep(0.0, 0.05, tau);
44
+ float flash = rise * (0.85 * exp(-max(tau - 0.05, 0.0) / 0.2) + 0.15 * exp(-tau / 0.7));
45
+ vFlash = flash;
46
+ vSeed = aFly.y;
47
+ vec3 p = position;
48
+ // slow bobbing on top of the drift
49
+ p.y += 0.035 * uBound * sin(uTime * (0.7 + 0.5 * aFly.y) + aFly.y * 40.0);
50
+ vec4 mv = modelViewMatrix * vec4(p, 1.0);
51
+ float body = 0.03 * uBound;
52
+ float halo = 0.55 * uBound * uGlowSize * (0.7 + 0.3 * uIntensity);
53
+ vRad = flash > 0.004 ? max(halo, body) : body;
54
+ float pix = vRad * projectionMatrix[1][1] * uViewH / max(-mv.z, 0.05);
55
+ gl_PointSize = clamp(pix, 1.5, 200.0);
56
+ // keep energy when a sprite is clamped to a minimum size
57
+ vFade = min(pix / 1.5, 1.0);
58
+ gl_Position = projectionMatrix * mv;
59
+ }
60
+ `;
61
+ const frag = /* glsl */ `
62
+ uniform vec3 uColor;
63
+ uniform vec3 uFlashColor;
64
+ uniform float uIntensity;
65
+ uniform float uOpacity;
66
+ uniform float uGlowSize;
67
+ uniform float uBound;
68
+ varying float vFlash;
69
+ varying float vSeed;
70
+ varying float vRad;
71
+ varying float vFade;
72
+
73
+ void main(){
74
+ float d = length(gl_PointCoord - 0.5) * 2.0;
75
+ if (d > 1.0) discard;
76
+ float r = d * vRad / uBound; // radius in host units
77
+ float edge = 1.0 - smoothstep(0.75, 1.0, d);
78
+ // faint body: a tiny soft dot that is always there
79
+ float bodyR = 0.018;
80
+ float body = exp(-(r * r) / (bodyR * bodyR)) * 0.5;
81
+ // flash: hot lantern core + wide soft halo (additive, energy kept low)
82
+ float g = max(uGlowSize, 0.2);
83
+ float core = exp(-(r * r) / (0.0016 * g * g + 0.0004));
84
+ float halo = exp(-r / (0.34 * g)) * 0.06 + exp(-r / (0.15 * g)) * 0.2 + exp(-r / (0.05 * g)) * 0.45;
85
+ float f = vFlash;
86
+ // small per-fly colour variation: some lanterns are greener, some warmer
87
+ vec3 flashCol = mix(uFlashColor, uFlashColor * vec3(1.25, 0.95, 0.5), vSeed * 0.5);
88
+ vec3 col = uColor * body * (1.0 - 0.6 * f);
89
+ col += flashCol * f * (halo + core * 1.1);
90
+ col += vec3(1.0, 1.0, 0.86) * core * f * f * 1.7; // white-hot only in the thin core
91
+ col *= edge * uIntensity * uOpacity * vFade * vFade;
92
+ if (max(col.r, max(col.g, col.b)) < 0.002) discard;
93
+ gl_FragColor = vec4(col, 1.0);
94
+ }
95
+ `;
96
+ function mulberry32(seed) {
97
+ return () => {
98
+ seed |= 0;
99
+ seed = (seed + 0x6d2b79f5) | 0;
100
+ let r = Math.imul(seed ^ (seed >>> 15), 1 | seed);
101
+ r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;
102
+ return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
103
+ };
104
+ }
105
+ function createUniforms() {
106
+ return {
107
+ uTime: { value: 0 },
108
+ uSpeed: { value: 1 },
109
+ uIntensity: { value: 1 },
110
+ uOpacity: { value: 1 },
111
+ uColor: { value: new THREE.Color() },
112
+ uFlashColor: { value: new THREE.Color() },
113
+ uCoupling: { value: 1 },
114
+ uFrequency: { value: 0.55 },
115
+ uSpread: { value: 2.4 },
116
+ uDriftSpeed: { value: 1 },
117
+ uGlowSize: { value: 1 },
118
+ uBound: { value: 1 },
119
+ uViewH: { value: 800 },
120
+ };
121
+ }
122
+ function applyConfig(u, c, bound, env) {
123
+ u.uSpeed.value = c.speed;
124
+ u.uIntensity.value = c.intensity;
125
+ u.uOpacity.value = c.opacity;
126
+ u.uColor.value.set(c.color);
127
+ u.uFlashColor.value.set(c.flashColor);
128
+ u.uCoupling.value = c.coupling;
129
+ u.uFrequency.value = c.frequency;
130
+ u.uSpread.value = c.spread;
131
+ u.uDriftSpeed.value = c.driftSpeed;
132
+ u.uGlowSize.value = c.glowSize;
133
+ u.uBound.value = bound;
134
+ }
135
+ function build(u, c, bound, env) {
136
+ const n = Math.round(Math.min(Math.max(c.count, 20), 400));
137
+ const hasHost = !!(env && env.hasHost);
138
+ const TAU = Math.PI * 2;
139
+ const SIM_DT = 1 / 30;
140
+ const PACE = 0.06;
141
+ const rand = mulberry32(1307);
142
+ const pos = new Float32Array(n * 3);
143
+ const fly = new Float32Array(n * 3);
144
+ const theta = new Float32Array(n);
145
+ const omegaDev = new Float32Array(n); // relative natural-frequency offset
146
+ const drift = new Float32Array(n); // phase of the slow frequency wander
147
+ const sn = new Float32Array(n);
148
+ const cs = new Float32Array(n);
149
+ const w = new Float32Array(n * n);
150
+ const wsum = new Float32Array(n);
151
+ // homes: uniform in an ellipsoidal shell around the host (unit direction + radial fraction)
152
+ const b = bound;
153
+ const cy = 0.25 * b;
154
+ const SQ = 0.72; // vertical squash of the shell
155
+ const rIn0 = hasHost ? 1.15 * b : 0;
156
+ const homeDir = new Float32Array(n * 3);
157
+ const homeS = new Float32Array(n);
158
+ const spinRate = new Float32Array(n);
159
+ const off = new Float32Array(n * 3); // flow-driven offset from home
160
+ for (let i = 0; i < n; i++) {
161
+ let dx = 0, dy = 1, dz = 0, sr = 0.5;
162
+ for (let k = 0; k < 40; k++) {
163
+ dy = rand() * 2 - 1;
164
+ const ph = rand() * TAU, rr = Math.sqrt(1 - dy * dy);
165
+ dx = rr * Math.cos(ph);
166
+ dz = rr * Math.sin(ph);
167
+ // radius with density uniform in volume
168
+ const ri = rIn0 / b, ro = Math.max(c.spread, ri + 0.45);
169
+ const r = Math.cbrt(ri * ri * ri + rand() * (ro * ro * ro - ri * ri * ri));
170
+ sr = Math.min(Math.max((r - ri) / (ro - ri), 0), 1);
171
+ if (dy * SQ * r + 0.25 > -0.7)
172
+ break;
173
+ }
174
+ homeDir[i * 3] = dx;
175
+ homeDir[i * 3 + 1] = dy;
176
+ homeDir[i * 3 + 2] = dz;
177
+ homeS[i] = sr;
178
+ spinRate[i] = (0.6 + 0.8 * rand()) * (rand() < 0.8 ? 1 : -0.6);
179
+ theta[i] = rand() * TAU;
180
+ // gaussian spread of natural frequencies (sigma = 8%)
181
+ const g = Math.sqrt(-2 * Math.log(1 - rand() * 0.9999)) * Math.cos(TAU * rand());
182
+ omegaDev[i] = Math.max(-0.25, Math.min(0.25, g * 0.08));
183
+ drift[i] = rand() * TAU;
184
+ fly[i * 3 + 1] = rand();
185
+ }
186
+ const geo = new THREE.BufferGeometry();
187
+ const posAttr = new THREE.BufferAttribute(pos, 3);
188
+ const flyAttr = new THREE.BufferAttribute(fly, 3);
189
+ posAttr.setUsage(THREE.DynamicDrawUsage);
190
+ flyAttr.setUsage(THREE.DynamicDrawUsage);
191
+ geo.setAttribute('position', posAttr);
192
+ geo.setAttribute('aFly', flyAttr);
193
+ const mat = new THREE.ShaderMaterial({
194
+ vertexShader: vert, fragmentShader: frag, uniforms: u,
195
+ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending,
196
+ });
197
+ const pts = new THREE.Points(geo, mat);
198
+ pts.frustumCulled = false;
199
+ const vp = new THREE.Vector2();
200
+ pts.onBeforeRender = (renderer) => {
201
+ renderer.getDrawingBufferSize(vp);
202
+ u.uViewH.value = vp.y * 0.5;
203
+ };
204
+ const group = new THREE.Group();
205
+ group.add(pts);
206
+ let simTime = 0; // seconds of simulated time
207
+ let acc = 0; // leftover time for the fixed-rate oscillator step
208
+ let stepCount = 0;
209
+ // coupling weights, refreshed a few times a second (bodies move slowly)
210
+ function refreshWeights() {
211
+ const ell = Math.max(0.3 * u.uSpread.value * b, 0.25 * b);
212
+ const inv = 1 / (ell * ell);
213
+ for (let i = 0; i < n; i++)
214
+ wsum[i] = 0;
215
+ for (let i = 0; i < n; i++) {
216
+ const xi = pos[i * 3], yi = pos[i * 3 + 1], zi = pos[i * 3 + 2];
217
+ w[i * n + i] = 0;
218
+ for (let j = i + 1; j < n; j++) {
219
+ const dx = pos[j * 3] - xi, dy = pos[j * 3 + 1] - yi, dz = pos[j * 3 + 2] - zi;
220
+ const q = (dx * dx + dy * dy + dz * dz) * inv;
221
+ const v = q < 9 ? Math.exp(-q) : 0;
222
+ w[i * n + j] = v;
223
+ w[j * n + i] = v;
224
+ wsum[i] += v;
225
+ wsum[j] += v;
226
+ }
227
+ }
228
+ }
229
+ // natural angular frequency of fly i at time t. A slowly turning frequency
230
+ // gradient acts as a pacemaker: once locked, phase lags away from the fast side,
231
+ // so each synchronised flash sweeps across the swarm as a travelling wave.
232
+ let gx = 1, gy = 0, gz = 0;
233
+ function omegaOf(i, t, base) {
234
+ const i3 = i * 3;
235
+ const grad = (pos[i3] * gx + (pos[i3 + 1] - cy) * gy + pos[i3 + 2] * gz) / Math.max(u.uSpread.value * b, 0.1);
236
+ return base * (1 + omegaDev[i] + 0.022 * Math.sin(0.045 * t + drift[i]) + PACE * grad);
237
+ }
238
+ function turnPacemaker(t) {
239
+ const a = 0.021 * t + 0.6, e = 0.35 * Math.sin(0.013 * t);
240
+ gx = Math.cos(a) * Math.cos(e);
241
+ gy = Math.sin(e);
242
+ gz = Math.sin(a) * Math.cos(e);
243
+ }
244
+ function oscillatorStep(h) {
245
+ const base = TAU * Math.max(u.uFrequency.value, 0.02);
246
+ // slow breathing of the coupling: ramps up from zero, then eases in and out of sync
247
+ const t = simTime;
248
+ turnPacemaker(t);
249
+ const ramp = Math.min(t / 14, 1);
250
+ const breathe = 0.6 + 0.4 * Math.cos((TAU * Math.max(t - 14, 0)) / 80);
251
+ const K = Math.max(u.uCoupling.value, 0) * 0.35 * base * ramp * ramp * (3 - 2 * ramp) * breathe;
252
+ for (let i = 0; i < n; i++) {
253
+ sn[i] = Math.sin(theta[i]);
254
+ cs[i] = Math.cos(theta[i]);
255
+ }
256
+ for (let i = 0; i < n; i++) {
257
+ let S = 0, C = 0;
258
+ const row = i * n;
259
+ for (let j = 0; j < n; j++) {
260
+ const wij = w[row + j];
261
+ S += wij * sn[j];
262
+ C += wij * cs[j];
263
+ }
264
+ // sum_j w_ij sin(theta_j - theta_i) = cos(theta_i) S - sin(theta_i) C
265
+ const pull = (cs[i] * S - sn[i] * C) / Math.max(wsum[i], 1);
266
+ let th = theta[i] + (omegaOf(i, t, base) + K * pull) * h;
267
+ th -= TAU * Math.floor(th / TAU);
268
+ theta[i] = th;
269
+ }
270
+ }
271
+ // Each fly is tethered to a home that circles the host slowly; the ABC flow
272
+ // stirs the offset from home, so neighbours wander together while the
273
+ // swarm as a whole keeps its even spread (no clumping at the shell).
274
+ let spin = 0;
275
+ function driftStep(h) {
276
+ const t = simTime;
277
+ const k = 1.05 / b;
278
+ const amp = 0.11 * b * u.uDriftSpeed.value;
279
+ spin += h * 0.045 * u.uDriftSpeed.value;
280
+ // ABC flow with slowly turning phases: still exactly divergence-free
281
+ const p1 = 0.13 * t, p2 = 0.09 * t + 1.7, p3 = 0.11 * t + 4.1;
282
+ const A = amp, B = 0.8 * amp, C = 0.65 * amp;
283
+ const rOut = Math.max(u.uSpread.value * b, rIn0 + 0.45 * b);
284
+ const tether = 0.25;
285
+ for (let i = 0; i < n; i++) {
286
+ const i3 = i * 3;
287
+ const r = rIn0 + homeS[i] * (rOut - rIn0);
288
+ const ang = spin * spinRate[i] * (1.6 * b / (r + b));
289
+ const ca = Math.cos(ang), sa = Math.sin(ang);
290
+ const dx = homeDir[i3], dz = homeDir[i3 + 2];
291
+ const hx = (ca * dx - sa * dz) * r;
292
+ const hy = homeDir[i3 + 1] * SQ * r + cy;
293
+ const hz = (sa * dx + ca * dz) * r;
294
+ let x = pos[i3], y = pos[i3 + 1], z = pos[i3 + 2];
295
+ const vx = A * Math.sin(k * z + p1) + C * Math.cos(k * y + p3);
296
+ const vy = B * Math.sin(k * x + p2) + A * Math.cos(k * z + p1);
297
+ const vz = C * Math.sin(k * y + p3) + B * Math.cos(k * x + p2);
298
+ const ox = off[i3] + (vx - tether * off[i3]) * h;
299
+ const oy = off[i3 + 1] + (vy - tether * off[i3 + 1]) * h;
300
+ const oz = off[i3 + 2] + (vz - tether * off[i3 + 2]) * h;
301
+ off[i3] = ox;
302
+ off[i3 + 1] = oy;
303
+ off[i3 + 2] = oz;
304
+ x = hx + ox;
305
+ y = hy + oy;
306
+ z = hz + oz;
307
+ // keep clear of the host and above the floor (a soft projection)
308
+ if (rIn0 > 0) {
309
+ const d = Math.sqrt(x * x + y * y + z * z) + 1e-6;
310
+ const lim = rIn0 * 0.92;
311
+ if (d < lim + 0.15 * b) {
312
+ const e = (lim + 0.15 * b - d) / (0.15 * b);
313
+ const nd = d + 0.15 * b * e * e * 0.5;
314
+ const f = Math.max(nd, lim) / d;
315
+ x *= f;
316
+ y *= f;
317
+ z *= f;
318
+ }
319
+ }
320
+ if (y < -0.75 * b)
321
+ y = -0.75 * b - 0.1 * b * Math.tanh((-0.75 * b - y) / (0.1 * b));
322
+ pos[i3] = x;
323
+ pos[i3 + 1] = y;
324
+ pos[i3 + 2] = z;
325
+ }
326
+ }
327
+ driftStep(0);
328
+ refreshWeights();
329
+ return {
330
+ object: group,
331
+ materials: [mat],
332
+ apply(c2) { },
333
+ update(dt, camera) {
334
+ const h = Math.min(Math.max(dt, 0), 0.1) * Math.max(u.uSpeed.value, 0);
335
+ u.uTime.value += h;
336
+ // drift: substeps of at most 1/30 s
337
+ const subs = Math.max(1, Math.ceil(h / SIM_DT));
338
+ for (let s = 0; s < subs; s++) {
339
+ driftStep(h / subs);
340
+ simTime += h / subs;
341
+ }
342
+ // oscillators at a fixed rate
343
+ acc += h;
344
+ let guard = 0;
345
+ while (acc >= SIM_DT && guard < 6) {
346
+ oscillatorStep(SIM_DT);
347
+ acc -= SIM_DT;
348
+ guard++;
349
+ stepCount++;
350
+ if (stepCount % 10 === 0)
351
+ refreshWeights();
352
+ }
353
+ if (guard >= 6)
354
+ acc = 0;
355
+ // render phase = state + the unstepped remainder
356
+ const base = TAU * Math.max(u.uFrequency.value, 0.02);
357
+ for (let i = 0; i < n; i++) {
358
+ const om = omegaOf(i, simTime, base);
359
+ let th = theta[i] + om * acc;
360
+ th -= TAU * Math.floor(th / TAU);
361
+ fly[i * 3] = th / TAU;
362
+ fly[i * 3 + 2] = om / TAU;
363
+ }
364
+ posAttr.needsUpdate = true;
365
+ flyAttr.needsUpdate = true;
366
+ },
367
+ dispose() { geo.dispose(); mat.dispose(); },
368
+ };
369
+ }
370
+ /** Defaults for every knob, so an older saved config still renders. */
371
+ const DEFAULTS = {
372
+ intensity: 1,
373
+ speed: 1,
374
+ color: '#6f7d45',
375
+ opacity: 1,
376
+ count: 150,
377
+ coupling: 1,
378
+ frequency: 0.55,
379
+ spread: 2.4,
380
+ driftSpeed: 1,
381
+ glowSize: 1,
382
+ flashColor: '#d4ff5c',
383
+ };
384
+ const FireflySwarmEffect = ({ boundingRadius, config, animations, hasHost = false, onObjectReady, onObjectDispose }) => {
385
+ const rootRef = useRef(null);
386
+ const animCache = useRef({});
387
+ useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
388
+ // a standalone effect gets boundingRadius 1
389
+ const b = boundingRadius > 0 ? boundingRadius : 1;
390
+ const cfg = useMemo(() => ({ ...DEFAULTS, ...config }), [config]);
391
+ const env = useMemo(() => ({ hasHost: hasHost }), [hasHost]);
392
+ // uniforms outlive the objects, so keyframe animations survive a rebuild
393
+ const uniforms = useMemo(() => createUniforms(), []);
394
+ // rebuilt only when the host size or a structural knob changes
395
+ const instance = useMemo(() => build(uniforms, { ...DEFAULTS, ...config }, b, env), [uniforms, b, env, cfg.count]);
396
+ useEffect(() => () => instance.dispose(), [instance]);
397
+ useLayoutEffect(() => {
398
+ applyConfig(uniforms, cfg, b, env);
399
+ instance.apply(cfg);
400
+ }, [instance, uniforms, cfg, b, env]);
401
+ useFrame((state, delta) => {
402
+ instance.update(Math.min(delta, 0.1), state.camera);
403
+ if (animations?.length)
404
+ applyGenerativeAnimations(instance.materials, animations, animCache.current, Date.now(), false);
405
+ });
406
+ return (_jsx("group", { ref: rootRef, children: _jsx("primitive", { object: instance.object }) }));
407
+ };
408
+ export default FireflySwarmEffect;