@3driseai/3drise-viewer 0.1.16 → 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 (116) 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 +2 -2
  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/ModelPrimitive.d.ts +2 -0
  29. package/dist/components/ModelPrimitive.d.ts.map +1 -1
  30. package/dist/components/ModelPrimitive.js +3 -3
  31. package/dist/components/ObjectNode.d.ts.map +1 -1
  32. package/dist/components/ObjectNode.js +36 -8
  33. package/dist/components/ReactionDiffusionGrid.d.ts +15 -0
  34. package/dist/components/ReactionDiffusionGrid.d.ts.map +1 -0
  35. package/dist/components/ReactionDiffusionGrid.js +376 -0
  36. package/dist/components/WallpaperGrid.d.ts +15 -0
  37. package/dist/components/WallpaperGrid.d.ts.map +1 -0
  38. package/dist/components/WallpaperGrid.js +555 -0
  39. package/dist/components/cursor/CursorController.d.ts.map +1 -1
  40. package/dist/components/effects/BlackHoleEffect.d.ts +3 -3
  41. package/dist/components/effects/BlackHoleEffect.d.ts.map +1 -1
  42. package/dist/components/effects/BlackHoleEffect.js +293 -163
  43. package/dist/components/effects/DandelionSeedsEffect.d.ts +13 -0
  44. package/dist/components/effects/DandelionSeedsEffect.d.ts.map +1 -0
  45. package/dist/components/effects/DandelionSeedsEffect.js +510 -0
  46. package/dist/components/effects/FallingLeavesEffect.d.ts +13 -0
  47. package/dist/components/effects/FallingLeavesEffect.d.ts.map +1 -0
  48. package/dist/components/effects/FallingLeavesEffect.js +446 -0
  49. package/dist/components/effects/FieldLinesEffect.d.ts +11 -0
  50. package/dist/components/effects/FieldLinesEffect.d.ts.map +1 -0
  51. package/dist/components/effects/FieldLinesEffect.js +260 -0
  52. package/dist/components/effects/FireflySwarmEffect.d.ts +13 -0
  53. package/dist/components/effects/FireflySwarmEffect.d.ts.map +1 -0
  54. package/dist/components/effects/FireflySwarmEffect.js +408 -0
  55. package/dist/components/effects/GalaxyEffect.d.ts +13 -0
  56. package/dist/components/effects/GalaxyEffect.d.ts.map +1 -0
  57. package/dist/components/effects/GalaxyEffect.js +374 -0
  58. package/dist/components/effects/HarmonicShellEffect.d.ts +11 -0
  59. package/dist/components/effects/HarmonicShellEffect.d.ts.map +1 -0
  60. package/dist/components/effects/HarmonicShellEffect.js +209 -0
  61. package/dist/components/effects/IceCrystalsEffect.d.ts +3 -3
  62. package/dist/components/effects/IceCrystalsEffect.d.ts.map +1 -1
  63. package/dist/components/effects/IceCrystalsEffect.js +286 -166
  64. package/dist/components/effects/IceHaloEffect.d.ts +13 -0
  65. package/dist/components/effects/IceHaloEffect.d.ts.map +1 -0
  66. package/dist/components/effects/IceHaloEffect.js +306 -0
  67. package/dist/components/effects/LichtenbergEffect.d.ts +13 -0
  68. package/dist/components/effects/LichtenbergEffect.d.ts.map +1 -0
  69. package/dist/components/effects/LichtenbergEffect.js +517 -0
  70. package/dist/components/effects/MoleculesEffect.d.ts +3 -3
  71. package/dist/components/effects/MoleculesEffect.d.ts.map +1 -1
  72. package/dist/components/effects/MoleculesEffect.js +322 -164
  73. package/dist/components/effects/MurmurationEffect.d.ts +13 -0
  74. package/dist/components/effects/MurmurationEffect.d.ts.map +1 -0
  75. package/dist/components/effects/MurmurationEffect.js +539 -0
  76. package/dist/components/effects/PlanetaryRingsEffect.d.ts +13 -0
  77. package/dist/components/effects/PlanetaryRingsEffect.d.ts.map +1 -0
  78. package/dist/components/effects/PlanetaryRingsEffect.js +458 -0
  79. package/dist/components/effects/PortalEffect.d.ts +3 -3
  80. package/dist/components/effects/PortalEffect.d.ts.map +1 -1
  81. package/dist/components/effects/PortalEffect.js +240 -163
  82. package/dist/components/effects/ShockwaveEffect.d.ts +5 -3
  83. package/dist/components/effects/ShockwaveEffect.d.ts.map +1 -1
  84. package/dist/components/effects/ShockwaveEffect.js +538 -142
  85. package/dist/components/effects/SmokePlumeEffect.d.ts +3 -3
  86. package/dist/components/effects/SmokePlumeEffect.d.ts.map +1 -1
  87. package/dist/components/effects/SmokePlumeEffect.js +180 -116
  88. package/dist/components/effects/SmokeRingEffect.d.ts +3 -3
  89. package/dist/components/effects/SmokeRingEffect.d.ts.map +1 -1
  90. package/dist/components/effects/SmokeRingEffect.js +260 -135
  91. package/dist/components/effects/StrangeAttractorEffect.d.ts +11 -0
  92. package/dist/components/effects/StrangeAttractorEffect.d.ts.map +1 -0
  93. package/dist/components/effects/StrangeAttractorEffect.js +229 -0
  94. package/dist/components/effects/VolumetricFogEffect.d.ts +5 -3
  95. package/dist/components/effects/VolumetricFogEffect.d.ts.map +1 -1
  96. package/dist/components/effects/VolumetricFogEffect.js +211 -93
  97. package/dist/components/effects/WaterCausticsEffect.d.ts +13 -0
  98. package/dist/components/effects/WaterCausticsEffect.d.ts.map +1 -0
  99. package/dist/components/effects/WaterCausticsEffect.js +308 -0
  100. package/dist/components/effects/index.d.ts +9 -0
  101. package/dist/components/effects/index.d.ts.map +1 -1
  102. package/dist/components/effects/index.js +9 -0
  103. package/dist/components/index.d.ts +16 -0
  104. package/dist/components/index.d.ts.map +1 -1
  105. package/dist/components/index.js +8 -0
  106. package/dist/constants.d.ts +1 -1
  107. package/dist/constants.d.ts.map +1 -1
  108. package/dist/constants.js +1 -1
  109. package/dist/hooks/useModelObject.d.ts +6 -1
  110. package/dist/hooks/useModelObject.d.ts.map +1 -1
  111. package/dist/hooks/useModelObject.js +19 -6
  112. package/dist/types/gridConfigs.d.ts +98 -0
  113. package/dist/types/gridConfigs.d.ts.map +1 -1
  114. package/dist/types/index.d.ts +1 -1
  115. package/dist/types/index.d.ts.map +1 -1
  116. package/package.json +2 -2
