@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,192 +1,312 @@
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';
4
+ import { useFrame } from '@react-three/fiber';
5
5
  import { applyGenerativeAnimations } from '../../utils/handleGenerativeEffectsAnimations.js';
6
6
  import { useObjectReady } from '../../hooks/useObjectReady.js';
7
- // ── Crystal spike shader ──────────────────────────────────────────────────────
8
- const spikeVert = /* glsl */ `
9
- varying vec3 vNormal;
10
- varying vec3 vWorldPos;
11
- void main() {
12
- vNormal = normalize(normalMatrix * normal);
13
- vWorldPos = (modelMatrix * vec4(position, 1.0)).xyz;
14
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
7
+ /**
8
+ * Six-fold snow crystals growing as frost on the host.
9
+ *
10
+ * Each crystal is a flat plate, leaned off the surface like hoar frost. The
11
+ * fragment shader folds the plate into one 30-degree wedge (D6 symmetry) and
12
+ * draws a hexagonal plate or a dendrite with 60-degree side branches, blended
13
+ * by `habit`. Crystals grow like diffusion (R ~ sqrt(t)), hold, then
14
+ * sublimate, each on its own clock. Facets glint as they turn, and a cloud of
15
+ * diamond dust drifts around the host.
16
+ */
17
+ const ADD = { transparent: true, depthWrite: false, blending: THREE.AdditiveBlending };
18
+ const DUST_COUNT = 220;
19
+ function mulberry32(seed) {
20
+ return () => {
21
+ seed |= 0;
22
+ seed = (seed + 0x6d2b79f5) | 0;
23
+ let r = Math.imul(seed ^ (seed >>> 15), 1 | seed);
24
+ r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;
25
+ return ((r ^ (r >>> 14)) >>> 0) / 4294967296;
26
+ };
27
+ }
28
+ 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]]; }
29
+ function add(a, b) { return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; }
30
+ function scale(a, s) { return [a[0] * s, a[1] * s, a[2] * s]; }
31
+ function norm(a) { const l = Math.hypot(a[0], a[1], a[2]) || 1; return [a[0] / l, a[1] / l, a[2] / l]; }
32
+ /** Crystal transforms and seeds for n plates on a sphere of radius `bound`. */
33
+ function layoutIceCrystals(count, bound, size) {
34
+ const n = Math.round(Math.min(Math.max(count, 10), 60));
35
+ const rand = mulberry32(5);
36
+ const matrices = new Float32Array(n * 16);
37
+ const seeds = new Float32Array(n * 3);
38
+ const golden = Math.PI * (3 - Math.sqrt(5));
39
+ for (let i = 0; i < n; i++) {
40
+ const y = 1 - ((i + 0.5) / n) * 2;
41
+ const rad = Math.sqrt(1 - y * y);
42
+ const ph = i * golden;
43
+ const nrm = [Math.cos(ph) * rad, y, Math.sin(ph) * rad];
44
+ // a random tangent, then lean the plate 35-65 degrees off the surface like hoar frost
45
+ const up = Math.abs(nrm[1]) > 0.9 ? [1, 0, 0] : [0, 1, 0];
46
+ let t = cross(up, nrm);
47
+ t = norm(t);
48
+ const b = cross(nrm, t);
49
+ const spin = rand() * Math.PI * 2;
50
+ const tan = norm(add(scale(t, Math.cos(spin)), scale(b, Math.sin(spin))));
51
+ const lean = (35 + rand() * 30) * Math.PI / 180;
52
+ // plate normal: tilt the surface normal toward the tangent
53
+ const pn = norm(add(scale(nrm, Math.cos(lean)), scale(tan, Math.sin(lean))));
54
+ const px = norm(cross(pn, tan).map((v) => -v));
55
+ const py = cross(pn, px);
56
+ const s = bound * size * 0.32 * (0.6 + 0.8 * rand());
57
+ const center = add(scale(nrm, bound * 1.01 + s * 0.45 * Math.sin(lean)), scale(tan, s * 0.45 * Math.cos(lean)));
58
+ const m = [
59
+ px[0] * s, px[1] * s, px[2] * s, 0,
60
+ py[0] * s, py[1] * s, py[2] * s, 0,
61
+ pn[0] * s, pn[1] * s, pn[2] * s, 0,
62
+ center[0], center[1], center[2], 1,
63
+ ];
64
+ matrices.set(m, i * 16);
65
+ seeds[i * 3] = rand();
66
+ seeds[i * 3 + 1] = rand();
67
+ seeds[i * 3 + 2] = rand();
68
+ }
69
+ return { count: n, matrices, seeds };
70
+ }
71
+ function seedTriples(count, seed) {
72
+ const rand = mulberry32(seed);
73
+ const out = new Float32Array(count * 3);
74
+ for (let i = 0; i < out.length; i++)
75
+ out[i] = rand();
76
+ return out;
77
+ }
78
+ const iceVert = /* glsl */ `
79
+ attribute vec3 aSeed; // x: shape seed, y: phase, z: size jitter
80
+ uniform float uTime;
81
+ uniform float uPulseSpeed;
82
+ varying vec2 vUv;
83
+ varying vec3 vNormalW;
84
+ varying vec3 vViewW;
85
+ varying vec3 vTangentW;
86
+ varying vec3 vSeed;
87
+ varying float vGrowth;
88
+ void main(){
89
+ vUv = uv * 2.0 - 1.0;
90
+ vSeed = aSeed;
91
+ // Grow, hold, sublimate. Diffusion-limited growth: radius ~ sqrt(t).
92
+ float cyc = fract(uTime * uPulseSpeed * 0.06 + aSeed.y);
93
+ float grow = sqrt(clamp(cyc / 0.55, 0.0, 1.0));
94
+ float fade = 1.0 - smoothstep(0.82, 1.0, cyc);
95
+ vGrowth = grow * fade;
96
+ mat4 m = modelMatrix * instanceMatrix;
97
+ vec4 wp = m * vec4(position, 1.0);
98
+ vNormalW = normalize(mat3(m) * vec3(0.0, 0.0, 1.0));
99
+ vTangentW = normalize(mat3(m) * vec3(1.0, 0.0, 0.0));
100
+ vViewW = normalize(cameraPosition - wp.xyz);
101
+ gl_Position = projectionMatrix * viewMatrix * wp;
15
102
  }
