@3driseai/3drise-viewer 0.1.16 → 0.1.18

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 (145) 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/Galaxy.d.ts.map +1 -1
  14. package/dist/components/Galaxy.js +2 -0
  15. package/dist/components/GirihGrid.d.ts +15 -0
  16. package/dist/components/GirihGrid.d.ts.map +1 -0
  17. package/dist/components/GirihGrid.js +62 -0
  18. package/dist/components/GravWaveGrid.d.ts +15 -0
  19. package/dist/components/GravWaveGrid.d.ts.map +1 -0
  20. package/dist/components/GravWaveGrid.js +62 -0
  21. package/dist/components/GridsGenerator.d.ts +2 -2
  22. package/dist/components/GridsGenerator.d.ts.map +1 -1
  23. package/dist/components/GridsGenerator.js +21 -0
  24. package/dist/components/HyperbolicGrid.d.ts +15 -0
  25. package/dist/components/HyperbolicGrid.d.ts.map +1 -0
  26. package/dist/components/HyperbolicGrid.js +61 -0
  27. package/dist/components/LayoutInstances.d.ts +13 -0
  28. package/dist/components/LayoutInstances.d.ts.map +1 -0
  29. package/dist/components/LayoutInstances.js +39 -0
  30. package/dist/components/ModelPrimitive.d.ts +2 -0
  31. package/dist/components/ModelPrimitive.d.ts.map +1 -1
  32. package/dist/components/ModelPrimitive.js +3 -3
  33. package/dist/components/ObjectNode.d.ts.map +1 -1
  34. package/dist/components/ObjectNode.js +44 -9
  35. package/dist/components/ParticlesGenerator.d.ts +10 -0
  36. package/dist/components/ParticlesGenerator.d.ts.map +1 -0
  37. package/dist/components/ParticlesGenerator.js +23 -0
  38. package/dist/components/ReactionDiffusionGrid.d.ts +15 -0
  39. package/dist/components/ReactionDiffusionGrid.d.ts.map +1 -0
  40. package/dist/components/ReactionDiffusionGrid.js +376 -0
  41. package/dist/components/WallpaperGrid.d.ts +15 -0
  42. package/dist/components/WallpaperGrid.d.ts.map +1 -0
  43. package/dist/components/WallpaperGrid.js +555 -0
  44. package/dist/components/cursor/CursorController.d.ts.map +1 -1
  45. package/dist/components/effects/BlackHoleEffect.d.ts +3 -3
  46. package/dist/components/effects/BlackHoleEffect.d.ts.map +1 -1
  47. package/dist/components/effects/BlackHoleEffect.js +293 -163
  48. package/dist/components/effects/DandelionSeedsEffect.d.ts +13 -0
  49. package/dist/components/effects/DandelionSeedsEffect.d.ts.map +1 -0
  50. package/dist/components/effects/DandelionSeedsEffect.js +510 -0
  51. package/dist/components/effects/FallingLeavesEffect.d.ts +13 -0
  52. package/dist/components/effects/FallingLeavesEffect.d.ts.map +1 -0
  53. package/dist/components/effects/FallingLeavesEffect.js +446 -0
  54. package/dist/components/effects/FieldLinesEffect.d.ts +11 -0
  55. package/dist/components/effects/FieldLinesEffect.d.ts.map +1 -0
  56. package/dist/components/effects/FieldLinesEffect.js +260 -0
  57. package/dist/components/effects/FireflySwarmEffect.d.ts +13 -0
  58. package/dist/components/effects/FireflySwarmEffect.d.ts.map +1 -0
  59. package/dist/components/effects/FireflySwarmEffect.js +408 -0
  60. package/dist/components/effects/GalaxyEffect.d.ts +13 -0
  61. package/dist/components/effects/GalaxyEffect.d.ts.map +1 -0
  62. package/dist/components/effects/GalaxyEffect.js +374 -0
  63. package/dist/components/effects/HarmonicShellEffect.d.ts +11 -0
  64. package/dist/components/effects/HarmonicShellEffect.d.ts.map +1 -0
  65. package/dist/components/effects/HarmonicShellEffect.js +209 -0
  66. package/dist/components/effects/IceCrystalsEffect.d.ts +3 -3
  67. package/dist/components/effects/IceCrystalsEffect.d.ts.map +1 -1
  68. package/dist/components/effects/IceCrystalsEffect.js +286 -166
  69. package/dist/components/effects/IceHaloEffect.d.ts +13 -0
  70. package/dist/components/effects/IceHaloEffect.d.ts.map +1 -0
  71. package/dist/components/effects/IceHaloEffect.js +306 -0
  72. package/dist/components/effects/LichtenbergEffect.d.ts +13 -0
  73. package/dist/components/effects/LichtenbergEffect.d.ts.map +1 -0
  74. package/dist/components/effects/LichtenbergEffect.js +517 -0
  75. package/dist/components/effects/MoleculesEffect.d.ts +3 -3
  76. package/dist/components/effects/MoleculesEffect.d.ts.map +1 -1
  77. package/dist/components/effects/MoleculesEffect.js +322 -164
  78. package/dist/components/effects/MurmurationEffect.d.ts +13 -0
  79. package/dist/components/effects/MurmurationEffect.d.ts.map +1 -0
  80. package/dist/components/effects/MurmurationEffect.js +539 -0
  81. package/dist/components/effects/PlanetaryRingsEffect.d.ts +13 -0
  82. package/dist/components/effects/PlanetaryRingsEffect.d.ts.map +1 -0
  83. package/dist/components/effects/PlanetaryRingsEffect.js +458 -0
  84. package/dist/components/effects/PortalEffect.d.ts +3 -3
  85. package/dist/components/effects/PortalEffect.d.ts.map +1 -1
  86. package/dist/components/effects/PortalEffect.js +240 -163
  87. package/dist/components/effects/ShockwaveEffect.d.ts +5 -3
  88. package/dist/components/effects/ShockwaveEffect.d.ts.map +1 -1
  89. package/dist/components/effects/ShockwaveEffect.js +538 -142
  90. package/dist/components/effects/SmokePlumeEffect.d.ts +3 -3
  91. package/dist/components/effects/SmokePlumeEffect.d.ts.map +1 -1
  92. package/dist/components/effects/SmokePlumeEffect.js +180 -116
  93. package/dist/components/effects/SmokeRingEffect.d.ts +3 -3
  94. package/dist/components/effects/SmokeRingEffect.d.ts.map +1 -1
  95. package/dist/components/effects/SmokeRingEffect.js +260 -135
  96. package/dist/components/effects/StrangeAttractorEffect.d.ts +11 -0
  97. package/dist/components/effects/StrangeAttractorEffect.d.ts.map +1 -0
  98. package/dist/components/effects/StrangeAttractorEffect.js +229 -0
  99. package/dist/components/effects/VolumetricFogEffect.d.ts +5 -3
  100. package/dist/components/effects/VolumetricFogEffect.d.ts.map +1 -1
  101. package/dist/components/effects/VolumetricFogEffect.js +211 -93
  102. package/dist/components/effects/WaterCausticsEffect.d.ts +13 -0
  103. package/dist/components/effects/WaterCausticsEffect.d.ts.map +1 -0
  104. package/dist/components/effects/WaterCausticsEffect.js +308 -0
  105. package/dist/components/effects/index.d.ts +9 -0
  106. package/dist/components/effects/index.d.ts.map +1 -1
  107. package/dist/components/effects/index.js +9 -0
  108. package/dist/components/index.d.ts +19 -0
  109. package/dist/components/index.d.ts.map +1 -1
  110. package/dist/components/index.js +10 -0
  111. package/dist/components/particles/BioluminescentWakeParticles.d.ts +12 -0
  112. package/dist/components/particles/BioluminescentWakeParticles.d.ts.map +1 -0
  113. package/dist/components/particles/BioluminescentWakeParticles.js +1123 -0
  114. package/dist/components/particles/BrownianDustParticles.d.ts +12 -0
  115. package/dist/components/particles/BrownianDustParticles.d.ts.map +1 -0
  116. package/dist/components/particles/BrownianDustParticles.js +757 -0
  117. package/dist/components/particles/ElectronOrbitalsParticles.d.ts +12 -0
  118. package/dist/components/particles/ElectronOrbitalsParticles.d.ts.map +1 -0
  119. package/dist/components/particles/ElectronOrbitalsParticles.js +944 -0
  120. package/dist/components/particles/GrinderSparksParticles.d.ts +12 -0
  121. package/dist/components/particles/GrinderSparksParticles.d.ts.map +1 -0
  122. package/dist/components/particles/GrinderSparksParticles.js +744 -0
  123. package/dist/components/particles/TidalStreamParticles.d.ts +12 -0
  124. package/dist/components/particles/TidalStreamParticles.d.ts.map +1 -0
  125. package/dist/components/particles/TidalStreamParticles.js +788 -0
  126. package/dist/components/particles/index.d.ts +11 -0
  127. package/dist/components/particles/index.d.ts.map +1 -0
  128. package/dist/components/particles/index.js +6 -0
  129. package/dist/constants.d.ts +1 -1
  130. package/dist/constants.d.ts.map +1 -1
  131. package/dist/constants.js +1 -1
  132. package/dist/hooks/useModelObject.d.ts +6 -1
  133. package/dist/hooks/useModelObject.d.ts.map +1 -1
  134. package/dist/hooks/useModelObject.js +19 -6
  135. package/dist/types/gridConfigs.d.ts +98 -0
  136. package/dist/types/gridConfigs.d.ts.map +1 -1
  137. package/dist/types/index.d.ts +1 -1
  138. package/dist/types/index.d.ts.map +1 -1
  139. package/dist/utils/particleTrails.d.ts +70 -0
  140. package/dist/utils/particleTrails.d.ts.map +1 -0
  141. package/dist/utils/particleTrails.js +369 -0
  142. package/dist/utils/pointerField.d.ts +28 -0
  143. package/dist/utils/pointerField.d.ts.map +1 -0
  144. package/dist/utils/pointerField.js +103 -0
  145. package/package.json +2 -2