@@ -1,162 +1,558 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef, useMemo, useEffect } from 'react';
3
- import { useFrame } from '@react-three/fiber';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
4
3
  import * as THREE from 'three';
4
+ import { useFrame } from '@react-three/fiber';
5
5
  import { applyGenerativeAnimations } from '../../utils/handleGenerativeEffectsAnimations.js';
6
6
  import { useObjectReady } from '../../hooks/useObjectReady.js';
7
- // Ring vertex — organic warp + vertical ripple
8
- const vertexShader = /* glsl */ `
9
- uniform float uProgress;
10
- uniform float uExpansionScale;
11
-
12
- void main() {
13
- vec3 pos = position;
14
- float expansion = 1.0 + uProgress * uExpansionScale;
15
- pos.x *= expansion;
16
- pos.y *= expansion;
17
-
18
- // Organic warp: slight Z ripple based on angle along the torus
19
- float angle = atan(pos.y, pos.x);
20
- float warp = sin(angle * 8.0 + uProgress * 18.0) * 0.018 * (1.0 - uProgress);
21
- pos.z += warp;
7
+ /**
8
+ * A Sedov–Taylor blast wave: a razor-bright front over a compressed shell that bursts out, slows and cools, raising dust and a brief condensation dome. Use for explosions, impacts, sci-fi pulses.
9
+ *
10
+ * R ∝ t^(2/5) · ρ₂/ρ₁ = (γ+1)/(γ−1) = 4 · ρ(r) ≈ 4(r/R)^9 · front heat ∝ Ṙ² · grain kick at t₀ = R⁻¹(r₀)
11
+ */
12
+ const NOISE3 = /* glsl */ `
13
+ float hash13(vec3 p3){
14
+ p3 = fract(p3 * 0.1031);
15
+ p3 += dot(p3, p3.zyx + 31.32);
16
+ return fract((p3.x + p3.y) * p3.z);
17
+ }
18
+ float vnoise(vec3 p){
19
+ vec3 i = floor(p);
20
+ vec3 f = fract(p);
21
+ f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
22
+ float n000 = hash13(i);
23
+ float n100 = hash13(i + vec3(1.0, 0.0, 0.0));
24
+ float n010 = hash13(i + vec3(0.0, 1.0, 0.0));
25
+ float n110 = hash13(i + vec3(1.0, 1.0, 0.0));
26
+ float n001 = hash13(i + vec3(0.0, 0.0, 1.0));
27
+ float n101 = hash13(i + vec3(1.0, 0.0, 1.0));
28
+ float n011 = hash13(i + vec3(0.0, 1.0, 1.0));
29
+ float n111 = hash13(i + vec3(1.0, 1.0, 1.0));
30
+ return mix(mix(mix(n000, n100, f.x), mix(n010, n110, f.x), f.y),
31
+ mix(mix(n001, n101, f.x), mix(n011, n111, f.x), f.y), f.z);
32
+ }
33
+ // low-frequency fBm; the rotation between octaves hides the lattice
34
+ const mat3 FBM_ROT = mat3(0.00, 0.80, 0.60, -0.80, 0.36, -0.48, -0.60, -0.48, 0.64);
35
+ float fbm3(vec3 p, int octaves){
36
+ float a = 0.5, s = 0.0;
37
+ for (int i = 0; i < 5; i++){
38
+ if (i >= octaves) break;
39
+ s += a * vnoise(p);
40
+ p = FBM_ROT * p * 2.02;
41
+ a *= 0.5;
42
+ }
43
+ return s;
44
+ }
45
+ `;
46
+ // ---------------------------------------------------------------------------
47
+ // Sedov–Taylor blast wave.
48
+ // A point explosion of energy E in gas of density rho has only one length
49
+ // scale, R = (E t^2 / rho)^(1/5), so the front bursts out fast and then
50
+ // coasts: R ~ t^(2/5), speed dR/dt ~ t^(-3/5). The front is a strong shock:
51
+ // the gas jumps to (gamma+1)/(gamma-1) = 4 times its density (gamma = 5/3)
52
+ // and nearly all swept-up mass sits in a thin shell, rho(r) ~ 4 (r/R)^9,
53
+ // leaving a hot rarefied interior. Post-shock temperature goes as the front
54
+ // speed squared, so the front starts blue-white and cools as it slows. A thin
55
+ // decelerating shell is overstable (Vishniac): low-order lobes grow with age.
56
+ // Every slot carries (tau, R/Rmax, dR/dtau, fade) and (seed, flash, heat, life)
57
+ // from the CPU clock, so the shaders only draw the state.
58
+ // ---------------------------------------------------------------------------
59
+ const waveCommon = /* glsl */ `
60
+ uniform float uTime;
61
+ uniform float uBound;
62
+ uniform float uExpansionRadius;
63
+ uniform float uThickness;
64
+ uniform float uInstability;
65
+ uniform float uDeceleration;
66
+ uniform float uShape;
67
+ uniform vec4 uWaveA[4]; // tau (0..1), R / Rmax, dR/dtau, fade
68
+ uniform vec4 uWaveB[4]; // seed, birth flash, heat (0..1), life in seconds
69
+ ${NOISE3}
22
70
 