16
103
  `;
17
- const spikeFrag = /* glsl */ `
104
+ const iceFrag = /* glsl */ `
18
105
  uniform vec3 uIceColor;
106
+ uniform float uHabit;
107
+ uniform float uBranches;
108
+ uniform float uSparkle;
19
109
  uniform float uOpacity;
20
110
  uniform float uIntensity;
21
- varying vec3 vNormal;
22
- varying vec3 vWorldPos;
111
+ varying vec2 vUv;
112
+ varying vec3 vNormalW;
113
+ varying vec3 vViewW;
114
+ varying vec3 vTangentW;
115
+ varying vec3 vSeed;
116
+ varying float vGrowth;
117
+
118
+ const float PI = 3.14159265359;
119
+
120
+ float h1(float n){ return fract(sin(n * 12.9898) * 43758.5453); }
121
+ float sdSeg(vec2 p, vec2 a, vec2 b){
122
+ vec2 pa = p - a, ba = b - a;
123
+ float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
124
+ return length(pa - ba * h);
125
+ }
126
+ float sdHexagon(vec2 p, float r){
127
+ const vec3 k = vec3(-0.866025404, 0.5, 0.577350269);
128
+ p = abs(p);
129
+ p -= 2.0 * min(dot(k.xy, p), 0.0) * k.xy;
130
+ p -= vec2(clamp(p.x, -k.z * r, k.z * r), r);
131
+ return length(p) * sign(p.y);
132
+ }
133
+ vec3 hue(float h){ return clamp(abs(mod(h * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0); }
23
134
 
24
- void main() {
25
- vec3 viewDir = normalize(cameraPosition - vWorldPos);
26
- vec3 n = normalize(vNormal);
135
+ void main(){
136
+ float R = vGrowth * 0.92;
137
+ if (R < 0.01) discard;
138
+ vec2 p = vUv;
139
+ float r = length(p);
140
+ if (r > 1.0) discard;
27
141
 
28
- // Specular glint — sharp facet reflection
29
- vec3 h = normalize(viewDir + vec3(0.6, 1.0, 0.4));
30
- float spec = pow(max(dot(n, h), 0.0), 38.0) * 2.5;
142
+ // Six-fold symmetry: fold into a 30 degree wedge with the arm along +x.
143
+ float a = atan(p.y, p.x);
144
+ a = abs(mod(a + PI / 6.0, PI / 3.0) - PI / 6.0);
145
+ vec2 q = r * vec2(cos(a), sin(a));
146
+ float sector = floor(mod(atan(p.y, p.x) + PI / 6.0, 2.0 * PI) / (PI / 3.0));
31
147
 
32
- // Fresnel edge glow
33
- float fresnel = pow(1.0 - abs(dot(n, viewDir)), 2.0);
148
+ // Dendrite: a main arm with side branches at 60 degrees. Branches nearer
149
+ // the centre started earlier and are longer (tip-splitting instability).
150
+ float d = sdSeg(q, vec2(0.0), vec2(R, 0.0));
151
+ float nb = clamp(floor(uBranches + 0.5), 0.0, 7.0);
152
+ for (int i = 0; i < 7; i++){
153
+ float fi = float(i);
154
+ if (fi >= nb) break;
155
+ float xi = R * (0.16 + 0.74 * (fi + 0.5) / max(nb, 1.0)) + (h1(vSeed.x * 7.0 + fi) - 0.5) * 0.04;
156
+ float li = (R - xi) * uHabit * (0.55 + 0.45 * h1(vSeed.x * 13.0 + fi));
157
+ vec2 b0 = vec2(xi, 0.0);
158
+ vec2 b1 = b0 + li * vec2(0.5, 0.8660254);
159
+ d = min(d, sdSeg(q, b0, b1));
160
+ // third-order ticks on long branches
161
+ if (uHabit > 0.5 && li > 0.12){
162
+ vec2 m = mix(b0, b1, 0.55);
163
+ d = min(d, sdSeg(q, m, m + li * 0.3 * (uHabit - 0.4) * vec2(1.0, 0.0)));
164
+ }
165
+ }
166
+ // Plate habit: a hexagonal plate with a ridge, shrinking as dendrites take over.
167
+ float plateR = R * mix(0.62, 0.16, uHabit);
168
+ float plate = sdHexagon(p, plateR);
169
+ float ridge = abs(sdHexagon(p, plateR * 0.55));
34
170
 
35
- // Facet shading: vary brightness by normal orientation
36
- float shade = 0.5 + 0.5 * abs(dot(n, normalize(vec3(1.0, 2.0, 0.5))));
171
+ float w = R * 0.032 + 0.006;
172
+ float fw = fwidth(d) + 1e-4;
173
+ float arms = 1.0 - smoothstep(w - fw, w + fw, d);
174
+ float fwp = fwidth(plate) + 1e-4;
175
+ float plateFill = 1.0 - smoothstep(-fwp, fwp, plate);
176
+ float plateLines = (1.0 - smoothstep(0.0, fwp * 1.5, abs(plate))) + (1.0 - smoothstep(0.0, fwp * 1.5, ridge)) * plateFill;
37
177
 
38
- vec3 col = uIceColor * shade + vec3(spec) + vec3(0.6, 0.9, 1.0) * fresnel * 0.5;
39
- float alpha = (shade * 0.5 + fresnel * 0.35 + spec * 0.15) * uOpacity * uIntensity;
40
- gl_FragColor = vec4(col, clamp(alpha, 0.0, 1.0));
178
+ float ice = max(arms, plateFill * 0.35);
179
+ if (ice + plateLines < 0.01) discard;
180
+
181
+ // Each sector is a slightly different facet: it glints only when the
182
+ // reflected light lines up with the eye, so sparkles follow the view.
183
+ vec3 N = normalize(vNormalW);
184
+ vec3 T = normalize(vTangentW);
185
+ vec3 B = cross(N, T);
186
+ float sa = (h1(vSeed.x * 3.0 + sector) - 0.5) * 0.7;
187
+ float sb = (h1(vSeed.x * 5.0 + sector) - 0.5) * 0.7;
188
+ vec3 Nf = normalize(N + T * sa + B * sb);
189
+ vec3 V = normalize(vViewW);
190
+ vec3 L = normalize(vec3(0.35, 1.0, 0.45));
191
+ vec3 H = normalize(L + V);
192
+ float glint = pow(max(abs(dot(Nf, H)), 0.0), 90.0) * uSparkle * 3.0;
193
+ float ndv = abs(dot(N, V));
194
+ float fres = pow(1.0 - ndv, 2.0);
195
+
196
+ // thin edges split light into colour
197
+ float edge = arms * (1.0 - smoothstep(0.0, w, d));
198
+ vec3 disp = hue(fract(d * 9.0 + ndv * 1.3 + vSeed.x)) * edge * 0.35;
199
+
200
+ vec3 col = uIceColor * (0.35 + 0.65 * fres) * ice
201
+ + uIceColor * plateLines * 0.6
202
+ + disp
203
+ + vec3(1.0) * glint * ice;
204
+ float alpha = clamp((ice * (0.35 + 0.45 * fres) + plateLines * 0.4 + glint) * uOpacity, 0.0, 1.0);
205
+ gl_FragColor = vec4(col * uIntensity, alpha);
41
206
  }
42
207
  `;
