@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.
- package/README.md +4 -3
- package/dist/components/ChladniGrid.d.ts +15 -0
- package/dist/components/ChladniGrid.d.ts.map +1 -0
- package/dist/components/ChladniGrid.js +62 -0
- package/dist/components/CustomPrimitive.d.ts +2 -0
- package/dist/components/CustomPrimitive.d.ts.map +1 -1
- package/dist/components/CustomPrimitive.js +2 -2
- package/dist/components/EffectsGenerator.d.ts.map +1 -1
- package/dist/components/EffectsGenerator.js +26 -2
- package/dist/components/FlowNetGrid.d.ts +15 -0
- package/dist/components/FlowNetGrid.d.ts.map +1 -0
- package/dist/components/FlowNetGrid.js +61 -0
- package/dist/components/Galaxy.d.ts.map +1 -1
- package/dist/components/Galaxy.js +2 -0
- package/dist/components/GirihGrid.d.ts +15 -0
- package/dist/components/GirihGrid.d.ts.map +1 -0
- package/dist/components/GirihGrid.js +62 -0
- package/dist/components/GravWaveGrid.d.ts +15 -0
- package/dist/components/GravWaveGrid.d.ts.map +1 -0
- package/dist/components/GravWaveGrid.js +62 -0
- package/dist/components/GridsGenerator.d.ts +2 -2
- package/dist/components/GridsGenerator.d.ts.map +1 -1
- package/dist/components/GridsGenerator.js +21 -0
- package/dist/components/HyperbolicGrid.d.ts +15 -0
- package/dist/components/HyperbolicGrid.d.ts.map +1 -0
- package/dist/components/HyperbolicGrid.js +61 -0
- package/dist/components/LayoutInstances.d.ts +13 -0
- package/dist/components/LayoutInstances.d.ts.map +1 -0
- package/dist/components/LayoutInstances.js +39 -0
- package/dist/components/ModelPrimitive.d.ts +2 -0
- package/dist/components/ModelPrimitive.d.ts.map +1 -1
- package/dist/components/ModelPrimitive.js +3 -3
- package/dist/components/ObjectNode.d.ts.map +1 -1
- package/dist/components/ObjectNode.js +44 -9
- package/dist/components/ParticlesGenerator.d.ts +10 -0
- package/dist/components/ParticlesGenerator.d.ts.map +1 -0
- package/dist/components/ParticlesGenerator.js +23 -0
- package/dist/components/ReactionDiffusionGrid.d.ts +15 -0
- package/dist/components/ReactionDiffusionGrid.d.ts.map +1 -0
- package/dist/components/ReactionDiffusionGrid.js +376 -0
- package/dist/components/WallpaperGrid.d.ts +15 -0
- package/dist/components/WallpaperGrid.d.ts.map +1 -0
- package/dist/components/WallpaperGrid.js +555 -0
- package/dist/components/cursor/CursorController.d.ts.map +1 -1
- package/dist/components/effects/BlackHoleEffect.d.ts +3 -3
- package/dist/components/effects/BlackHoleEffect.d.ts.map +1 -1
- package/dist/components/effects/BlackHoleEffect.js +293 -163
- package/dist/components/effects/DandelionSeedsEffect.d.ts +13 -0
- package/dist/components/effects/DandelionSeedsEffect.d.ts.map +1 -0
- package/dist/components/effects/DandelionSeedsEffect.js +510 -0
- package/dist/components/effects/FallingLeavesEffect.d.ts +13 -0
- package/dist/components/effects/FallingLeavesEffect.d.ts.map +1 -0
- package/dist/components/effects/FallingLeavesEffect.js +446 -0
- package/dist/components/effects/FieldLinesEffect.d.ts +11 -0
- package/dist/components/effects/FieldLinesEffect.d.ts.map +1 -0
- package/dist/components/effects/FieldLinesEffect.js +260 -0
- package/dist/components/effects/FireflySwarmEffect.d.ts +13 -0
- package/dist/components/effects/FireflySwarmEffect.d.ts.map +1 -0
- package/dist/components/effects/FireflySwarmEffect.js +408 -0
- package/dist/components/effects/GalaxyEffect.d.ts +13 -0
- package/dist/components/effects/GalaxyEffect.d.ts.map +1 -0
- package/dist/components/effects/GalaxyEffect.js +374 -0
- package/dist/components/effects/HarmonicShellEffect.d.ts +11 -0
- package/dist/components/effects/HarmonicShellEffect.d.ts.map +1 -0
- package/dist/components/effects/HarmonicShellEffect.js +209 -0
- package/dist/components/effects/IceCrystalsEffect.d.ts +3 -3
- package/dist/components/effects/IceCrystalsEffect.d.ts.map +1 -1
- package/dist/components/effects/IceCrystalsEffect.js +286 -166
- package/dist/components/effects/IceHaloEffect.d.ts +13 -0
- package/dist/components/effects/IceHaloEffect.d.ts.map +1 -0
- package/dist/components/effects/IceHaloEffect.js +306 -0
- package/dist/components/effects/LichtenbergEffect.d.ts +13 -0
- package/dist/components/effects/LichtenbergEffect.d.ts.map +1 -0
- package/dist/components/effects/LichtenbergEffect.js +517 -0
- package/dist/components/effects/MoleculesEffect.d.ts +3 -3
- package/dist/components/effects/MoleculesEffect.d.ts.map +1 -1
- package/dist/components/effects/MoleculesEffect.js +322 -164
- package/dist/components/effects/MurmurationEffect.d.ts +13 -0
- package/dist/components/effects/MurmurationEffect.d.ts.map +1 -0
- package/dist/components/effects/MurmurationEffect.js +539 -0
- package/dist/components/effects/PlanetaryRingsEffect.d.ts +13 -0
- package/dist/components/effects/PlanetaryRingsEffect.d.ts.map +1 -0
- package/dist/components/effects/PlanetaryRingsEffect.js +458 -0
- package/dist/components/effects/PortalEffect.d.ts +3 -3
- package/dist/components/effects/PortalEffect.d.ts.map +1 -1
- package/dist/components/effects/PortalEffect.js +240 -163
- package/dist/components/effects/ShockwaveEffect.d.ts +5 -3
- package/dist/components/effects/ShockwaveEffect.d.ts.map +1 -1
- package/dist/components/effects/ShockwaveEffect.js +538 -142
- package/dist/components/effects/SmokePlumeEffect.d.ts +3 -3
- package/dist/components/effects/SmokePlumeEffect.d.ts.map +1 -1
- package/dist/components/effects/SmokePlumeEffect.js +180 -116
- package/dist/components/effects/SmokeRingEffect.d.ts +3 -3
- package/dist/components/effects/SmokeRingEffect.d.ts.map +1 -1
- package/dist/components/effects/SmokeRingEffect.js +260 -135
- package/dist/components/effects/StrangeAttractorEffect.d.ts +11 -0
- package/dist/components/effects/StrangeAttractorEffect.d.ts.map +1 -0
- package/dist/components/effects/StrangeAttractorEffect.js +229 -0
- package/dist/components/effects/VolumetricFogEffect.d.ts +5 -3
- package/dist/components/effects/VolumetricFogEffect.d.ts.map +1 -1
- package/dist/components/effects/VolumetricFogEffect.js +211 -93
- package/dist/components/effects/WaterCausticsEffect.d.ts +13 -0
- package/dist/components/effects/WaterCausticsEffect.d.ts.map +1 -0
- package/dist/components/effects/WaterCausticsEffect.js +308 -0
- package/dist/components/effects/index.d.ts +9 -0
- package/dist/components/effects/index.d.ts.map +1 -1
- package/dist/components/effects/index.js +9 -0
- package/dist/components/index.d.ts +19 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +10 -0
- package/dist/components/particles/BioluminescentWakeParticles.d.ts +12 -0
- package/dist/components/particles/BioluminescentWakeParticles.d.ts.map +1 -0
- package/dist/components/particles/BioluminescentWakeParticles.js +1123 -0
- package/dist/components/particles/BrownianDustParticles.d.ts +12 -0
- package/dist/components/particles/BrownianDustParticles.d.ts.map +1 -0
- package/dist/components/particles/BrownianDustParticles.js +757 -0
- package/dist/components/particles/ElectronOrbitalsParticles.d.ts +12 -0
- package/dist/components/particles/ElectronOrbitalsParticles.d.ts.map +1 -0
- package/dist/components/particles/ElectronOrbitalsParticles.js +944 -0
- package/dist/components/particles/GrinderSparksParticles.d.ts +12 -0
- package/dist/components/particles/GrinderSparksParticles.d.ts.map +1 -0
- package/dist/components/particles/GrinderSparksParticles.js +744 -0
- package/dist/components/particles/TidalStreamParticles.d.ts +12 -0
- package/dist/components/particles/TidalStreamParticles.d.ts.map +1 -0
- package/dist/components/particles/TidalStreamParticles.js +788 -0
- package/dist/components/particles/index.d.ts +11 -0
- package/dist/components/particles/index.d.ts.map +1 -0
- package/dist/components/particles/index.js +6 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/hooks/useModelObject.d.ts +6 -1
- package/dist/hooks/useModelObject.d.ts.map +1 -1
- package/dist/hooks/useModelObject.js +19 -6
- package/dist/types/gridConfigs.d.ts +98 -0
- package/dist/types/gridConfigs.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/particleTrails.d.ts +70 -0
- package/dist/utils/particleTrails.d.ts.map +1 -0
- package/dist/utils/particleTrails.js +369 -0
- package/dist/utils/pointerField.d.ts +28 -0
- package/dist/utils/pointerField.d.ts.map +1 -0
- package/dist/utils/pointerField.js +103 -0
- 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
|
|
2
|
-
import
|
|
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":"
|
|
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"}
|