23
- gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
71
+ // Vishniac lobes on a ring: azimuthal modes l = 3..6, amplitude growing with age
72
+ float ringLobes(float th, float seed, float tau){
73
+ float s = 0.0;
74
+ for (int l = 3; l <= 6; l++){
75
+ float fl = float(l);
76
+ s += sin(fl * th + seed * fl * 2.39 + uTime * 0.04 * (7.0 - fl)) / (fl - 1.5);
77
+ }
78
+ return s * uInstability * 0.1 * pow(tau, 1.3);
79
+ }
80
+ // the same on a sphere: low-frequency noise on the direction
81
+ float shellLobes(vec3 dir, float seed, float tau){
82
+ float n = vnoise(dir * 1.7 + vec3(seed * 17.0, seed * 5.0 + uTime * 0.03, 3.0));
83
+ n += 0.5 * vnoise(dir * 3.1 + vec3(seed * 9.0, 7.0, uTime * 0.02));
84
+ return (n - 0.75) * uInstability * 0.16 * pow(tau, 1.3);
85
+ }
86
+ // hot front colour: blue-white while the shock is strong, cooling to the rim colour
87
+ vec3 frontColour(vec3 rim, float heat){
88
+ return mix(rim, vec3(0.8, 0.9, 1.0), heat) * (0.55 + 2.1 * heat * heat);
24
89
  }
25
90
  `;
26
- const fragmentShader = /* glsl */ `
27
- uniform float uProgress;
28
- uniform vec3 uColor;
29
- uniform vec3 uRimColor;
91
+ // Ground mode: one disc in the XZ plane that sums all slots.
92
+ const discVert = /* glsl */ `
93
+ uniform float uExpansionRadius;
94
+ uniform float uBound;
95
+ varying vec3 vLocal;
96
+ void main(){
97
+ vec3 p = vec3(position.x, 0.0, -position.y) * uExpansionRadius * uBound * 1.25;
98
+ vLocal = p;
99
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
100
+ }
101
+ `;
102
+ const discFrag = /* glsl */ `
103
+ uniform vec3 uColor;
104
+ uniform vec3 uRimColor;
105
+ uniform float uIntensity;
30
106
  uniform float uOpacity;
31
- uniform float u_time;
32
-
33
- // Cheap 1D noise for surface texture
34
- float hash(float n){ return fract(sin(n) * 43758.5453); }
35
-
36
- void main() {
37
- // Core fade
38
- float fade = 1.0 - uProgress;
39
-
40
- // Leading-edge corona: ultra-bright blast front
41
- float corona = exp(-uProgress * 6.0) * 2.5;
42
-
43
- // High-frequency energy shimmer on the ring surface
44
- float shimmer = 0.75 + 0.25 * hash(uProgress * 40.0 + u_time * 7.0);
45
-
46
- // Rim glow (raised power = tighter, brighter rim)
47
- float rim = pow(max(0.0, 1.0 - uProgress), 4.0);
48
-
49
- // Chromatic split — push rim color toward white at the leading edge
50
- vec3 rimBoosted = mix(uRimColor, vec3(1.4, 1.4, 1.6), corona * 0.5);
51
- vec3 finalColor = mix(uColor, rimBoosted, rim + corona * 0.4);
52
- finalColor += rimBoosted * corona * 0.6;
53
-
54
- float alpha = fade * uOpacity * shimmer;
55
- if (alpha < 0.005) discard;
56
-
57
- gl_FragColor = vec4(finalColor, alpha);
107
+ ${waveCommon}
108
+ varying vec3 vLocal;
109
+ void main(){
110
+ float r = length(vLocal.xz);
111
+ float th = atan(vLocal.z, vLocal.x);
112
+ float Rmax = uExpansionRadius * uBound;
113
+ float aa = fwidth(r);
114
+ vec3 col = vec3(0.0);
115
+ for (int i = 0; i < 4; i++){
116
+ vec4 A = uWaveA[i];
117
+ if (A.w <= 0.0) continue;
118
+ vec4 B = uWaveB[i];
119
+ float Re = A.y * Rmax * (1.0 + ringLobes(th, B.x, A.x));
120
+ float d = r - Re;
121
+ float w = max(uThickness * uBound * 0.5, aa * 0.8);
122
+ // front: razor leading edge (the gas ahead is untouched), slightly softer behind the jump
123
+ float lead = exp(-d * d / (w * w * 0.45));
124
+ float trail = exp(-d * d / (w * w * 2.2));
125
+ float front = mix(trail, lead, smoothstep(-aa, aa, d));
126
+ float behind = 1.0 - smoothstep(-w, w * 0.5, d);
127
+ // compressed shell rho/4 = (r/R)^9 and the rarefied hot interior
128
+ float x = clamp(r / max(Re, 1e-4), 0.0, 1.0);
129
+ float shell = pow(x, 9.0) * behind;
130
+ float interior = x * x * behind;
131
+ // filaments along the front, fixed to the gas (they only change with age)
132
+ float fil = 0.7 + 0.6 * vnoise(vec3(cos(th) * 6.0, sin(th) * 6.0, B.x * 13.0 + A.x * 1.2));
133
+ vec3 hot = frontColour(uRimColor, B.z);
134
+ vec3 c = hot * front * fil;
135
+ c += mix(uRimColor, hot, 0.5) * shell * (0.3 + 0.7 * B.z) * fil;
136
+ c += mix(uRimColor, uColor, 0.5) * interior * 0.05 * B.z;
137
+ // birth flash: the front and a ground glow burn in the flash colour
138
+ c += uColor * B.y * (front * 2.5 + 1.1 * exp(-r * r / (uBound * uBound * 1.6)));
139
+ col += c * A.w;
140
+ }
141
+ col *= uIntensity * uOpacity;
142
+ if (max(col.r, max(col.g, col.b)) < 0.002) discard;
143
+ gl_FragColor = vec4(col, 1.0);
144
+ }
145
+ `;
146
+ // Sphere mode (and the condensation dome): an instanced unit sphere per slot.
147
+ const sphereVert = /* glsl */ `
148
+ attribute float aSlot;
149
+ uniform float uHaze;
150
+ ${waveCommon}
151
+ varying vec3 vN;
152
+ varying vec3 vView;
153
+ varying vec3 vDir;
154
+ varying vec4 vA;
155
+ varying vec4 vB;
156
+ void main(){
157
+ int i = int(aSlot + 0.5);
158
+ vA = uWaveA[i];
159
+ vB = uWaveB[i];
160
+ vec3 dir = normalize(position);
161
+ vDir = dir;
162
+ float R = vA.y * uExpansionRadius * uBound * mix(1.0, 0.86, uHaze);
163
+ vec3 p = dir * R * (1.0 + shellLobes(dir, vB.x, vA.x));
164
+ if (vA.w <= 0.0) p = vec3(0.0);
165
+ vec4 wp = modelMatrix * vec4(p, 1.0);
166
+ vN = normalize(mat3(modelMatrix) * dir);
167
+ vView = cameraPosition - wp.xyz;
168
+ gl_Position = projectionMatrix * viewMatrix * wp;
58
169
  }
59
170
  `;