43
- // ── Frost particle shader ─────────────────────────────────────────────────────
44
- const frostVert = /* glsl */ `
45
- attribute float aBaseY;
46
- attribute float aSize;
47
- attribute vec2 aXZ;
48
- uniform float u_time;
49
- uniform float uRange;
50
- varying float vA;
51
- void main() {
52
- float y = mod(aBaseY - u_time * 0.35, uRange) - uRange * 0.5;
53
- vec3 pos = vec3(aXZ.x, y, aXZ.y);
54
- vA = 0.4 + 0.6 * smoothstep(-uRange * 0.5, -uRange * 0.3, y)
55
- * smoothstep(uRange * 0.5, uRange * 0.3, y);
56
- vec4 mv = modelViewMatrix * vec4(pos, 1.0);
57
- gl_PointSize = aSize * (180.0 / -mv.z);
58
- gl_Position = projectionMatrix * mv;
208
+ const dustVert = /* glsl */ `
209
+ attribute vec3 aSeed;
210
+ uniform float uTime;
211
+ uniform float uBound;
212
+ uniform float uIntensity;
213
+ varying float vGlint;
214
+ varying float vFade;
215
+ void main(){
216
+ // diamond dust: tiny plates drifting down in a slow spiral
217
+ float fall = fract(aSeed.x - uTime * 0.018);
218
+ float y = (fall - 0.5) * 3.2 * uBound;
219
+ float ang = aSeed.y * 6.2832 + uTime * 0.08;
220
+ float rad = uBound * (1.15 + 0.9 * aSeed.z);
221
+ vec3 pos = vec3(cos(ang) * rad, y, sin(ang) * rad);
222
+ vec4 wp = modelMatrix * vec4(pos, 1.0);
223
+ // each plate tumbles slowly; it flashes when its face mirrors light into the eye
224
+ float tum = uTime * 0.35 + aSeed.z * 20.0;
225
+ vec3 n = normalize(vec3(sin(tum), cos(tum * 0.7 + aSeed.y * 9.0), sin(tum * 0.5 + 1.3)));
226
+ vec3 V = normalize(cameraPosition - wp.xyz);
227
+ vec3 L = normalize(vec3(0.35, 1.0, 0.45));
228
+ vGlint = pow(abs(dot(n, normalize(L + V))), 60.0);
229
+ vFade = smoothstep(0.0, 0.12, fall) * (1.0 - smoothstep(0.88, 1.0, fall));
230
+ vec4 mv = viewMatrix * wp;
231
+ gl_PointSize = (1.5 + 7.0 * vGlint) * uIntensity * (6.0 * uBound / max(-mv.z, 1e-3));
232
+ gl_Position = projectionMatrix * mv;
59
233
  }
