@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,176 +1,334 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef, useMemo } from 'react';
3
- import { useFrame } from '@react-three/fiber';
2
+ import { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
4
3
  import * as THREE from 'three';
5
- import { getShaderByName } from '@3driseai/3drise-core';
4
+ import { useFrame } from '@react-three/fiber';
6
5
  import { applyGenerativeAnimations } from '../../utils/handleGenerativeEffectsAnimations.js';
7
6
  import { useObjectReady } from '../../hooks/useObjectReady.js';
8
- // Glowing node points at each atom position
9
- // const nodeVertShader = /* glsl */`
10
- // attribute float aPhase;
11
- // attribute float aSize;
12
- // uniform float u_time;
13
- // uniform float uPointSize;
14
- // uniform float uIntensity;
15
- // varying float vAlpha;
16
- // varying vec3 vColor;
17
- // vec3 hsv2rgb(float h, float s, float v){
18
- // vec3 rgb=clamp(abs(mod(h*6.+vec3(0.,4.,2.),6.)-3.)-1.,0.,1.);
19
- // return v*mix(vec3(1.),rgb,s);
20
- // }
21
- // void main() {
22
- // // Each node has independent color + pulse
23
- // float hue = mod(aPhase + u_time * 0.08, 1.0);
24
- // float pulse = 0.5 + 0.5 * sin(u_time * 2.5 + aPhase * 6.28);
25
- // vColor = hsv2rgb(hue, 0.85, 1.0);
26
- // vAlpha = 0.55 + 0.45 * pulse;
27
- // vec4 mv = modelViewMatrix * vec4(position, 1.0);
28
- // gl_PointSize = uPointSize * aSize * (0.6 + 0.4 * pulse) * uIntensity * (250.0 / -mv.z);
29
- // gl_Position = projectionMatrix * mv;
30
- // }
31
- // `;
32
- // const nodeFragShader = /* glsl */`
33
- // varying float vAlpha;
34
- // varying vec3 vColor;
35
- // uniform float uOpacity;
36
- // void main() {
37
- // vec2 coord = gl_PointCoord - vec2(0.5);
38
- // float dist = length(coord);
39
- // if (dist > 0.5) discard;
40
- // // Gaussian core glow
41
- // float glow = exp(-dist * dist * 8.0);
42
- // // Four-point diffraction spike (like a bright star)
43
- // float cx = exp(-abs(coord.x) * 20.0) * exp(-coord.y*coord.y * 36.0);
44
- // float cy = exp(-abs(coord.y) * 20.0) * exp(-coord.x*coord.x * 36.0);
45
- // float spike = (cx + cy) * 0.5;
46
- // // White-hot centre
47
- // float core = exp(-dist * dist * 80.0);
48
- // vec3 col = mix(vColor, vec3(1.0), core * 0.8 + spike * 0.2);
49
- // gl_FragColor = vec4(col, vAlpha * uOpacity * (glow + spike * 0.6 + core));
50
- // }
51
- // `;
52
- const MoleculesEffect = ({ boundingRadius, config, animations, onObjectReady, onObjectDispose }) => {
53
- const { atomCount, orbitRadius, atomSize, showBonds, opacity, speed, intensity } = config;
54
- const startValuesCacheRef = useRef({});
55
- const rootRef = useRef(null);
56
- useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
57
- const shaderData = getShaderByName('Molecules');
58
- const tRef = useRef(new Float32Array(atomCount));
59
- const r = boundingRadius * orbitRadius;
60
- const PHI = 1.6180339887;
61
- // 3D Lissajous frequency ratios per atom
62
- const orbitalParams = useMemo(() => {
63
- const pairs = [
64
- [1, 2, 3], [2, 3, 1], [3, 1, 2], [1, 3, 2], [2, 1, 3], [3, 2, 1],
65
- [1, 1, 2], [2, 2, 1], [1, 2, 2], [2, 1, 1], [3, 3, 2], [2, 3, 3],
66
- ];
67
- return Array.from({ length: atomCount }, (_, i) => {
68
- const [fx, fy, fz] = pairs[i % pairs.length];
69
- return {
70
- fx, fy, fz,
71
- px: (i * PHI * Math.PI) % (Math.PI * 2),
72
- py: (i * PHI * PHI * Math.PI) % (Math.PI * 2),
73
- pz: (i * PHI ** 3 * Math.PI) % (Math.PI * 2),
74
- hueBase: i / Math.max(atomCount - 1, 1),
75
- speedMult: 0.65 + (i % 7) * 0.07,
76
- };
77
- });
78
- }, [atomCount]);
79
- // Resize tRef and spread initial times so atoms don't cluster at cycle start
80
- useMemo(() => {
81
- tRef.current = new Float32Array(atomCount);
82
- for (let i = 0; i < atomCount; i++)
83
- tRef.current[i] = (i / atomCount) * Math.PI * 2;
84
- }, [atomCount]);
85
- // ── Node (point) geometry ──────────────────────────────────────────────────
86
- const { nodeGeo, nodeMat } = useMemo(() => {
87
- const phases = new Float32Array(atomCount);
88
- const sizes = new Float32Array(atomCount);
89
- for (let i = 0; i < atomCount; i++) {
90
- phases[i] = i / atomCount; // 0..1 → hue spread
91
- sizes[i] = 0.8 + Math.random() * 0.6; // slight size variation
7
+ /**
8
+ * A molecule drawn as quantum electron clouds.
9
+ *
10
+ * The central atom shows hydrogen orbitals as rejection samples of |psi|^2,
11
+ * morphing from 1s to 2p, 3d and 4f (or holding one with `orbital`); the
12
+ * sign of psi colours the lobes. Outer atoms sit where the Thomson problem
13
+ * puts them (minimum Coulomb repulsion, which reproduces VSEPR shapes), each
14
+ * with a 1s cloud and a sigma-bond density between the nuclei. The molecule
15
+ * breathes with a symmetric stretch.
16
+ */
17
+ const PRE_ADD = { transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, premultipliedAlpha: true };
18
+ const ORBITAL_PERIOD = 7;
19
+ const ORBITAL_FADE = 1.6;
20
+ function mulberry32(seed) {
21
+ return () => {
22
+ seed |= 0;
23
+ seed = (seed + 0x6d2b79f5) | 0;
24
+ let r = Math.imul(seed ^ (seed >>> 15), 1 | seed);
25
+ r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;
26
+ return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
27
+ };
28
+ }
29
+ function cross(a, b) { return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; }
30
+ function add(a, b) { return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; }
31
+ function scale(a, s) { return [a[0] * s, a[1] * s, a[2] * s]; }
32
+ function norm(a) { const l = Math.hypot(a[0], a[1], a[2]) || 1; return [a[0] / l, a[1] / l, a[2] / l]; }
33
+ /** N points on a unit sphere minimising Coulomb repulsion (VSEPR shapes for N <= 6). */
34
+ function thomson(count, seed) {
35
+ const n = Math.round(Math.min(Math.max(count, 1), 20));
36
+ if (n === 1)
37
+ return [[0, 1, 0]];
38
+ const rand = mulberry32(seed);
39
+ let pts = Array.from({ length: n }, () => norm([rand() - 0.5, rand() - 0.5, rand() - 0.5]));
40
+ for (let it = 0; it < 400; it++) {
41
+ const step = 0.12 * (1 - it / 400) + 0.005;
42
+ const forces = pts.map(() => [0, 0, 0]);
43
+ for (let i = 0; i < n; i++) {
44
+ for (let j = i + 1; j < n; j++) {
45
+ const d = [pts[i][0] - pts[j][0], pts[i][1] - pts[j][1], pts[i][2] - pts[j][2]];
46
+ const l2 = d[0] * d[0] + d[1] * d[1] + d[2] * d[2] + 1e-9;
47
+ const f = 1 / (l2 * Math.sqrt(l2));
48
+ for (let k = 0; k < 3; k++) {
49
+ forces[i][k] += d[k] * f;
50
+ forces[j][k] -= d[k] * f;
51
+ }
52
+ }
92
53
  }
93
- const geo = new THREE.BufferGeometry();
94
- geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(atomCount * 3), 3));
95
- geo.setAttribute('aPhase', new THREE.BufferAttribute(phases, 1));
96
- geo.setAttribute('aSize', new THREE.BufferAttribute(sizes, 1));
97
- const mat = new THREE.ShaderMaterial({
98
- vertexShader: shaderData.vertex,
99
- fragmentShader: shaderData.fragment,
100
- uniforms: {
101
- u_time: { value: 0 },
102
- uPointSize: { value: atomSize * 200 },
103
- uIntensity: { value: intensity },
104
- uOpacity: { value: opacity },
105
- },
106
- transparent: true,
107
- depthWrite: false,
108
- blending: THREE.AdditiveBlending,
109
- });
110
- return { nodeGeo: geo, nodeMat: mat };
111
- }, [atomCount, atomSize, intensity, opacity]);
112
- // ── Bond (line) geometry with per-vertex color ─────────────────────────────
113
- const { bondGeo, bondMat } = useMemo(() => {
114
- const vertCount = atomCount * 2; // each bond = 2 verts
115
- const geo = new THREE.BufferGeometry();
116
- const positions = new Float32Array(vertCount * 3);
117
- const colors = new Float32Array(vertCount * 3);
118
- geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
119
- geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
120
- const mat = new THREE.LineBasicMaterial({
121
- vertexColors: true,
122
- transparent: true,
123
- opacity: opacity * 0.7,
124
- depthWrite: false,
125
- blending: THREE.AdditiveBlending,
54
+ pts = pts.map((p, i) => {
55
+ const fl = Math.hypot(...forces[i]) || 1;
56
+ return norm(add(p, scale(forces[i], step / fl)));
126
57
  });
127
- return { bondGeo: geo, bondMat: mat };
128
- }, [atomCount, opacity]);
129
- const atomPos = useRef([]);
130
- useMemo(() => {
131
- atomPos.current = Array.from({ length: atomCount }, () => new THREE.Vector3());
132
- }, [atomCount]);
133
- const tmpCol = useMemo(() => new THREE.Color(), []);
134
- useFrame((state, delta) => {
135
- const t = state.clock.getElapsedTime();
136
- nodeMat.uniforms.u_time.value = t;
137
- const nodePos = nodeGeo.attributes.position;
138
- for (let i = 0; i < atomCount; i++) {
139
- tRef.current[i] += delta * speed * orbitalParams[i].speedMult;
140
- const a = tRef.current[i];
141
- const p = orbitalParams[i];
142
- // 3D Lissajous position
143
- const x = Math.cos(p.fx * a + p.px) * r;
144
- const y = Math.sin(p.fy * a + p.py) * r * 0.75;
145
- const z = Math.sin(p.fz * a + p.pz) * r;
146
- atomPos.current[i].set(x, y, z);
147
- nodePos.setXYZ(i, x, y, z);
58
+ }
59
+ return pts;
60
+ }
61
+ /** Unnormalised hydrogen-like orbitals (real forms), written with z as the axis. */
62
+ const ORBITALS = [
63
+ { name: '1s', extent: 5, psi: (x, y, z, r) => Math.exp(-r) },
64
+ { name: '2p', extent: 12, psi: (x, y, z, r) => z * Math.exp(-r / 2) },
65
+ { name: '3d z²', extent: 22, psi: (x, y, z, r) => (3 * z * z - r * r) * Math.exp(-r / 3) },
66
+ { name: '3d xy', extent: 22, psi: (x, y, z, r) => x * y * Math.exp(-r / 3) },
67
+ { name: '4f z³', extent: 34, psi: (x, y, z, r) => z * (5 * z * z - 3 * r * r) * Math.exp(-r / 4) },
68
+ { name: '4f xyz', extent: 34, psi: (x, y, z, r) => x * y * z * Math.exp(-r / 4) },
69
+ ];
70
+ /** Rejection-samples |psi|^2; positions are scaled so the 92nd-percentile radius is 1. */
71
+ function sampleOrbital(index, count, seed) {
72
+ const o = ORBITALS[index];
73
+ const rand = mulberry32(seed);
74
+ const L = o.extent;
75
+ // estimate the peak density
76
+ let peak = 0;
77
+ for (let i = 0; i < 20000; i++) {
78
+ const x = (rand() * 2 - 1) * L, y = (rand() * 2 - 1) * L, z = (rand() * 2 - 1) * L;
79
+ const v = o.psi(x, y, z, Math.hypot(x, y, z));
80
+ peak = Math.max(peak, v * v);
81
+ }
82
+ peak *= 1.05;
83
+ const pts = [];
84
+ const signs = [];
85
+ let guard = 0;
86
+ while (pts.length < count && guard < count * 4000) {
87
+ guard++;
88
+ const x = (rand() * 2 - 1) * L, y = (rand() * 2 - 1) * L, z = (rand() * 2 - 1) * L;
89
+ const v = o.psi(x, y, z, Math.hypot(x, y, z));
90
+ if (rand() * peak < v * v) {
91
+ pts.push([x, y, z]);
92
+ signs.push(v >= 0 ? 1 : -1);
148
93
  }
149
- nodePos.needsUpdate = true;
150
- // Update bond geometry + animated per-vertex color
151
- if (showBonds) {
152
- const bondPos = bondGeo.attributes.position;
153
- const bondCol = bondGeo.attributes.color;
154
- for (let i = 0; i < atomCount; i++) {
155
- const curr = atomPos.current[i];
156
- const next = atomPos.current[(i + 1) % atomCount];
157
- bondPos.setXYZ(i * 2, curr.x, curr.y, curr.z);
158
- bondPos.setXYZ(i * 2 + 1, next.x, next.y, next.z);
159
- // Color: each vertex gets a hue based on its atom's phase + time
160
- const hue0 = (orbitalParams[i].hueBase + t * 0.07) % 1;
161
- const hue1 = (orbitalParams[(i + 1) % atomCount].hueBase + t * 0.07) % 1;
162
- tmpCol.setHSL(hue0, 1.0, 0.6);
163
- bondCol.setXYZ(i * 2, tmpCol.r, tmpCol.g, tmpCol.b);
164
- tmpCol.setHSL(hue1, 1.0, 0.6);
165
- bondCol.setXYZ(i * 2 + 1, tmpCol.r, tmpCol.g, tmpCol.b);
166
- }
167
- bondPos.needsUpdate = true;
168
- bondCol.needsUpdate = true;
94
+ }
95
+ const radii = pts.map((p) => Math.hypot(...p)).sort((a, b) => a - b);
96
+ const s = 1 / (radii[Math.floor(radii.length * 0.92)] || 1);
97
+ const pos = new Float32Array(pts.length * 3);
98
+ // orbitals are written with z as the axis; stand it up as world Y
99
+ pts.forEach((p, i) => { pos[i * 3] = p[0] * s; pos[i * 3 + 1] = p[2] * s; pos[i * 3 + 2] = p[1] * s; });
100
+ return { pos, signs };
101
+ }
102
+ // Orbital sampling is the slow part (~200 ms for all six), so keep it across remounts.
103
+ const orbitalCache = new Map();
104
+ function cachedOrbital(index, count) {
105
+ const key = index + ':' + count;
106
+ let hit = orbitalCache.get(key);
107
+ if (!hit) {
108
+ if (orbitalCache.size >= 24)
109
+ orbitalCache.clear();
110
+ hit = sampleOrbital(index, count, 100 + index);
111
+ orbitalCache.set(key, hit);
112
+ }
113
+ return hit;
114
+ }
115
+ function gauss(rand) {
116
+ return Math.sqrt(-2 * Math.log(1 - rand() * 0.999)) * Math.cos(Math.PI * 2 * rand());
117
+ }
118
+ /**
119
+ * One point cloud holding every orbital set (kinds 0-5), the ligand 1s clouds (kind 6)
120
+ * and the bond clouds (kind 7).
121
+ * position = orbital sample, or unit ligand direction
122
+ * normal = offset in atom-size units (ligands and bonds)
123
+ * aInfo = kind, sign, phase
124
+ */
125
+ function buildMolecule(atomCount, density, axisSeed) {
126
+ const dens = Math.min(Math.max(density, 0.25), 2);
127
+ const perOrbital = Math.round(4000 * dens);
128
+ const perLigand = Math.round(260 * dens);
129
+ const perBond = Math.round(180 * dens);
130
+ const dirs = atomCount > 0 ? thomson(atomCount, 3 + axisSeed) : [];
131
+ const rand = mulberry32(41);
132
+ const pos = [], nrm = [], info = [];
133
+ ORBITALS.forEach((_, k) => {
134
+ const { pos: p, signs } = cachedOrbital(k, perOrbital);
135
+ for (let i = 0; i < signs.length; i++) {
136
+ pos.push(p[i * 3], p[i * 3 + 1], p[i * 3 + 2]);
137
+ nrm.push(0, 0, 0);
138
+ info.push(k, signs[i], rand());
139
+ }
140
+ });
141
+ for (const d of dirs) {
142
+ for (let i = 0; i < perLigand; i++) {
143
+ // 1s density: radial distance follows r^2 e^-2r
144
+ const r = -Math.log(rand() * rand() * rand() + 1e-6) / 2 / 3;
145
+ const u = norm([gauss(rand), gauss(rand), gauss(rand)]);
146
+ pos.push(d[0], d[1], d[2]);
147
+ nrm.push(u[0] * r, u[1] * r, u[2] * r);
148
+ info.push(6, 1, rand());
149
+ }
150
+ for (let i = 0; i < perBond; i++) {
151
+ // sigma bond: density peaks between the nuclei, gaussian across the axis
152
+ const t = Math.min(Math.max(0.5 + gauss(rand) * 0.17, 0.12), 0.88);
153
+ pos.push(d[0] * t, d[1] * t, d[2] * t);
154
+ nrm.push(gauss(rand) * 0.35, gauss(rand) * 0.35, gauss(rand) * 0.35);
155
+ info.push(7, 1, rand());
156
+ }
157
+ }
158
+ return {
159
+ position: new Float32Array(pos),
160
+ normal: new Float32Array(nrm),
161
+ aInfo: new Float32Array(info),
162
+ ligands: dirs,
163
+ };
164
+ }
165
+ const cloudVert = /* glsl */ `
166
+ attribute vec3 aInfo; // x: kind (0-5 orbital set, 6 ligand, 7 bond), y: sign, z: phase
167
+ uniform float uTime;
168
+ uniform float uSetA;
169
+ uniform float uSetB;
170
+ uniform float uMix; // 0 shows set A, 1 shows set B
171
+ uniform float uOrbitRadius;
172
+ uniform float uAtomSize;
173
+ uniform float uBound;
174
+ uniform float uStretch;
175
+ uniform float uShowBonds;
176
+ uniform float uIntensity;
177
+ varying float vSign;
178
+ varying float vAlpha;
179
+ varying float vKind;
180
+ void main(){
181
+ float kind = aInfo.x;
182
+ float w = 0.0;
183
+ vec3 p = position;
184
+ float bondLen = uOrbitRadius * uBound;
185
+ float atomR = uAtomSize * uBound * 6.0;
186
+ // symmetric stretch: ligands and bond clouds breathe together
187
+ float stretch = 1.0 + uStretch * 0.035 * sin(uTime * 2.2);
188
+ if (kind < 5.5){
189
+ w = (abs(kind - uSetA) < 0.5 ? 1.0 - uMix : 0.0) + (abs(kind - uSetB) < 0.5 ? uMix : 0.0);
190
+ p *= bondLen * 0.62;
191
+ } else if (kind < 6.5){
192
+ w = 1.0;
193
+ p = p * stretch * bondLen + normal * atomR;
194
+ } else {
195
+ w = uShowBonds;
196
+ p = p * stretch * bondLen + normal * atomR * 0.5;
197
+ }
198
+ if (w < 0.002){
199
+ gl_PointSize = 0.0;
200
+ gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
201
+ vAlpha = 0.0;
202
+ return;
203
+ }
204
+ vSign = aInfo.y;
205
+ vKind = kind;
206
+ // the cloud is alive: each sample fades slowly in and out
207
+ vAlpha = w * (0.55 + 0.45 * sin(uTime * 0.9 + aInfo.z * 6.2832));
208
+ vec4 mv = modelViewMatrix * vec4(p, 1.0);
209
+ gl_PointSize = (kind < 5.5 ? 1.0 : 0.8) * uIntensity * (3.2 * bondLen / max(-mv.z, 1e-3)) * 6.0;
210
+ gl_Position = projectionMatrix * mv;
211
+ }
212
+ `;
213
+ const cloudFrag = /* glsl */ `
214
+ uniform vec3 uColor;
215
+ uniform vec3 uPhaseColor;
216
+ uniform vec3 uBondColor;
217
+ uniform float uOpacity;
218
+ varying float vSign;
219
+ varying float vAlpha;
220
+ varying float vKind;
221
+ void main(){
222
+ float d = length(gl_PointCoord - 0.5);
223
+ if (d > 0.5 || vAlpha < 0.002) discard;
224
+ float g = smoothstep(0.5, 0.0, d);
225
+ // the wavefunction's sign colours the lobes
226
+ vec3 col = vKind > 6.5 ? uBondColor : (vSign > 0.0 ? uColor : uPhaseColor);
227
+ float a = g * g * vAlpha * uOpacity * 0.9;
228
+ gl_FragColor = vec4(col * a, a);
229
+ }
230
+ `;
231
+ const spriteVert = /* glsl */ `
232
+ varying vec2 vUv;
233
+ uniform float uOrbitRadius;
234
+ uniform float uBound;
235
+ void main(){
236
+ vUv = uv;
237
+ // camera-facing quad
238
+ vec4 mv = modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
239
+ mv.xy += position.xy * uOrbitRadius * uBound * 0.35;
240
+ gl_Position = projectionMatrix * mv;
241
+ }
242
+ `;
243
+ const nucleusFrag = /* glsl */ `
244
+ uniform vec3 uColor;
245
+ uniform float uOpacity;
246
+ uniform float uIntensity;
247
+ varying vec2 vUv;
248
+ void main(){
249
+ float d = length(vUv - 0.5) * 2.0;
250
+ float g = exp(-d * d * 18.0) + exp(-d * 5.0) * 0.15;
251
+ vec3 col = mix(uColor, vec3(1.0), exp(-d * d * 60.0));
252
+ gl_FragColor = vec4(col * g * uIntensity * uOpacity, g * uOpacity);
253
+ }
254
+ `;
255
+ const MoleculesEffect = ({ boundingRadius, config, animations, onObjectReady, onObjectDispose }) => {
256
+ const { intensity = 1, speed = 1, color = '#00d4ff', opacity = 0.9, atomCount = 6, orbitRadius = 1.5, atomSize = 0.08, showBonds = true, bondColor = '#ffffff', randomizeAxes = true, orbital = -1, phaseColor = '#ff5a8a', cloudDensity = 1, } = config;
257
+ const rootRef = useRef(null);
258
+ const spinRef = useRef(null);
259
+ const animCache = useRef({});
260
+ useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
261
+ // a standalone effect gets boundingRadius 1; guard against an empty host
262
+ const b = boundingRadius > 0 ? boundingRadius : 1;
263
+ const uniforms = useMemo(() => ({
264
+ uTime: { value: 0 },
265
+ uSpeed: { value: 1 },
266
+ uBound: { value: 1 },
267
+ uIntensity: { value: 1 },
268
+ uOpacity: { value: 1 },
269
+ uColor: { value: new THREE.Color() },
270
+ uPhaseColor: { value: new THREE.Color() },
271
+ uBondColor: { value: new THREE.Color() },
272
+ uOrbitRadius: { value: 1.5 },
273
+ uAtomSize: { value: 0.08 },
274
+ uShowBonds: { value: 1 },
275
+ uStretch: { value: 1 },
276
+ uOrbital: { value: -1 },
277
+ uSetA: { value: 0 },
278
+ uSetB: { value: 1 },
279
+ uMix: { value: 0 },
280
+ }), []);
281
+ const materials = useMemo(() => [
282
+ new THREE.ShaderMaterial({ vertexShader: cloudVert, fragmentShader: cloudFrag, uniforms, ...PRE_ADD }),
283
+ new THREE.ShaderMaterial({ vertexShader: spriteVert, fragmentShader: nucleusFrag, uniforms, ...PRE_ADD }),
284
+ ], [uniforms]);
285
+ useEffect(() => () => materials.forEach((m) => m.dispose()), [materials]);
286
+ const cloudGeometry = useMemo(() => {
287
+ const m = buildMolecule(Math.round(Math.min(Math.max(atomCount, 0), 20)), cloudDensity, 0);
288
+ const g = new THREE.BufferGeometry();
289
+ g.setAttribute('position', new THREE.BufferAttribute(m.position, 3));
290
+ g.setAttribute('normal', new THREE.BufferAttribute(m.normal, 3));
291
+ g.setAttribute('aInfo', new THREE.BufferAttribute(m.aInfo, 3));
292
+ return g;
293
+ }, [atomCount, cloudDensity]);
294
+ useEffect(() => () => cloudGeometry.dispose(), [cloudGeometry]);
295
+ const quadGeometry = useMemo(() => new THREE.PlaneGeometry(1, 1), []);
296
+ useEffect(() => () => quadGeometry.dispose(), [quadGeometry]);
297
+ useLayoutEffect(() => {
298
+ uniforms.uSpeed.value = speed;
299
+ uniforms.uBound.value = b;
300
+ uniforms.uIntensity.value = intensity;
301
+ uniforms.uOpacity.value = opacity;
302
+ uniforms.uColor.value.set(color);
303
+ uniforms.uPhaseColor.value.set(phaseColor);
304
+ uniforms.uBondColor.value.set(bondColor);
305
+ uniforms.uOrbitRadius.value = orbitRadius;
306
+ uniforms.uAtomSize.value = atomSize;
307
+ uniforms.uShowBonds.value = showBonds ? 1 : 0;
308
+ uniforms.uOrbital.value = orbital;
309
+ }, [uniforms, speed, b, intensity, opacity, color, phaseColor, bondColor, orbitRadius, atomSize, showBonds, orbital]);
310
+ useFrame((_, delta) => {
311
+ if (animations?.length)
312
+ applyGenerativeAnimations(materials, animations, animCache.current, Date.now(), false);
313
+ const t = (uniforms.uTime.value += delta * uniforms.uSpeed.value);
314
+ if (spinRef.current)
315
+ spinRef.current.rotation.y = t * 0.15;
316
+ const fixed = Math.round(uniforms.uOrbital.value);
317
+ if (fixed >= 0) {
318
+ // hold one orbital
319
+ uniforms.uSetA.value = Math.min(fixed, ORBITALS.length - 1);
320
+ uniforms.uSetB.value = uniforms.uSetA.value;
321
+ uniforms.uMix.value = 0;
169
322
  }
170
- if (animations?.length) {
171
- applyGenerativeAnimations([nodeMat, bondMat], animations, startValuesCacheRef.current, Date.now(), false);
323
+ else {
324
+ // cycle 1s -> 2p -> 3d -> 4f with a crossfade at the end of each period
325
+ const k = Math.floor(t / ORBITAL_PERIOD);
326
+ const local = t - k * ORBITAL_PERIOD;
327
+ uniforms.uSetA.value = k % ORBITALS.length;
328
+ uniforms.uSetB.value = (k + 1) % ORBITALS.length;
329
+ uniforms.uMix.value = THREE.MathUtils.smoothstep(local, ORBITAL_PERIOD - ORBITAL_FADE, ORBITAL_PERIOD);
172
330
  }
173
331
  });
174
- return (_jsxs("group", { ref: rootRef, name: "MoleculesEffect", children: [_jsx("points", { geometry: nodeGeo, material: nodeMat }), showBonds && (_jsx("lineSegments", { geometry: bondGeo, material: bondMat }))] }));
332
+ return (_jsx("group", { ref: rootRef, children: _jsx("group", { rotation: randomizeAxes ? [0.45, 0, 0.3] : [0, 0, 0], children: _jsxs("group", { ref: spinRef, children: [_jsx("points", { geometry: cloudGeometry, material: materials[0], frustumCulled: false }), _jsx("mesh", { geometry: quadGeometry, material: materials[1], frustumCulled: false })] }) }) }));
175
333
  };
176
334
  export default MoleculesEffect;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { AnimationOptions, MurmurationConfig } from '@3driseai/3drise-core';
3
+ import type { ObjectReadyProps } from '../../types/objectReady';
4
+ export interface MurmurationEffectProps extends ObjectReadyProps {
5
+ boundingRadius: number;
6
+ config: MurmurationConfig;
7
+ animations?: AnimationOptions[];
8
+ /** True when the effect is attached to an object, so it can react to the host. */
9
+ hasHost?: boolean;
10
+ }
11
+ declare const MurmurationEffect: React.FC<MurmurationEffectProps>;
12
+ export default MurmurationEffect;
13
+ //# sourceMappingURL=MurmurationEffect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MurmurationEffect.d.ts","sourceRoot":"","sources":["../../../src/components/effects/MurmurationEffect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAG3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC5D,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAChC,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAodD,QAAA,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CA+BvD,CAAC;AAEF,eAAe,iBAAiB,CAAC"}