60
- // Flash disc at ring birth — bright flat burst that fades in ~0.2 progress
61
- const flashFrag = /* glsl */ `
62
- uniform float uProgress;
63
- uniform vec3 uColor;
171
+ // Emission of a thin spherical shell seen at impact parameter b: the chord through
172
+ // the shell between radii Ri and Ro. It peaks just inside the silhouette, which is
173
+ // the physically exact version of a Fresnel edge.
174
+ const shellFrag = /* glsl */ `
175
+ uniform vec3 uColor;
176
+ uniform vec3 uRimColor;
177
+ uniform float uIntensity;
64
178
  uniform float uOpacity;
65
-
66
- varying vec2 vUv;
67
-
68
- void main() {
69
- vec2 c = vUv * 2.0 - 1.0;
70
- float dist = length(c);
71
- if (dist > 1.0) discard;
72
- float fade = exp(-uProgress * 12.0);
73
- float radial = 1.0 - smoothstep(0.4, 1.0, dist);
74
- float alpha = fade * radial * uOpacity * 0.9;
75
- if (alpha < 0.005) discard;
76
- gl_FragColor = vec4(uColor * 2.0, alpha);
179
+ uniform float uExpansionRadius;
180
+ uniform float uBound;
181
+ uniform float uThickness;
182
+ varying vec3 vN;
183
+ varying vec3 vView;
184
+ varying vec3 vDir;
185
+ varying vec4 vA;
186
+ varying vec4 vB;
187
+ ${NOISE3}
188
+ float chord(float ro, float ri, float b){
189
+ return 2.0 * (sqrt(max(ro * ro - b * b, 0.0)) - sqrt(max(ri * ri - b * b, 0.0)));
190
+ }
191
+ void main(){
192
+ if (vA.w <= 0.0) discard;
193
+ float mu = clamp(abs(dot(normalize(vN), normalize(vView))), 0.0, 1.0);
194
+ float R = max(vA.y * uExpansionRadius * uBound, 1e-3);
195
+ float b = R * sqrt(1.0 - mu * mu);
196
+ // no fwidth here: the chord profile is already smooth in b, and fwidth(b)
197
+ // diverges at the centre of the disc where b = R*sqrt(1 - mu*mu)
198
+ float w = clamp(uThickness * uBound * 0.5, R * 0.002, R * 0.2);
199
+ float front = chord(R, R - w, b) / (2.0 * sqrt(2.0 * R * w - w * w));
200
+ // compressed shell rho/4 ~ (r/R)^9: three thin slabs, weighted by that profile
201
+ float r1 = R * 0.965, r2 = R * 0.9, r3 = R * 0.8;
202
+ float shell = 0.35 * chord(R - w, r1, b) / (2.0 * sqrt(max(R * R - r1 * r1, 1e-6)))
203
+ + 0.09 * chord(r1, r2, b) / (2.0 * sqrt(max(R * R - r2 * r2, 1e-6)))
204
+ + 0.02 * chord(r2, r3, b) / (2.0 * sqrt(max(R * R - r3 * r3, 1e-6)));
205
+ // filaments fixed to the gas: they only change with age, never per frame
206
+ float fil = 0.7 + 0.6 * vnoise(vDir * 4.5 + vec3(vB.x * 13.0, vA.x * 1.2, 2.0));
207
+ vec3 hot = mix(uRimColor, vec3(0.8, 0.9, 1.0), vB.z) * (0.55 + 2.1 * vB.z * vB.z);
208
+ // a hot plasma radiates as density squared (emission measure), so square the
209
+ // integrated chords: the limb keeps its value and the disc goes dark
210
+ vec3 c = hot * front * front * 2.4 * fil;
211
+ c += mix(uRimColor, hot, 0.5) * shell * shell * (0.9 + 2.5 * vB.z) * fil;
212
+ // birth flash: the young fireball glows through
213
+ c += uColor * vB.y * (front * 2.5 + 0.9 * mu * mu);
214
+ c *= vA.w * uIntensity * uOpacity;
215
+ if (max(c.r, max(c.g, c.b)) < 0.002) discard;
216
+ gl_FragColor = vec4(c, 1.0);
77
217
  }
78
218
  `;
79
- const flashVert = /* glsl */ `
80
- varying vec2 vUv;
81
- void main() {
82
- vUv = uv;
83
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
219
+ // Wilson cloud: the rarefaction behind the front cools humid air below its dew point
220
+ // for a moment. The haze forms just behind the shock, then evaporates from the thin
221
+ // parts first (rising coverage threshold) as the pressure recovers.
222
+ const hazeFrag = /* glsl */ `
223
+ uniform vec3 uColor;
224
+ uniform float uOpacity;
225
+ uniform float uCondensation;
226
+ uniform float uShape;
227
+ varying vec3 vN;
228
+ varying vec3 vView;
229
+ varying vec3 vDir;
230
+ varying vec4 vA;
231
+ varying vec4 vB;
232
+ ${NOISE3}
233
+ void main(){
234
+ float tau = vA.x;
235
+ float life = uCondensation * smoothstep(0.02, 0.09, tau) * (1.0 - smoothstep(0.14, 0.42, tau));
236
+ if (vA.w <= 0.0 || life < 0.002) discard;
237
+ float ground = 1.0 - step(0.5, uShape);
238
+ if (ground > 0.5 && vDir.y < -0.01) discard;
239
+ float n = fbm3(vDir * 2.3 + vec3(vB.x * 11.0, 1.0, vB.x * 5.0), 3);
240
+ float th = mix(0.3, 0.62, smoothstep(0.06, 0.42, tau));
241
+ float dens = smoothstep(th, th + 0.2, n);
242
+ float mu = clamp(abs(dot(normalize(vN), normalize(vView))), 0.0, 1.0);
243
+ float limb = min(1.0 / max(mu, 0.2), 3.0) * mix(0.45, 0.3, step(0.5, uShape));
244
+ float a = life * dens * limb * 0.3 * uOpacity * vA.w;
245
+ // the dome sits on the ground: fade its skirt in over a short height
246
+ a *= mix(1.0, smoothstep(-0.01, 0.12, vDir.y), ground);
247
+ if (a < 0.003) discard;
248
+ vec3 col = vec3(0.8, 0.84, 0.9) * (0.55 + 0.45 * clamp(vDir.y, 0.0, 1.0)) + uColor * vB.y * 0.6;
249
+ gl_FragColor = vec4(col * a, a);
84
250
  }
85
251
  `;
86
- const ShockwaveEffect = ({ boundingRadius, config, animations, onObjectReady, onObjectDispose }) => {
87
- const { ringCount, expansionRadius, thickness, intervalSeconds, color, rimColor, opacity, speed, intensity } = config;
88
- const startValuesCacheRef = useRef({});
89
- const rootRef = useRef(null);
90
- useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
91
- const meshRefs = useRef([]);
92
- const flashRefs = useRef([]);
93
- const makeRingStates = (count, interval) => Array.from({ length: count }, (_, i) => ({
94
- progress: 0,
95
- cooldown: (i / Math.max(count, 1)) * interval,
96
- }));
97
- const ringStates = useRef(makeRingStates(ringCount, intervalSeconds));
98
- useEffect(() => {
99
- ringStates.current = makeRingStates(ringCount, intervalSeconds);
100
- }, [ringCount, intervalSeconds]);
101
- const colorVec = useMemo(() => { const c = new THREE.Color(color); return new THREE.Vector3(c.r, c.g, c.b); }, [color]);
102
- const rimColorVec = useMemo(() => { const c = new THREE.Color(rimColor); return new THREE.Vector3(c.r, c.g, c.b); }, [rimColor]);
103
- const materials = useMemo(() => Array.from({ length: ringCount }, () => new THREE.ShaderMaterial({
104
- vertexShader,
105
- fragmentShader,
106
- uniforms: {
107
- uProgress: { value: 0 },
108
- uExpansionScale: { value: (expansionRadius - 1) * intensity },
109
- uColor: { value: colorVec },
110
- uRimColor: { value: rimColorVec },
111
- uOpacity: { value: opacity },
112
- u_time: { value: 0 },
113
- },
114
- transparent: true,
115
- depthWrite: false,
116
- blending: THREE.AdditiveBlending,
117
- side: THREE.DoubleSide,
118
- })), [ringCount, expansionRadius, intensity, colorVec, rimColorVec, opacity]);
119
- const flashMaterials = useMemo(() => Array.from({ length: ringCount }, () => new THREE.ShaderMaterial({
120
- vertexShader: flashVert,
121
- fragmentShader: flashFrag,
122
- uniforms: {
123
- uProgress: { value: 0 },
124
- uColor: { value: colorVec },
125
- uOpacity: { value: opacity },
252
+ // Dust skirt: a grain at r0 is hit when R(t0) = r0, i.e. tau0 = r0^(1/p). It is thrown
253
+ // up and out with a velocity proportional to the front speed at that moment, then
254
+ // follows a ballistic path with linear drag and settles; fine grains hang longer.
255
+ const dustVert = /* glsl */ `
256
+ attribute vec4 aDust; // slot, r0 (0..1), azimuth, visibility rank
257
+ attribute vec4 aDust2; // rand, rand, cos(polar), fine (0/1)
258
+ uniform float uDust;
259
+ uniform vec3 uRimColor;
260
+ ${waveCommon}
261
+ varying float vAlpha;
262
+ varying vec3 vCol;
263
+ varying float vSoft;
264
+ void main(){
265
+ int i = int(aDust.x + 0.5);
266
+ vec4 A = uWaveA[i];
267
+ vec4 B = uWaveB[i];
268
+ float p = mix(1.0, 0.4, uDeceleration);
269
+ float Rmax = uExpansionRadius * uBound;
270
+ float r0min = min(1.05 / max(uExpansionRadius, 1.1), 0.9);
271
+ float r0 = sqrt(mix(r0min * r0min, 0.94, aDust.y));
272
+ float tau0 = pow(r0, 1.0 / p);
273
+ float L = max(B.w, 0.1);
274
+ float s = (A.x - tau0) * L;
275
+ float fine = aDust2.w;
276
+ float v0 = min(p * pow(max(tau0, 1e-3), p - 1.0), 3.0) * Rmax / L;
277
+ float az = aDust.z;
278
+ vec3 pos;
279
+ if (uShape < 0.5){
280
+ float k = mix(1.4, 2.6, fine);
281
+ float g = mix(2.0, 0.4, fine) * uBound;
282
+ float vx = v0 * (0.12 + 0.3 * aDust2.x);
283
+ float vy = v0 * (0.3 + 1.0 * aDust2.y * aDust2.y) * mix(1.0, 0.7, fine);
284
+ float e = (1.0 - exp(-k * max(s, 0.0))) / k;
285
+ float rh = r0 * Rmax + vx * e;
286
+ float y = max((vy + g / k) * e - g * max(s, 0.0) / k, 0.0);
287
+ pos = vec3(cos(az) * rh, y, sin(az) * rh);
288
+ } else {
289
+ // snowplough: the grain is swept into the shell and rides just behind the front
290
+ float R = A.y * Rmax;
291
+ float sp = sqrt(max(1.0 - aDust2.z * aDust2.z, 0.0));
292
+ vec3 dir = vec3(sp * cos(az), aDust2.z, sp * sin(az));
293
+ float rr = max(r0 * Rmax, R * (0.8 + 0.14 * aDust2.x));
294
+ pos = dir * rr;
295
+ }
296
+ float on = step(0.0, s) * step(aDust.w, uDust) * A.w;
297
+ float fade = smoothstep(0.0, 0.1, s) * exp(-s / (L * mix(0.2, 0.36, fine)));
298
+ vAlpha = on * fade * mix(0.55, 0.26, fine) * (0.5 + 0.5 * uDust);
299
+ vSoft = fine;
300
+ // dusty brown, lit for a moment by the hot gas that just passed
301
+ vec3 dustC = mix(vec3(0.54, 0.42, 0.31), vec3(0.66, 0.58, 0.5), fine * 0.6 + aDust2.x * 0.3);
302
+ vCol = dustC + uRimColor * exp(-s * 4.0) * B.z * 1.2;
303
+ vec4 mv = modelViewMatrix * vec4(pos, 1.0);
304
+ float size = mix(0.9 + 1.1 * aDust2.y, 6.0 + 5.0 * aDust2.x, fine) * (1.0 + 0.8 * fine * min(s / L, 1.0));
305
+ gl_PointSize = vAlpha > 0.001 ? size * (18.0 * uBound / max(-mv.z, 0.05)) : 0.0;
306
+ gl_Position = projectionMatrix * mv;
307
+ }
308
+ `;
309
+ const dustFrag = /* glsl */ `
310
+ uniform float uOpacity;
311
+ varying float vAlpha;
312
+ varying vec3 vCol;
313
+ varying float vSoft;
314
+ void main(){
315
+ float d = length(gl_PointCoord - 0.5) * 2.0;
316
+ if (d > 1.0 || vAlpha < 0.001) discard;
317
+ float g = mix(exp(-d * d * 5.0), exp(-d * d * 2.5) * (1.0 - d), vSoft) * (1.0 - smoothstep(0.75, 1.0, d));
318
+ float a = g * vAlpha * uOpacity;
319
+ if (a < 0.002) discard;
320
+ gl_FragColor = vec4(vCol * a, a);
321
+ }
322
+ `;
323
+ function mulberry32(seed) {
324
+ return () => {
325
+ seed |= 0;
326
+ seed = (seed + 0x6d2b79f5) | 0;
327
+ let r = Math.imul(seed ^ (seed >>> 15), 1 | seed);
328
+ r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;
329
+ return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
330
+ };
331
+ }
332
+ function smooth01(e0, e1, x) {
333
+ const t = Math.min(Math.max((x - e0) / (e1 - e0), 0), 1);
334
+ return t * t * (3 - 2 * t);
335
+ }
336
+ const DUST_PER_SLOT = 1600;
337
+ function buildDustGeometry() {
338
+ const n = DUST_PER_SLOT * 4;
339
+ const rand = mulberry32(31);
340
+ const a1 = new Float32Array(n * 4);
341
+ const a2 = new Float32Array(n * 4);
342
+ for (let i = 0; i < n; i++) {
343
+ a1[i * 4] = i % 4;
344
+ a1[i * 4 + 1] = rand();
345
+ a1[i * 4 + 2] = rand() * Math.PI * 2;
346
+ a1[i * 4 + 3] = rand() * 0.999;
347
+ a2[i * 4] = rand();
348
+ a2[i * 4 + 1] = rand();
349
+ a2[i * 4 + 2] = rand() * 2 - 1;
350
+ a2[i * 4 + 3] = rand() < 0.55 ? 1 : 0;
351
+ }
352
+ const geo = new THREE.BufferGeometry();
353
+ geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(n * 3), 3));
354
+ geo.setAttribute('aDust', new THREE.BufferAttribute(a1, 4));
355
+ geo.setAttribute('aDust2', new THREE.BufferAttribute(a2, 4));
356
+ return geo;
357
+ }
358
+ function buildShellGeometry() {
359
+ const sph = new THREE.SphereGeometry(1, 72, 48);
360
+ const geo = new THREE.InstancedBufferGeometry();
361
+ geo.setIndex(sph.getIndex());
362
+ geo.setAttribute('position', sph.getAttribute('position'));
363
+ geo.setAttribute('normal', sph.getAttribute('normal'));
364
+ geo.setAttribute('aSlot', new THREE.InstancedBufferAttribute(new Float32Array([0, 1, 2, 3]), 1));
365
+ geo.instanceCount = 4;
366
+ sph.dispose();
367
+ return geo;
368
+ }
369
+ function createUniforms() {
370
+ return {
371
+ uTime: { value: 0 },
372
+ uSpeed: { value: 1 },
373
+ uIntensity: { value: 1 },
374
+ uOpacity: { value: 0.8 },
375
+ uColor: { value: new THREE.Color('#ffffff') },
376
+ uRimColor: { value: new THREE.Color('#88ccff') },
377
+ uRingCount: { value: 2 },
378
+ uExpansionRadius: { value: 3 },
379
+ uThickness: { value: 0.04 },
380
+ uIntervalSeconds: { value: 2 },
381
+ uShape: { value: 0 },
382
+ uDeceleration: { value: 0.85 },
383
+ uInstability: { value: 0.4 },
384
+ uDust: { value: 0.5 },
385
+ uCondensation: { value: 0.3 },
386
+ uBound: { value: 1 },
387
+ uWaveA: { value: [new THREE.Vector4(), new THREE.Vector4(), new THREE.Vector4(), new THREE.Vector4()] },
388
+ uWaveB: { value: [new THREE.Vector4(), new THREE.Vector4(), new THREE.Vector4(), new THREE.Vector4()] },
389
+ };
390
+ }
391
+ function applyConfig(u, c, bound, env) {
392
+ u.uSpeed.value = c.speed;
393
+ u.uIntensity.value = c.intensity;
394
+ u.uOpacity.value = c.opacity;
395
+ u.uColor.value.set(c.color);
396
+ u.uRimColor.value.set(c.rimColor);
397
+ u.uRingCount.value = c.ringCount;
398
+ u.uExpansionRadius.value = c.expansionRadius;
399
+ u.uThickness.value = c.thickness;
400
+ u.uIntervalSeconds.value = c.intervalSeconds;
401
+ u.uShape.value = c.shape;
402
+ u.uDeceleration.value = c.deceleration;
403
+ u.uInstability.value = c.instability;
404
+ u.uDust.value = c.dust;
405
+ u.uCondensation.value = c.condensation;
406
+ u.uBound.value = bound;
407
+ }
408
+ function build(u, c, bound, env) {
409
+ const add = { transparent: true, depthWrite: false, blending: THREE.AdditiveBlending };
410
+ const premul = { transparent: true, depthWrite: false, blending: THREE.NormalBlending, premultipliedAlpha: true };
411
+ const discMat = new THREE.ShaderMaterial({ vertexShader: discVert, fragmentShader: discFrag, uniforms: u, side: THREE.DoubleSide, ...add });
412
+ const shellU = { ...u, uHaze: { value: 0 } };
413
+ const hazeU = { ...u, uHaze: { value: 1 } };
414
+ const shellMat = new THREE.ShaderMaterial({ vertexShader: sphereVert, fragmentShader: shellFrag, uniforms: shellU, ...add });
415
+ const hazeMat = new THREE.ShaderMaterial({ vertexShader: sphereVert, fragmentShader: hazeFrag, uniforms: hazeU, ...premul });
416
+ const dustMat = new THREE.ShaderMaterial({ vertexShader: dustVert, fragmentShader: dustFrag, uniforms: u, ...premul });
417
+ const quad = new THREE.PlaneGeometry(2, 2);
418
+ const shellGeo = buildShellGeometry();
419
+ const dustGeo = buildDustGeometry();
420
+ const disc = new THREE.Mesh(quad, discMat);
421
+ const shell = new THREE.Mesh(shellGeo, shellMat);
422
+ const haze = new THREE.Mesh(shellGeo, hazeMat);
423
+ const dust = new THREE.Points(dustGeo, dustMat);
424
+ disc.frustumCulled = false;
425
+ shell.frustumCulled = false;
426
+ haze.frustumCulled = false;
427
+ dust.frustumCulled = false;
428
+ disc.renderOrder = 0;
429
+ shell.renderOrder = 0;
430
+ haze.renderOrder = 1;
431
+ dust.renderOrder = 2;
432
+ const group = new THREE.Group();
433
+ group.add(disc, shell, haze, dust);
434
+ // CPU clock: one launch every intervalSeconds, each front lives ringCount intervals
435
+ const ages = new Float32Array(4);
436
+ const seeds = new Float32Array(4);
437
+ const alive = [false, false, false, false];
438
+ let clock = 0;
439
+ let first = true;
440
+ let launches = 0;
441
+ function setVisibility() {
442
+ const sphere = u.uShape.value >= 0.5;
443
+ disc.visible = !sphere;
444
+ shell.visible = sphere;
445
+ haze.visible = u.uCondensation.value > 0.001;
446
+ dust.visible = u.uDust.value > 0.001;
447
+ }
448
+ return {
449
+ object: group,
450
+ materials: [discMat, shellMat, hazeMat, dustMat],
451
+ apply(c2) {
452
+ setVisibility();
126
453
  },
127
- transparent: true,
128
- depthWrite: false,
129
- blending: THREE.AdditiveBlending,
130
- side: THREE.DoubleSide,
131
- })), [ringCount, colorVec, opacity]);
132
- const radius = boundingRadius;
133
- useFrame((state, delta) => {
134
- const t = state.clock.getElapsedTime();
135
- for (let i = 0; i < ringCount; i++) {
136
- const state_ = ringStates.current[i];
137
- const mat = materials[i];
138
- const fMat = flashMaterials[i];
139
- mat.uniforms.u_time.value = t;
140
- if (state_.cooldown > 0) {
141
- state_.cooldown -= delta;
142
- mat.uniforms.uProgress.value = 1;
143
- fMat.uniforms.uProgress.value = 1;
144
- continue;
454
+ update(dt, camera) {
455
+ const dts = dt * u.uSpeed.value;
456
+ u.uTime.value += dts;
457
+ const n = Math.min(Math.max(Math.round(u.uRingCount.value), 1), 4);
458
+ const interval = Math.max(0.05, u.uIntervalSeconds.value);
459
+ const life = interval * n;
460
+ const p = 1 - 0.6 * Math.min(Math.max(u.uDeceleration.value, 0), 1);
461
+ for (let i = 0; i < 4; i++) {
462
+ if (!alive[i])
463
+ continue;
464
+ ages[i] += dts;
465
+ if (ages[i] >= life || i >= n)
466
+ alive[i] = false;
145
467
  }
146
- state_.progress += delta * speed;
147
- const p = Math.min(state_.progress, 1);
148
- mat.uniforms.uProgress.value = p;
149
- fMat.uniforms.uProgress.value = p;
150
- if (state_.progress >= 1) {
151
- state_.progress = 0;
152
- state_.cooldown = intervalSeconds / speed;
468
+ clock += dts;
469
+ if (first || clock >= interval) {
470
+ clock = first ? 0 : Math.min(clock - interval, interval);
471
+ first = false;
472
+ let slot = -1;
473
+ for (let i = 0; i < n && slot < 0; i++)
474
+ if (!alive[i])
475
+ slot = i;
476
+ if (slot < 0) {
477
+ slot = 0;
478
+ for (let i = 1; i < n; i++)
479
+ if (ages[i] > ages[slot])
480
+ slot = i;
481
+ }
482
+ alive[slot] = true;
483
+ ages[slot] = 0;
484
+ seeds[slot] = (launches * 0.6180339887 + 0.137) % 1;
485
+ launches++;
153
486
  }
154
- }
155
- if (animations?.length) {
156
- applyGenerativeAnimations([...materials, ...flashMaterials], animations, startValuesCacheRef.current, Date.now(), false);
157
- }
487
+ for (let i = 0; i < 4; i++) {
488
+ const A = u.uWaveA.value[i];
489
+ const B = u.uWaveB.value[i];
490
+ if (!alive[i]) {
491
+ A.set(0, 0, 0, 0);
492
+ B.set(0, 0, 0, life);
493
+ continue;
494
+ }
495
+ const tau = Math.min(ages[i] / life, 1);
496
+ const f = Math.pow(tau, p);
497
+ const v = Math.min(p * Math.pow(Math.max(tau, 1e-3), p - 1), 6);
498
+ const heat = (v * v) / (v * v + 1.2);
499
+ const fade = 1 - smooth01(0.55, 1, tau);
500
+ const flash = Math.exp(-ages[i] / 0.16);
501
+ A.set(tau, f, v, fade);
502
+ B.set(seeds[i], flash, heat, life);
503
+ }
504
+ setVisibility();
505
+ },
506
+ dispose() {
507
+ quad.dispose();
508
+ shellGeo.dispose();
509
+ dustGeo.dispose();
510
+ discMat.dispose();
511
+ shellMat.dispose();
512
+ hazeMat.dispose();
513
+ dustMat.dispose();
514
+ },
515
+ };
516
+ }
517
+ /** Defaults for every knob, so an older saved config still renders. */
518
+ const DEFAULTS = {
519
+ intensity: 1,
520
+ speed: 1,
521
+ color: '#ffffff',
522
+ opacity: 0.8,
523
+ ringCount: 2,
524
+ expansionRadius: 3,
525
+ thickness: 0.04,
526
+ intervalSeconds: 2,
527
+ rimColor: '#88ccff',
528
+ shape: 0,
529
+ deceleration: 0.85,
530
+ instability: 0.4,
531
+ dust: 0.5,
532
+ condensation: 0.3,
533
+ };
534
+ const ShockwaveEffect = ({ boundingRadius, config, animations, hasHost = false, onObjectReady, onObjectDispose }) => {
535
+ const rootRef = useRef(null);
536
+ const animCache = useRef({});
537
+ useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
538
+ // a standalone effect gets boundingRadius 1
539
+ const b = boundingRadius > 0 ? boundingRadius : 1;
540
+ const cfg = useMemo(() => ({ ...DEFAULTS, ...config }), [config]);
541
+ const env = useMemo(() => ({ hasHost: hasHost }), [hasHost]);
542
+ // uniforms outlive the objects, so keyframe animations survive a rebuild
543
+ const uniforms = useMemo(() => createUniforms(), []);
544
+ // rebuilt only when the host size or a structural knob changes
545
+ const instance = useMemo(() => build(uniforms, { ...DEFAULTS, ...config }, b, env), [uniforms, b, env]);
546
+ useEffect(() => () => instance.dispose(), [instance]);
547
+ useLayoutEffect(() => {
548
+ applyConfig(uniforms, cfg, b, env);
549
+ instance.apply(cfg);
550
+ }, [instance, uniforms, cfg, b, env]);
551
+ useFrame((state, delta) => {
552
+ instance.update(Math.min(delta, 0.1), state.camera);
553
+ if (animations?.length)
554
+ applyGenerativeAnimations(instance.materials, animations, animCache.current, Date.now(), false);
158
555
  });
159
- const flashR = radius * 1.05;
160
- return (_jsx("group", { ref: rootRef, children: Array.from({ length: ringCount }, (_, i) => (_jsxs("group", { children: [_jsx("mesh", { ref: el => { meshRefs.current[i] = el; }, material: materials[i], rotation: [-Math.PI / 2, 0, 0], children: _jsx("torusGeometry", { args: [radius, thickness * intensity, 10, 80] }) }), _jsx("mesh", { ref: el => { flashRefs.current[i] = el; }, material: flashMaterials[i], rotation: [-Math.PI / 2, 0, 0], children: _jsx("circleGeometry", { args: [flashR, 48] }) })] }, i))) }));
556
+ return (_jsx("group", { ref: rootRef, children: _jsx("primitive", { object: instance.object }) }));
161
557
  };
162
558
  export default ShockwaveEffect;