60
234
  `;
61
- const frostFrag = /* glsl */ `
235
+ const dustFrag = /* glsl */ `
236
+ uniform vec3 uIceColor;
62
237
  uniform float uOpacity;
63
- varying float vA;
64
- void main() {
65
- vec2 c = gl_PointCoord - 0.5;
66
- if (length(c) > 0.5) discard;
67
- float g = exp(-dot(c, c) * 6.0);
68
- gl_FragColor = vec4(0.85, 0.95, 1.0, g * vA * uOpacity);
238
+ uniform float uSparkle;
239
+ varying float vGlint;
240
+ varying float vFade;
241
+ void main(){
242
+ vec2 c = gl_PointCoord - 0.5;
243
+ float d = length(c);
244
+ if (d > 0.5) discard;
245
+ // a soft core plus a faint four-point star when it flashes
246
+ float core = smoothstep(0.5, 0.0, d);
247
+ float star = (exp(-abs(c.x) * 30.0) * exp(-c.y * c.y * 40.0) + exp(-abs(c.y) * 30.0) * exp(-c.x * c.x * 40.0)) * vGlint;
248
+ float a = (core * core * (0.25 + vGlint * uSparkle) + star * uSparkle) * vFade * uOpacity;
249
+ gl_FragColor = vec4(mix(uIceColor, vec3(1.0), 0.6 + 0.4 * vGlint), a);
69
250
  }
70
251
  `;