@@ -0,0 +1,517 @@
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
+ * A Lichtenberg figure grown by the dielectric breakdown model: a glowing fern of electricity creeps over the surface, holds, fades and regrows. Use for energy, sci-fi, power and lightning scenes.
9
+ *
10
+ * Niemeyer–Pietronero DBM: ∇²φ = 0, φ = 0 on the cluster · P(grow at i) ∝ φᵢ^η · φ from point charges Σ(1 − R₁/rᵢⱼ)
11
+ */
12
+ // ---------------------------------------------------------------------------
13
+ // Dielectric breakdown model (Niemeyer, Pietronero & Wiesmann 1984).
14
+ // The discharge is a cluster held at potential 0 inside a field that is 1 far
15
+ // away; the potential obeys Laplace's equation. A new site next to the cluster
16
+ // breaks down with probability proportional to phi^eta. Tips poke furthest into
17
+ // the field, so they see the largest phi and grow fastest; that runaway is what
18
+ // makes the fern. eta ~ 1 gives dense, DLA-like bushes, eta ~ 3-4 thin lightning.
19
+ // Laplace is solved with the point-charge approximation of Kim et al. (2007):
20
+ // phi_i = sum over cluster points j of (1 - R1 / r_ij), updated incrementally.
21
+ // The sites are a jittered Fibonacci set on the unit sphere with a k-nearest
22
+ // neighbour graph, so the figure crawls over the host's surface.
23
+ // ---------------------------------------------------------------------------
24
+ function mulberry32(seed) {
25
+ return () => {
26
+ seed |= 0;
27
+ seed = (seed + 0x6d2b79f5) | 0;
28
+ let r = Math.imul(seed ^ (seed >>> 15), 1 | seed);
29
+ r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;
30
+ return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
31
+ };
32
+ }
33
+ const LB_K = 7; // neighbours per site
34
+ const LB_FILL = 0.5; // most of the sites the discharge may take
35
+ const LB_REACH = -0.72; // stop once a channel gets this far round the sphere (cos of the angle from the root)
36
+ /** Grow the tree once. Returns sites, growth order, parents, subtree sizes and depths. */
37
+ function growLichtenberg(count, eta) {
38
+ const n = Math.round(Math.min(Math.max(count, 800), 8000));
39
+ const rand = mulberry32(1931);
40
+ const pos = new Float32Array(n * 3);
41
+ const spacing = Math.sqrt((4 * Math.PI) / n);
42
+ const golden = Math.PI * (3 - Math.sqrt(5));
43
+ // jittered Fibonacci sphere
44
+ for (let i = 0; i < n; i++) {
45
+ const y = 1 - (2 * (i + 0.5)) / n;
46
+ const r = Math.sqrt(Math.max(0, 1 - y * y));
47
+ const a = i * golden;
48
+ let x = Math.cos(a) * r + (rand() - 0.5) * spacing * 0.55;
49
+ let yy = y + (rand() - 0.5) * spacing * 0.55;
50
+ let z = Math.sin(a) * r + (rand() - 0.5) * spacing * 0.55;
51
+ const l = Math.hypot(x, yy, z);
52
+ x /= l;
53
+ yy /= l;
54
+ z /= l;
55
+ pos[i * 3] = x;
56
+ pos[i * 3 + 1] = yy;
57
+ pos[i * 3 + 2] = z;
58
+ }
59
+ // k-nearest neighbours through a uniform grid
60
+ const cell = spacing * 1.8;
61
+ const gdim = Math.ceil(2.2 / cell);
62
+ const cellOf = (v) => Math.min(gdim - 1, Math.max(0, Math.floor((v + 1.1) / cell)));
63
+ const heads = new Int32Array(gdim * gdim * gdim).fill(-1);
64
+ const next = new Int32Array(n);
65
+ for (let i = 0; i < n; i++) {
66
+ const key = (cellOf(pos[i * 3]) * gdim + cellOf(pos[i * 3 + 1])) * gdim + cellOf(pos[i * 3 + 2]);
67
+ next[i] = heads[key];
68
+ heads[key] = i;
69
+ }
70
+ const nbr = new Int32Array(n * LB_K).fill(-1);
71
+ const bestD = new Float32Array(LB_K);
72
+ const maxD2 = cell * cell;
73
+ for (let i = 0; i < n; i++) {
74
+ const px = pos[i * 3], py = pos[i * 3 + 1], pz = pos[i * 3 + 2];
75
+ const cx = cellOf(px), cy = cellOf(py), cz = cellOf(pz);
76
+ bestD.fill(1e9);
77
+ for (let ax = Math.max(0, cx - 1); ax <= Math.min(gdim - 1, cx + 1); ax++)
78
+ for (let ay = Math.max(0, cy - 1); ay <= Math.min(gdim - 1, cy + 1); ay++)
79
+ for (let az = Math.max(0, cz - 1); az <= Math.min(gdim - 1, cz + 1); az++) {
80
+ for (let j = heads[(ax * gdim + ay) * gdim + az]; j >= 0; j = next[j]) {
81
+ if (j === i)
82
+ continue;
83
+ const dx = pos[j * 3] - px, dy = pos[j * 3 + 1] - py, dz = pos[j * 3 + 2] - pz;
84
+ const d2 = dx * dx + dy * dy + dz * dz;
85
+ if (d2 > maxD2 || d2 >= bestD[LB_K - 1])
86
+ continue;
87
+ // insertion into the sorted k-best list
88
+ let k = LB_K - 1;
89
+ while (k > 0 && bestD[k - 1] > d2) {
90
+ bestD[k] = bestD[k - 1];
91
+ nbr[i * LB_K + k] = nbr[i * LB_K + k - 1];
92
+ k--;
93
+ }
94
+ bestD[k] = d2;
95
+ nbr[i * LB_K + k] = j;
96
+ }
97
+ }
98
+ }
99
+ // --- DBM growth ------------------------------------------------------
100
+ const target = Math.floor(n * LB_FILL);
101
+ const R1 = spacing * 0.5;
102
+ const state = new Uint8Array(n); // 0 free, 1 candidate, 2 cluster
103
+ const phi = new Float64Array(n);
104
+ const cands = new Int32Array(n);
105
+ let nc = 0;
106
+ const order = new Int32Array(target + 1); // site index by growth step
107
+ const parent = new Int32Array(n).fill(-1);
108
+ const weights = new Float64Array(n);
109
+ let grown = 0;
110
+ // root: the site closest to a fixed direction (upper front)
111
+ let root = 0, rootDot = -2;
112
+ for (let i = 0; i < n; i++) {
113
+ const d = pos[i * 3] * 0.35 + pos[i * 3 + 1] * 0.8 + pos[i * 3 + 2] * 0.49;
114
+ if (d > rootDot) {
115
+ rootDot = d;
116
+ root = i;
117
+ }
118
+ }
119
+ let lo = 0, hi = 0;
120
+ const addSite = (s) => {
121
+ state[s] = 2;
122
+ order[grown++] = s;
123
+ const sx = pos[s * 3], sy = pos[s * 3 + 1], sz = pos[s * 3 + 2];
124
+ const rmin = R1 * 1.2;
125
+ lo = Infinity;
126
+ hi = -Infinity;
127
+ // remove s from the candidate list; every candidate feels the new charge
128
+ for (let c = 0; c < nc; c++) {
129
+ const j = cands[c];
130
+ if (j === s) {
131
+ cands[c] = cands[--nc];
132
+ c--;
133
+ continue;
134
+ }
135
+ const dx = pos[j * 3] - sx, dy = pos[j * 3 + 1] - sy, dz = pos[j * 3 + 2] - sz;
136
+ const p = phi[j] + 1 - R1 / Math.max(Math.sqrt(dx * dx + dy * dy + dz * dz), rmin);
137
+ phi[j] = p;
138
+ if (p < lo)
139
+ lo = p;
140
+ if (p > hi)
141
+ hi = p;
142
+ }
143
+ // new candidates get the potential of the whole cluster
144
+ for (let k = 0; k < LB_K; k++) {
145
+ const j = nbr[s * LB_K + k];
146
+ if (j < 0 || state[j] !== 0)
147
+ continue;
148
+ state[j] = 1;
149
+ const jx = pos[j * 3], jy = pos[j * 3 + 1], jz = pos[j * 3 + 2];
150
+ let p = 0;
151
+ for (let g = 0; g < grown; g++) {
152
+ const o = order[g];
153
+ const dx = pos[o * 3] - jx, dy = pos[o * 3 + 1] - jy, dz = pos[o * 3 + 2] - jz;
154
+ p += 1 - R1 / Math.max(Math.sqrt(dx * dx + dy * dy + dz * dz), rmin);
155
+ }
156
+ phi[j] = p;
157
+ if (p < lo)
158
+ lo = p;
159
+ if (p > hi)
160
+ hi = p;
161
+ cands[nc++] = j;
162
+ }
163
+ };
164
+ addSite(root);
165
+ const rx = pos[root * 3], ry = pos[root * 3 + 1], rz = pos[root * 3 + 2];
166
+ let reached = false;
167
+ while (grown < target && nc > 0 && !reached) {
168
+ const span = Math.max(hi - lo, 1e-9);
169
+ // P(i) ~ Phi_i^eta by rejection: draw a candidate uniformly, keep it with
170
+ // probability Phi^eta (Phi <= 1). Exact, and O(1/mean(Phi^eta)) instead of O(C).
171
+ let c = -1;
172
+ for (let tries = 0; tries < 6000; tries++) {
173
+ const t = Math.floor(rand() * nc);
174
+ if (rand() < Math.pow((phi[cands[t]] - lo) / span, eta)) {
175
+ c = t;
176
+ break;
177
+ }
178
+ }
179
+ if (c < 0) {
180
+ // fallback: roulette wheel over all candidates
181
+ let sum = 0;
182
+ for (let t = 0; t < nc; t++) {
183
+ sum += Math.pow((phi[cands[t]] - lo) / span, eta) + 1e-9;
184
+ weights[t] = sum;
185
+ }
186
+ const pick = rand() * sum;
187
+ c = 0;
188
+ while (c < nc - 1 && weights[c] < pick)
189
+ c++;
190
+ }
191
+ const s = cands[c];
192
+ // attach to the cluster neighbour that keeps the channel straightest
193
+ let best = -1, bestScore = -Infinity;
194
+ for (let k = 0; k < LB_K; k++) {
195
+ const j = nbr[s * LB_K + k];
196
+ if (j < 0 || state[j] !== 2)
197
+ continue;
198
+ const dx = pos[s * 3] - pos[j * 3], dy = pos[s * 3 + 1] - pos[j * 3 + 1], dz = pos[s * 3 + 2] - pos[j * 3 + 2];
199
+ const l = Math.hypot(dx, dy, dz) + 1e-9;
200
+ let score = -l / spacing;
201
+ const pj = parent[j];
202
+ if (pj >= 0) {
203
+ const ex = pos[j * 3] - pos[pj * 3], ey = pos[j * 3 + 1] - pos[pj * 3 + 1], ez = pos[j * 3 + 2] - pos[pj * 3 + 2];
204
+ const el = Math.hypot(ex, ey, ez) + 1e-9;
205
+ score += 0.8 * (dx * ex + dy * ey + dz * ez) / (l * el);
206
+ }
207
+ if (score > bestScore) {
208
+ bestScore = score;
209
+ best = j;
210
+ }
211
+ }
212
+ parent[s] = best;
213
+ addSite(s);
214
+ if (pos[s * 3] * rx + pos[s * 3 + 1] * ry + pos[s * 3 + 2] * rz < LB_REACH)
215
+ reached = true;
216
+ }
217
+ // subtree sizes (reverse growth order) and depths (forward)
218
+ const sub = new Float32Array(n);
219
+ const depth = new Float32Array(n);
220
+ let maxDepth = 1;
221
+ for (let g = 1; g < grown; g++) {
222
+ const s = order[g];
223
+ depth[s] = depth[parent[s]] + 1;
224
+ if (depth[s] > maxDepth)
225
+ maxDepth = depth[s];
226
+ }
227
+ for (let g = grown - 1; g >= 0; g--) {
228
+ const s = order[g];
229
+ sub[s] += 1;
230
+ if (parent[s] >= 0)
231
+ sub[parent[s]] += sub[s];
232
+ }
233
+ return { n, pos, order, parent, sub, depth, grown, maxDepth, spacing, root };
234
+ }
235
+ /** One ribbon per tree edge: endpoints, growth time, log subtree weight, path length, seed. */
236
+ function buildLichtenbergGeometry(tree) {
237
+ const m = tree.grown - 1;
238
+ const a = new Float32Array(m * 3);
239
+ const b = new Float32Array(m * 3);
240
+ const data = new Float32Array(m * 4);
241
+ const logMax = Math.log(1 + tree.grown);
242
+ for (let g = 1; g <= m; g++) {
243
+ const s = tree.order[g];
244
+ const p = tree.parent[s];
245
+ const i = g - 1;
246
+ a[i * 3] = tree.pos[p * 3];
247
+ a[i * 3 + 1] = tree.pos[p * 3 + 1];
248
+ a[i * 3 + 2] = tree.pos[p * 3 + 2];
249
+ b[i * 3] = tree.pos[s * 3];
250
+ b[i * 3 + 1] = tree.pos[s * 3 + 1];
251
+ b[i * 3 + 2] = tree.pos[s * 3 + 2];
252
+ data[i * 4] = g / tree.grown;
253
+ data[i * 4 + 1] = Math.log(1 + tree.sub[s]) / logMax;
254
+ data[i * 4 + 2] = tree.depth[s] * tree.spacing;
255
+ // the parent's weight, so the ribbon starts exactly as wide as the channel it leaves
256
+ data[i * 4 + 3] = Math.log(1 + tree.sub[p]) / logMax;
257
+ }
258
+ const geo = new THREE.InstancedBufferGeometry();
259
+ // quad: x = 0..1 along the edge, y = -1..1 across
260
+ geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array([0, -1, 0, 1, -1, 0, 1, 1, 0, 0, 1, 0]), 3));
261
+ geo.setIndex([0, 1, 2, 0, 2, 3]);
262
+ geo.setAttribute('aA', new THREE.InstancedBufferAttribute(a, 3));
263
+ geo.setAttribute('aB', new THREE.InstancedBufferAttribute(b, 3));
264
+ geo.setAttribute('aData', new THREE.InstancedBufferAttribute(data, 4));
265
+ geo.instanceCount = m;
266
+ return geo;
267
+ }
268
+ const ribbonVert = /* glsl */ `
269
+ attribute vec3 aA;
270
+ attribute vec3 aB;
271
+ attribute vec4 aData; // growth time 0..1, log subtree weight 0..1, path length, parent weight
272
+ uniform float uBound;
273
+ uniform float uGrowth; // reveal front, 0..1 in growth order
274
+ uniform float uSteps; // number of grown sites
275
+ uniform float uLineWidth;
276
+ varying vec2 vQuad; // x along 0..1, y across -1..1
277
+ varying float vWeight;
278
+ varying float vAge;
279
+ varying float vPath;
280
+ varying float vFacing;
281
+ varying float vCoreFrac;
282
+ varying float vSeed;
283
+ varying float vAlong;
284
+ varying float vLenCore;
285
+
286
+ void main(){
287
+ float birth = aData.x;
288
+ // each edge takes a few growth steps to extend from its parent
289
+ float prog = clamp((uGrowth - birth) * uSteps / 3.0, 0.0, 1.0);
290
+ vAge = (uGrowth - birth) * uSteps;
291
+ // taper from the parent's thickness to this edge's own, so joins line up
292
+ vWeight = mix(min(aData.w, aData.y + 0.18), aData.y, position.x);
293
+ vPath = aData.z;
294
+ vSeed = fract(birth * 91.7 + aData.z * 13.3);
295
+ vec3 A = aA * uBound * 1.01;
296
+ vec3 B = mix(aA, aB, prog) * uBound * 1.01;
297
+ vec4 a = modelViewMatrix * vec4(A, 1.0);
298
+ vec4 b = modelViewMatrix * vec4(B, 1.0);
299
+ vec3 dir = b.xyz - a.xyz;
300
+ vec4 p = mix(a, b, position.x);
301
+ // camera-facing ribbon, width constant in angle (a fraction of the screen height).
302
+ // The quad is as wide as the glow; the hot core is a thinner line inside it.
303
+ vec3 side = normalize(cross(dir + vec3(1e-7), p.xyz));
304
+ float glowW = uLineWidth * (0.0055 + 0.026 * vWeight * vWeight);
305
+ float coreW = uLineWidth * (0.0011 + 0.0024 * vWeight);
306
+ vCoreFrac = coreW / glowW;
307
+ float halfW = glowW * max(-p.z, 0.01);
308
+ p.xyz += side * position.y * halfW * (prog > 0.0 ? 1.0 : 0.0);
309
+ // position along the edge, measured in core half-widths: used to cross-fade the
310
+ // ends so two ribbons meeting at a node add up to one continuous channel
311
+ float lenCore = length(dir) / max(coreW * max(-p.z, 0.01), 1e-6);
312
+ vAlong = position.x * lenCore;
313
+ vLenCore = lenCore;
314
+ // how much the edge faces the viewer: standalone, the far side of the ball is dimmer
315
+ vec3 nrm = normalize(mat3(modelViewMatrix) * normalize(aA + aB));
316
+ vFacing = dot(nrm, normalize(-p.xyz));
317
+ vQuad = vec2(position.x, position.y);
318
+ gl_Position = projectionMatrix * p;
319
+ }
320
+ `;
321
+ const ribbonFrag = /* glsl */ `
322
+ uniform vec3 uColor;
323
+ uniform vec3 uCoreColor;
324
+ uniform float uIntensity;
325
+ uniform float uOpacity;
326
+ uniform float uFade;
327
+ uniform float uTime;
328
+ uniform float uPulse;
329
+ uniform float uHasHost;
330
+ varying vec2 vQuad;
331
+ varying float vWeight;
332
+ varying float vAge;
333
+ varying float vPath;
334
+ varying float vFacing;
335
+ varying float vCoreFrac;
336
+ varying float vSeed;
337
+ varying float vAlong;
338
+ varying float vLenCore;
339
+
340
+ void main(){
341
+ if (vAge <= 0.0) discard;
342
+ float d = abs(vQuad.y);
343
+ float aa = fwidth(vQuad.y);
344
+ // hot core: a thin gaussian line inside the ribbon (soft, so square ribbon
345
+ // corners never show at a bend), never thinner than one pixel
346
+ float cw = max(vCoreFrac, aa * 0.8);
347
+ float core = exp(-(d * d) / (cw * cw) * 1.3);
348
+ // half strength at each end, so a pair of ribbons sums to one at their node
349
+ float endFade = 0.5 + 0.5 * clamp(min(vAlong, vLenCore - vAlong) / 1.3, 0.0, 1.0);
350
+ // glow: a tight inner halo plus a wide soft one, fading to zero at the ribbon edge
351
+ float glow = (exp(-d / max(vCoreFrac, 0.05) * 0.9) * 0.8 + exp(-d * d * 3.5) * 0.55) * (1.0 - smoothstep(0.5, 1.0, d));
352
+ // the freshly broken-down tip flashes, then settles
353
+ float flash = 1.0 + 2.0 * exp(-vAge / 14.0);
354
+ // slow pulses running outward along the channels (path length from the root)
355
+ float g = fract(uTime * 0.16 - vPath * 0.55);
356
+ float wave = exp(-g * 7.0) * smoothstep(0.0, 0.04, g);
357
+ float pulse = 1.0 + uPulse * 1.8 * wave * (0.25 + vWeight);
358
+ // gentle per-channel breathing, very slow
359
+ float breath = 0.85 + 0.15 * sin(uTime * 0.4 + vSeed * 6.2831);
360
+ float bright = flash * pulse * breath * endFade;
361
+ float facing = mix(mix(0.25, 1.0, smoothstep(-0.6, 0.4, vFacing)), mix(0.5, 1.0, smoothstep(0.0, 0.35, vFacing)), uHasHost);
362
+ vec3 col = uColor * glow * (0.2 + 1.5 * vWeight) * bright
363
+ + uCoreColor * core * (0.35 + 1.2 * vWeight * vWeight) * bright;
364
+ col *= facing * uFade * uIntensity * uOpacity;
365
+ gl_FragColor = vec4(col, 1.0);
366
+ }
367
+ `;
368
+ // a soft flare where the discharge enters the surface
369
+ const rootVert = /* glsl */ `
370
+ uniform float uBound;
371
+ uniform vec3 uRoot;
372
+ varying vec2 vUv;
373
+ varying float vFacing;
374
+ void main(){
375
+ vUv = position.xy;
376
+ vec4 mv = modelViewMatrix * vec4(uRoot * uBound * 1.012, 1.0);
377
+ vec3 nrm = normalize(mat3(modelViewMatrix) * uRoot);
378
+ vFacing = dot(nrm, normalize(-mv.xyz));
379
+ mv.xy += position.xy * uBound * 0.32;
380
+ gl_Position = projectionMatrix * mv;
381
+ }
382
+ `;
383
+ const rootFrag = /* glsl */ `
384
+ uniform vec3 uColor;
385
+ uniform vec3 uCoreColor;
386
+ uniform float uIntensity;
387
+ uniform float uOpacity;
388
+ uniform float uFade;
389
+ uniform float uGrowth;
390
+ uniform float uHasHost;
391
+ varying vec2 vUv;
392
+ varying float vFacing;
393
+ void main(){
394
+ float d = length(vUv);
395
+ if (d > 1.0) discard;
396
+ float g = exp(-d * d * 9.0) * 0.55 + exp(-d * 45.0) * 1.4;
397
+ g *= 1.0 - smoothstep(0.7, 1.0, d);
398
+ float vis = mix(mix(0.3, 1.0, smoothstep(-0.5, 0.3, vFacing)), smoothstep(-0.1, 0.25, vFacing), uHasHost);
399
+ float live = uFade * smoothstep(0.0, 0.02, uGrowth);
400
+ vec3 col = mix(uColor, uCoreColor, exp(-d * 10.0)) * g * vis * live * uIntensity * uOpacity;
401
+ gl_FragColor = vec4(col, 1.0);
402
+ }
403
+ `;
404
+ function createUniforms() {
405
+ return {
406
+ uTime: { value: 0 },
407
+ uSpeed: { value: 1 },
408
+ uIntensity: { value: 1 },
409
+ uOpacity: { value: 1 },
410
+ uColor: { value: new THREE.Color() },
411
+ uCoreColor: { value: new THREE.Color() },
412
+ uGrowthTime: { value: 9 },
413
+ uLineWidth: { value: 1 },
414
+ uPulse: { value: 0.6 },
415
+ uBound: { value: 1 },
416
+ uHasHost: { value: 1 },
417
+ uGrowth: { value: 0 },
418
+ uFade: { value: 1 },
419
+ uSteps: { value: 1 },
420
+ uRoot: { value: new THREE.Vector3(0, 1, 0) },
421
+ };
422
+ }
423
+ function applyConfig(u, c, bound, env) {
424
+ u.uSpeed.value = c.speed;
425
+ u.uIntensity.value = c.intensity;
426
+ u.uOpacity.value = c.opacity;
427
+ u.uColor.value.set(c.color);
428
+ u.uCoreColor.value.set(c.coreColor);
429
+ u.uGrowthTime.value = c.growthTime;
430
+ u.uLineWidth.value = c.lineWidth;
431
+ u.uPulse.value = c.pulse;
432
+ u.uBound.value = bound;
433
+ u.uHasHost.value = env && env.hasHost ? 1 : 0;
434
+ }
435
+ function build(u, c, bound, env) {
436
+ const tree = growLichtenberg(c.nodeCount, c.branching);
437
+ const geo = buildLichtenbergGeometry(tree);
438
+ u.uSteps.value = tree.grown;
439
+ u.uRoot.value.set(tree.pos[tree.root * 3], tree.pos[tree.root * 3 + 1], tree.pos[tree.root * 3 + 2]);
440
+ const add = { uniforms: u, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending };
441
+ const mat = new THREE.ShaderMaterial({ vertexShader: ribbonVert, fragmentShader: ribbonFrag, side: THREE.DoubleSide, ...add });
442
+ const rootMat = new THREE.ShaderMaterial({ vertexShader: rootVert, fragmentShader: rootFrag, ...add });
443
+ const mesh = new THREE.Mesh(geo, mat);
444
+ mesh.frustumCulled = false;
445
+ const quad = new THREE.PlaneGeometry(2, 2);
446
+ const flare = new THREE.Mesh(quad, rootMat);
447
+ flare.frustumCulled = false;
448
+ flare.renderOrder = 1;
449
+ const spin = new THREE.Group();
450
+ spin.add(mesh, flare);
451
+ const group = new THREE.Group();
452
+ group.add(spin);
453
+ const axis = new THREE.Vector3();
454
+ let cycle = -1;
455
+ return {
456
+ object: group,
457
+ materials: [mat, rootMat],
458
+ apply(c2) { },
459
+ update(dt, camera) {
460
+ u.uTime.value += dt * u.uSpeed.value;
461
+ // grow (55%), hold (25%), fade (20%), then regrow in a new orientation
462
+ const period = Math.max(u.uGrowthTime.value, 0.5);
463
+ const cyc = u.uTime.value / period;
464
+ const k = Math.floor(cyc);
465
+ const ph = cyc - k;
466
+ const gp = Math.min(ph / 0.55, 1);
467
+ // the leader creeps out, then the breakdown accelerates
468
+ u.uGrowth.value = Math.pow(gp, 1.25);
469
+ u.uFade.value = ph < 0.8 ? 1 : 1 - (ph - 0.8) / 0.2 * ((ph - 0.8) / 0.2) * (3 - 2 * ((ph - 0.8) / 0.2));
470
+ if (k !== cycle) {
471
+ cycle = k;
472
+ // golden-angle turns about a tilted axis: every cycle lands somewhere new
473
+ axis.set(Math.sin(k * 1.3), 1.6, Math.cos(k * 2.1)).normalize();
474
+ spin.quaternion.setFromAxisAngle(axis, k * 2.39996);
475
+ }
476
+ },
477
+ dispose() { geo.dispose(); quad.dispose(); mat.dispose(); rootMat.dispose(); },
478
+ };
479
+ }
480
+ /** Defaults for every knob, so an older saved config still renders. */
481
+ const DEFAULTS = {
482
+ intensity: 1,
483
+ speed: 1,
484
+ color: '#6f9dff',
485
+ opacity: 1,
486
+ nodeCount: 3200,
487
+ branching: 2.2,
488
+ growthTime: 9,
489
+ lineWidth: 1,
490
+ pulse: 0.6,
491
+ coreColor: '#f2f6ff',
492
+ };
493
+ const LichtenbergEffect = ({ boundingRadius, config, animations, hasHost = false, onObjectReady, onObjectDispose }) => {
494
+ const rootRef = useRef(null);
495
+ const animCache = useRef({});
496
+ useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
497
+ // a standalone effect gets boundingRadius 1
498
+ const b = boundingRadius > 0 ? boundingRadius : 1;
499
+ const cfg = useMemo(() => ({ ...DEFAULTS, ...config }), [config]);
500
+ const env = useMemo(() => ({ hasHost: hasHost }), [hasHost]);
501
+ // uniforms outlive the objects, so keyframe animations survive a rebuild
502
+ const uniforms = useMemo(() => createUniforms(), []);
503
+ // rebuilt only when the host size or a structural knob changes
504
+ const instance = useMemo(() => build(uniforms, { ...DEFAULTS, ...config }, b, env), [uniforms, b, env, cfg.nodeCount, cfg.branching]);
505
+ useEffect(() => () => instance.dispose(), [instance]);
506
+ useLayoutEffect(() => {
507
+ applyConfig(uniforms, cfg, b, env);
508
+ instance.apply(cfg);
509
+ }, [instance, uniforms, cfg, b, env]);
510
+ useFrame((state, delta) => {
511
+ instance.update(Math.min(delta, 0.1), state.camera);
512
+ if (animations?.length)
513
+ applyGenerativeAnimations(instance.materials, animations, animCache.current, Date.now(), false);
514
+ });
515
+ return (_jsx("group", { ref: rootRef, children: _jsx("primitive", { object: instance.object }) }));
516
+ };
517
+ export default LichtenbergEffect;
@@ -1,7 +1,7 @@
1
- import type { MoleculesConfig, AnimationOptions } from '@3driseai/3drise-core';
2
- import React from "react";
1
+ import React from 'react';
2
+ import type { AnimationOptions, MoleculesConfig } from '@3driseai/3drise-core';
3
3
  import type { ObjectReadyProps } from '../../types/objectReady';
4
- interface MoleculesEffectProps extends ObjectReadyProps {
4
+ export interface MoleculesEffectProps extends ObjectReadyProps {
5
5
  boundingRadius: number;
6
6
  config: MoleculesConfig;
7
7
  animations?: AnimationOptions[];
@@ -1 +1 @@
1
- {"version":3,"file":"MoleculesEffect.d.ts","sourceRoot":"","sources":["../../../src/components/effects/MoleculesEffect.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE/E,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,UAAU,oBAAqB,SAAQ,gBAAgB;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACnC;AA+CD,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA2HnD,CAAC;AACF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"MoleculesEffect.d.ts","sourceRoot":"","sources":["../../../src/components/effects/MoleculesEffect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAG3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC1D,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACnC;AAgQD,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA8FnD,CAAC;AAEF,eAAe,eAAe,CAAC"}