71
252
  const IceCrystalsEffect = ({ boundingRadius, config, animations, onObjectReady, onObjectDispose }) => {
72
- const { spikeCount, spikeLength, frostEnabled, iceColor, pulseSpeed, opacity, intensity } = config;
73
- const startValuesCacheRef = useRef({});
74
- const groupRef = useRef(null);
75
- useObjectReady(groupRef, config, onObjectReady, onObjectDispose);
76
- const timeRef = useRef(0);
77
- const r = boundingRadius;
78
- const N = Math.max(10, Math.min(spikeCount, 60));
79
- const { spikeMesh, spikeMat, frostGeo, frostMat } = useMemo(() => {
80
- const ic = new THREE.Color(iceColor);
81
- // ── Spike instanced mesh ─────────────────────────────────────────────
82
- const spikeLen = r * spikeLength * 0.55;
83
- const spikeBase = r * 0.055 * intensity;
84
- const spikeGeo = new THREE.ConeGeometry(spikeBase, spikeLen, 5);
85
- // Shift cone so its base is at origin (tip points up in local space)
86
- spikeGeo.translate(0, spikeLen / 2, 0);
87
- const sMat = new THREE.ShaderMaterial({
88
- vertexShader: spikeVert,
89
- fragmentShader: spikeFrag,
90
- uniforms: {
91
- uIceColor: { value: new THREE.Vector3(ic.r, ic.g, ic.b) },
92
- uOpacity: { value: opacity },
93
- uIntensity: { value: intensity },
94
- },
95
- transparent: true,
96
- depthWrite: false,
97
- blending: THREE.AdditiveBlending,
98
- side: THREE.DoubleSide,
99
- });
100
- const mesh = new THREE.InstancedMesh(spikeGeo, sMat, N);
101
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
102
- // Position spikes on Fibonacci sphere, pointing radially outward
103
- const tmpPos = new THREE.Vector3();
104
- const tmpQuat = new THREE.Quaternion();
105
- const tmpMat = new THREE.Matrix4();
106
- const UP = new THREE.Vector3(0, 1, 0);
107
- const goldenAngle = Math.PI * (3 - Math.sqrt(5));
108
- for (let i = 0; i < N; i++) {
109
- const y = 1 - (i / (N - 1)) * 2;
110
- const rad = Math.sqrt(1 - y * y) * r;
111
- const phi = i * goldenAngle;
112
- tmpPos.set(Math.cos(phi) * rad, y * r, Math.sin(phi) * rad);
113
- // Quaternion: rotate Y-up to radial direction
114
- const dir = tmpPos.clone().normalize();
115
- tmpQuat.setFromUnitVectors(UP, dir);
116
- // Scale Y randomly for variety
117
- const scaleY = 0.6 + Math.random() * 0.8;
118
- tmpMat.compose(tmpPos, tmpQuat, new THREE.Vector3(1, scaleY, 1));
119
- mesh.setMatrixAt(i, tmpMat);
120
- }
253
+ const { intensity = 1, speed = 0.5, opacity = 0.9, spikeCount = 30, spikeLength = 1, frostEnabled = true, iceColor = '#9fd8ff', pulseSpeed = 0.4, habit = 0.8, branches = 5, sparkle = 1, } = config;
254
+ const rootRef = useRef(null);
255
+ const animCache = useRef({});
256
+ useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
257
+ // a standalone effect gets boundingRadius 1; guard against an empty host
258
+ const b = boundingRadius > 0 ? boundingRadius : 1;
259
+ const uniforms = useMemo(() => ({
260
+ uTime: { value: 0 },
261
+ uSpeed: { value: 1 },
262
+ uBound: { value: 1 },
263
+ uIntensity: { value: 1 },
264
+ uOpacity: { value: 1 },
265
+ uIceColor: { value: new THREE.Color() },
266
+ uPulseSpeed: { value: 0.4 },
267
+ uHabit: { value: 0.8 },
268
+ uBranches: { value: 5 },
269
+ uSparkle: { value: 1 },
270
+ }), []);
271
+ const materials = useMemo(() => [
272
+ new THREE.ShaderMaterial({ vertexShader: iceVert, fragmentShader: iceFrag, uniforms, ...ADD, side: THREE.DoubleSide }),
273
+ new THREE.ShaderMaterial({ vertexShader: dustVert, fragmentShader: dustFrag, uniforms, ...ADD }),
274
+ ], [uniforms]);
275
+ useEffect(() => () => materials.forEach((m) => m.dispose()), [materials]);
276
+ const crystals = useMemo(() => {
277
+ const layout = layoutIceCrystals(spikeCount, b, spikeLength);
278
+ const plate = new THREE.PlaneGeometry(2, 2);
279
+ plate.setAttribute('aSeed', new THREE.InstancedBufferAttribute(layout.seeds, 3));
280
+ const mesh = new THREE.InstancedMesh(plate, materials[0], layout.count);
281
+ mesh.instanceMatrix.array.set(layout.matrices);
121
282
  mesh.instanceMatrix.needsUpdate = true;
122
- // ── Frost particles ──────────────────────────────────────────────────
123
- const FN = 180;
124
- const range = r * 2.5;
125
- const fBaseY = new Float32Array(FN);
126
- const fSize = new Float32Array(FN);
127
- const fXZ = new Float32Array(FN * 2);
128
- for (let i = 0; i < FN; i++) {
129
- fBaseY[i] = Math.random() * range;
130
- fSize[i] = 1.5 + Math.random() * 3.0;
131
- const a = Math.random() * Math.PI * 2;
132
- const fr = (0.9 + Math.random() * 0.8) * r;
133
- fXZ[i * 2] = Math.cos(a) * fr;
134
- fXZ[i * 2 + 1] = Math.sin(a) * fr;
135
- }
136
- const fGeo = new THREE.BufferGeometry();
137
- fGeo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(FN * 3), 3));
138
- fGeo.setAttribute('aBaseY', new THREE.BufferAttribute(fBaseY, 1));
139
- fGeo.setAttribute('aSize', new THREE.BufferAttribute(fSize, 1));
140
- fGeo.setAttribute('aXZ', new THREE.BufferAttribute(fXZ, 2));
141
- fGeo.frustumCulled = false;
142
- const fMat = new THREE.ShaderMaterial({
143
- vertexShader: frostVert,
144
- fragmentShader: frostFrag,
145
- uniforms: {
146
- u_time: { value: 0 },
147
- uRange: { value: range },
148
- uOpacity: { value: opacity * 0.55 },
149
- },
150
- transparent: true, depthWrite: false, blending: THREE.AdditiveBlending,
151
- });
152
- return { spikeMesh: mesh, spikeMat: sMat, frostGeo: fGeo, frostMat: fMat };
153
- // eslint-disable-next-line react-hooks/exhaustive-deps
154
- }, [N, r, spikeLength, iceColor, opacity, intensity]);
155
- // Pulse scale animation on spikes
156
- const pulseMats = useRef([]);
157
- useMemo(() => {
158
- const tmpMat = new THREE.Matrix4();
159
- const tmpPos = new THREE.Vector3();
160
- const tmpQuat = new THREE.Quaternion();
161
- const tmpScale = new THREE.Vector3();
162
- pulseMats.current = Array.from({ length: N }, (_, i) => {
163
- spikeMesh.getMatrixAt(i, tmpMat);
164
- return tmpMat.clone();
165
- });
166
- // eslint-disable-next-line react-hooks/exhaustive-deps
167
- }, [spikeMesh, N]);
168
- useFrame((state, delta) => {
169
- timeRef.current += delta;
170
- const t = timeRef.current;
171
- frostMat.uniforms.u_time.value = t;
172
- // Subtle spike pulse: scale Y slightly up/down per spike
173
- const tmpMat = new THREE.Matrix4();
174
- const tmpPos = new THREE.Vector3();
175
- const tmpQuat = new THREE.Quaternion();
176
- const tmpScale = new THREE.Vector3();
177
- for (let i = 0; i < N; i++) {
178
- const base = pulseMats.current[i];
179
- base.decompose(tmpPos, tmpQuat, tmpScale);
180
- const pulse = 0.88 + 0.12 * Math.sin(t * pulseSpeed * 1.8 + i * 0.47);
181
- tmpScale.y = pulse;
182
- tmpMat.compose(tmpPos, tmpQuat, tmpScale);
183
- spikeMesh.setMatrixAt(i, tmpMat);
184
- }
185
- spikeMesh.instanceMatrix.needsUpdate = true;
186
- if (animations?.length) {
187
- applyGenerativeAnimations([spikeMat, frostMat], animations, startValuesCacheRef.current, Date.now(), false);
188
- }
283
+ mesh.frustumCulled = false;
284
+ return mesh;
285
+ }, [spikeCount, b, spikeLength, materials]);
286
+ useEffect(() => () => { crystals.geometry.dispose(); crystals.dispose(); }, [crystals]);
287
+ const dustGeometry = useMemo(() => {
288
+ const g = new THREE.BufferGeometry();
289
+ g.setAttribute('position', new THREE.BufferAttribute(new Float32Array(DUST_COUNT * 3), 3));
290
+ g.setAttribute('aSeed', new THREE.BufferAttribute(seedTriples(DUST_COUNT, 9), 3));
291
+ return g;
292
+ }, []);
293
+ useEffect(() => () => dustGeometry.dispose(), [dustGeometry]);
294
+ useLayoutEffect(() => {
295
+ uniforms.uSpeed.value = speed;
296
+ uniforms.uBound.value = b;
297
+ uniforms.uIntensity.value = intensity;
298
+ uniforms.uOpacity.value = opacity;
299
+ uniforms.uIceColor.value.set(iceColor);
300
+ uniforms.uPulseSpeed.value = pulseSpeed;
301
+ uniforms.uHabit.value = habit;
302
+ uniforms.uBranches.value = branches;
303
+ uniforms.uSparkle.value = sparkle;
304
+ }, [uniforms, speed, b, intensity, opacity, iceColor, pulseSpeed, habit, branches, sparkle]);
305
+ useFrame((_, delta) => {
306
+ uniforms.uTime.value += delta * uniforms.uSpeed.value * 2;
307
+ if (animations?.length)
308
+ applyGenerativeAnimations(materials, animations, animCache.current, Date.now(), false);
189
309
  });
190
- return (_jsxs("group", { ref: groupRef, children: [_jsx("primitive", { object: spikeMesh }), frostEnabled && _jsx("points", { geometry: frostGeo, material: frostMat })] }));
310
+ return (_jsxs("group", { ref: rootRef, children: [_jsx("primitive", { object: crystals }), _jsx("points", { geometry: dustGeometry, material: materials[1], visible: frostEnabled, frustumCulled: false })] }));
191
311
  };
192
312
  export default IceCrystalsEffect;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { AnimationOptions, IceHaloConfig } from '@3driseai/3drise-core';
3
+ import type { ObjectReadyProps } from '../../types/objectReady';
4
+ export interface IceHaloEffectProps extends ObjectReadyProps {
5
+ boundingRadius: number;
6
+ config: IceHaloConfig;
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 IceHaloEffect: React.FC<IceHaloEffectProps>;
12
+ export default IceHaloEffect;
13
+ //# sourceMappingURL=IceHaloEffect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IceHaloEffect.d.ts","sourceRoot":"","sources":["../../../src/components/effects/IceHaloEffect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsD,MAAM,OAAO,CAAC;AAG3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,kBAAmB,SAAQ,gBAAgB;IACxD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAChC,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AA4RD,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA+B/C,CAAC;AAEF,eAAe,aAAa,CAAC"}