@3driseai/3drise-viewer 0.1.10 → 0.1.11

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 (57) hide show
  1. package/dist/components/CloudsGenerator.d.ts.map +1 -1
  2. package/dist/components/CloudsGenerator.js +3 -1
  3. package/dist/components/effects/DataStreamEffect.d.ts.map +1 -1
  4. package/dist/components/effects/DataStreamEffect.js +14 -8
  5. package/dist/components/effects/NeuralNetworkEffect.d.ts.map +1 -1
  6. package/dist/components/effects/NeuralNetworkEffect.js +11 -9
  7. package/dist/components/volumetricClouds/Cirrus.d.ts +4 -0
  8. package/dist/components/volumetricClouds/Cirrus.d.ts.map +1 -1
  9. package/dist/components/volumetricClouds/Cirrus.js +4 -0
  10. package/dist/components/volumetricClouds/Cumulonimbus.js +2 -2
  11. package/dist/components/volumetricClouds/Cumulus.js +2 -2
  12. package/dist/components/volumetricClouds/Stratocumulus.js +2 -2
  13. package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts +4 -1
  14. package/dist/components/volumetricClouds/VolumetricCloudsPass.d.ts.map +1 -1
  15. package/dist/components/volumetricClouds/VolumetricCloudsPass.js +45 -3
  16. package/dist/components/volumetricClouds/types.d.ts +3 -1
  17. package/dist/components/volumetricClouds/types.d.ts.map +1 -1
  18. package/dist/hooks/index.d.ts +2 -0
  19. package/dist/hooks/index.d.ts.map +1 -1
  20. package/dist/hooks/index.js +1 -0
  21. package/dist/hooks/useCloudLive.d.ts +1 -0
  22. package/dist/hooks/useCloudLive.d.ts.map +1 -1
  23. package/dist/hooks/useCloudLive.js +2 -1
  24. package/dist/hooks/useThunder.d.ts +37 -0
  25. package/dist/hooks/useThunder.d.ts.map +1 -0
  26. package/dist/hooks/useThunder.js +145 -0
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -1
  30. package/dist/shaders/clouds/CloudShadowMaterial.d.ts +52 -0
  31. package/dist/shaders/clouds/CloudShadowMaterial.d.ts.map +1 -0
  32. package/dist/shaders/clouds/CloudShadowMaterial.js +110 -0
  33. package/dist/shaders/clouds/CloudVolumeMaterial.d.ts +10 -0
  34. package/dist/shaders/clouds/CloudVolumeMaterial.d.ts.map +1 -1
  35. package/dist/shaders/clouds/CloudVolumeMaterial.js +38 -2
  36. package/dist/shaders/glsl/cloudCore.glsl.d.ts +3 -3
  37. package/dist/shaders/glsl/cloudCore.glsl.d.ts.map +1 -1
  38. package/dist/shaders/glsl/cloudCore.glsl.js +81 -9
  39. package/dist/shaders/glsl/cloudShadow.glsl.d.ts +15 -0
  40. package/dist/shaders/glsl/cloudShadow.glsl.d.ts.map +1 -0
  41. package/dist/shaders/glsl/cloudShadow.glsl.js +35 -0
  42. package/dist/types/cloudConfigs.d.ts +53 -0
  43. package/dist/types/cloudConfigs.d.ts.map +1 -1
  44. package/dist/types/cloudConfigs.js +83 -51
  45. package/dist/types/index.d.ts +2 -2
  46. package/dist/types/index.d.ts.map +1 -1
  47. package/dist/types/index.js +1 -1
  48. package/dist/utils/cloudShadowMap.d.ts +43 -0
  49. package/dist/utils/cloudShadowMap.d.ts.map +1 -0
  50. package/dist/utils/cloudShadowMap.js +60 -0
  51. package/dist/utils/cloudUniforms.d.ts +6 -1
  52. package/dist/utils/cloudUniforms.d.ts.map +1 -1
  53. package/dist/utils/cloudUniforms.js +18 -0
  54. package/dist/utils/gpuProfiler.d.ts +33 -0
  55. package/dist/utils/gpuProfiler.d.ts.map +1 -0
  56. package/dist/utils/gpuProfiler.js +115 -0
  57. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"CloudsGenerator.d.ts","sourceRoot":"","sources":["../../src/components/CloudsGenerator.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAOtE,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC1D;sFACkF;IAClF,QAAQ,CAAC,EAAE,OAAO,CAAC,wBAAwB,CAAC,GAAG,IAAI,CAAC;IACpD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA4CnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"CloudsGenerator.d.ts","sourceRoot":"","sources":["../../src/components/CloudsGenerator.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAOtE,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC1D;sFACkF;IAClF,QAAQ,CAAC,EAAE,OAAO,CAAC,wBAAwB,CAAC,GAAG,IAAI,CAAC;IACpD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA8CnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useMemo } from 'react';
3
- import { CLOUD_DECK_DEFAULTS, normalizeCirrus, normalizeCloudDeck, normalizeCloudQuality } from '../types/cloudConfigs.js';
3
+ import { CLOUD_DECK_DEFAULTS, normalizeCirrus, normalizeCloudDeck, normalizeCloudQuality, normalizeThunder } from '../types/cloudConfigs.js';
4
4
  import Cumulus from './volumetricClouds/Cumulus.js';
5
5
  import Stratocumulus from './volumetricClouds/Stratocumulus.js';
6
6
  import Cumulonimbus from './volumetricClouds/Cumulonimbus.js';
@@ -25,6 +25,7 @@ const CloudsGenerator = ({ settings, skySettings, timeSettings, sunIntensity, on
25
25
  const deck = useMemo(() => normalizeCloudDeck(settings?.config), [settings?.config]);
26
26
  const cirrus = useMemo(() => normalizeCirrus(settings?.cirrus), [settings?.cirrus]);
27
27
  const quality = useMemo(() => normalizeCloudQuality(settings?.quality), [settings?.quality]);
28
+ const thunder = useMemo(() => normalizeThunder(settings?.thunder), [settings?.thunder]);
28
29
  const deckOn = !!deck?.enabled;
29
30
  const cirrusOn = !!cirrus?.enabled;
30
31
  // The cirrus-only branch ignores the deck entirely (it passes deck={null} to the
@@ -37,6 +38,7 @@ const CloudsGenerator = ({ settings, skySettings, timeSettings, sunIntensity, on
37
38
  deck: deckOrDefault,
38
39
  cirrus: cirrusOn ? cirrus : null,
39
40
  quality,
41
+ thunder,
40
42
  skySettings,
41
43
  timeSettings,
42
44
  sunIntensity,
@@ -1 +1 @@
1
- {"version":3,"file":"DataStreamEffect.d.ts","sourceRoot":"","sources":["../../../src/components/effects/DataStreamEffect.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEhF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,UAAU,qBAAsB,SAAQ,gBAAgB;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACnC;AAqFD,QAAA,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAmDrD,CAAC;AACF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"DataStreamEffect.d.ts","sourceRoot":"","sources":["../../../src/components/effects/DataStreamEffect.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEhF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,UAAU,qBAAsB,SAAQ,gBAAgB;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACnC;AAuFD,QAAA,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAoErD,CAAC;AACF,eAAe,gBAAgB,CAAC"}
@@ -18,17 +18,19 @@ const vertexShader = /* glsl */ `
18
18
  uniform float uHeight;
19
19
  uniform float uPointSize;
20
20
  uniform float uIntensity;
21
+ uniform vec3 uColdColor;
22
+ uniform vec3 uMidColor;
23
+ uniform vec3 uHotColor;
24
+ uniform vec3 uTintColor;
21
25
 
22
26
  varying float vAlpha;
23
27
  varying vec3 vColor;
24
28
 
25
- // Palette: deep electric blue → cyan → white-gold
29
+ // Four-stop gradient: cold -> mid -> hot -> tint (config.color)
26
30
  vec3 palette(float t) {
27
- vec3 cold = vec3(0.05, 0.15, 0.9); // deep blue
28
- vec3 mid = vec3(0.0, 0.85, 1.0); // cyan
29
- vec3 hot = vec3(1.0, 0.92, 0.55); // warm gold-white
30
- if (t < 0.5) return mix(cold, mid, t * 2.0);
31
- else return mix(mid, hot, (t - 0.5) * 2.0);
31
+ if (t < 0.333) return mix(uColdColor, uMidColor, t * 3.0);
32
+ else if (t < 0.666) return mix(uMidColor, uHotColor, (t - 0.333) * 3.0);
33
+ else return mix(uHotColor, uTintColor, (t - 0.666) * 3.0);
32
34
  }
33
35
 
34
36
  void main() {
@@ -87,7 +89,7 @@ const fragmentShader = /* glsl */ `
87
89
  }
88
90
  `;
89
91
  const DataStreamEffect = ({ boundingRadius, config, animations, onObjectReady, onObjectDispose }) => {
90
- const { particleCount, spiralTurns, streamRadius, inward, particleSize, opacity, speed, intensity } = config;
92
+ const { particleCount, spiralTurns, streamRadius, inward, particleSize, opacity, speed, intensity, color = '#ffffff', coldColor = '#0d26e6', midColor = '#00d9ff', hotColor = '#ffeb8c', } = config;
91
93
  const startValuesCacheRef = useRef({});
92
94
  const rootRef = useRef(null);
93
95
  useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
@@ -123,13 +125,17 @@ const DataStreamEffect = ({ boundingRadius, config, animations, onObjectReady, o
123
125
  uPointSize: { value: particleSize * 80 },
124
126
  uIntensity: { value: intensity },
125
127
  uOpacity: { value: opacity },
128
+ uColdColor: { value: new THREE.Color(coldColor) },
129
+ uMidColor: { value: new THREE.Color(midColor) },
130
+ uHotColor: { value: new THREE.Color(hotColor) },
131
+ uTintColor: { value: new THREE.Color(color) },
126
132
  },
127
133
  transparent: true,
128
134
  depthWrite: false,
129
135
  blending: THREE.AdditiveBlending,
130
136
  });
131
137
  return { geometry: geo, material: mat };
132
- }, [particleCount, spiralTurns, r, h, inward, particleSize, intensity, speed, opacity]);
138
+ }, [particleCount, spiralTurns, r, h, inward, particleSize, intensity, speed, opacity, coldColor, midColor, hotColor, color]);
133
139
  useFrame((state) => {
134
140
  material.uniforms.u_time.value = state.clock.getElapsedTime();
135
141
  if (animations?.length) {
@@ -1 +1 @@
1
- {"version":3,"file":"NeuralNetworkEffect.d.ts","sourceRoot":"","sources":["../../../src/components/effects/NeuralNetworkEffect.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,UAAU,wBAAyB,SAAQ,gBAAgB;IACvD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACnC;AA6BD,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CA4J3D,CAAC;AACF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"NeuralNetworkEffect.d.ts","sourceRoot":"","sources":["../../../src/components/effects/NeuralNetworkEffect.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,UAAU,wBAAyB,SAAQ,gBAAgB;IACvD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;CACnC;AA+BD,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CA4J3D,CAAC;AACF,eAAe,mBAAmB,CAAC"}
@@ -4,7 +4,7 @@ import { useFrame } from '@react-three/fiber';
4
4
  import * as THREE from 'three';
5
5
  import { applyGenerativeAnimations } from '../../utils/handleGenerativeEffectsAnimations.js';
6
6
  import { useObjectReady } from '../../hooks/useObjectReady.js';
7
- // Glow sprite for pulse signals and node flashes
7
+ // Tight point sprites for electrical signals moving through the connections.
8
8
  const pulseVert = /* glsl */ `
9
9
  attribute float aBright;
10
10
  uniform float uSize;
@@ -13,7 +13,7 @@ const pulseVert = /* glsl */ `
13
13
  vB = aBright;
14
14
  if (vB < 0.01) { gl_PointSize = 0.0; gl_Position = vec4(0); return; }
15
15
  vec4 mv = modelViewMatrix * vec4(position, 1.0);
16
- gl_PointSize = uSize * vB * (200.0 / -mv.z);
16
+ gl_PointSize = uSize * (0.75 + vB * 0.25) * (200.0 / -mv.z);
17
17
  gl_Position = projectionMatrix * mv;
18
18
  }
19
19
  `;
@@ -23,10 +23,12 @@ const pulseFrag = /* glsl */ `
23
23
  varying float vB;
24
24
  void main() {
25
25
  if (vB < 0.01) discard;
26
- vec2 c = gl_PointCoord - 0.5;
27
- if (length(c) > 0.5) discard;
28
- float g = exp(-dot(c, c) * 5.5);
29
- gl_FragColor = vec4(uColor, g * vB * uOpacity);
26
+ vec2 c = gl_PointCoord - 0.5;
27
+ float radius = length(c);
28
+ if (radius > 0.5) discard;
29
+ float core = smoothstep(0.24, 0.0, radius);
30
+ float halo = smoothstep(0.5, 0.12, radius) * 0.2;
31
+ gl_FragColor = vec4(uColor, (core + halo) * vB * uOpacity);
30
32
  }
31
33
  `;
32
34
  const MAX_PULSES = 4;
@@ -105,7 +107,7 @@ const NeuralNetworkEffect = ({ boundingRadius, config, animations, onObjectReady
105
107
  const pulseMat = new THREE.ShaderMaterial({
106
108
  vertexShader: pulseVert, fragmentShader: pulseFrag,
107
109
  uniforms: {
108
- uSize: { value: 20 * intensity },
110
+ uSize: { value: 7 * intensity },
109
111
  uColor: { value: new THREE.Vector3(pc.r, pc.g, pc.b) },
110
112
  uOpacity: { value: opacity },
111
113
  },
@@ -176,11 +178,11 @@ const NeuralNetworkEffect = ({ boundingRadius, config, animations, onObjectReady
176
178
  }
177
179
  const pos = curPos[p.fromIdx].clone().lerp(curPos[p.toIdx], p.progress);
178
180
  ptPos.setXYZ(pi, pos.x, pos.y, pos.z);
179
- ptBright.setX(pi, 0.7 + 0.3 * Math.sin(p.progress * Math.PI));
181
+ ptBright.setX(pi, 0.75 + 0.25 * Math.sin(p.progress * Math.PI * 3.0));
180
182
  });
181
183
  ptPos.needsUpdate = true;
182
184
  ptBright.needsUpdate = true;
183
- pulseMat.uniforms.uSize.value = 20 * intensity;
185
+ pulseMat.uniforms.uSize.value = 7 * intensity;
184
186
  if (animations?.length) {
185
187
  applyGenerativeAnimations([pulseMat], animations, startValuesCacheRef.current, Date.now(), false);
186
188
  }
@@ -8,6 +8,10 @@ import type { CloudLayerProps } from './types';
8
8
  * case where the deck is switched off entirely, so the deck march is skipped and
9
9
  * only the three-sample ice pass runs. That is a fraction of the cost of any
10
10
  * volumetric type, which is why a clear-with-cirrus sky is close to free.
11
+ *
12
+ * No thunder here, deliberately: a bolt needs a deck to sit inside and a volume to
13
+ * light. An ice sheet has neither, so the flash would have nowhere to originate and
14
+ * nothing to scatter through.
11
15
  */
12
16
  declare const Cirrus: React.FC<CloudLayerProps>;
13
17
  export default Cirrus;
@@ -1 +1 @@
1
- {"version":3,"file":"Cirrus.d.ts","sourceRoot":"","sources":["../../../src/components/volumetricClouds/Cirrus.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;GAQG;AACH,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAErC,CAAC;AACF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"Cirrus.d.ts","sourceRoot":"","sources":["../../../src/components/volumetricClouds/Cirrus.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;;;;;GAYG;AACH,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAErC,CAAC;AACF,eAAe,MAAM,CAAC"}
@@ -8,6 +8,10 @@ import VolumetricCloudsPass from './VolumetricCloudsPass.js';
8
8
  * case where the deck is switched off entirely, so the deck march is skipped and
9
9
  * only the three-sample ice pass runs. That is a fraction of the cost of any
10
10
  * volumetric type, which is why a clear-with-cirrus sky is close to free.
11
+ *
12
+ * No thunder here, deliberately: a bolt needs a deck to sit inside and a volume to
13
+ * light. An ice sheet has neither, so the flash would have nowhere to originate and
14
+ * nothing to scatter through.
11
15
  */
12
16
  const Cirrus = ({ cirrus, quality, skySettings, timeSettings, sunIntensity, animations, mouseMove, id, onObjectReady, onObjectDispose }) => (_jsx(VolumetricCloudsPass, { deck: null, cirrus: cirrus, quality: quality, skySettings: skySettings, timeSettings: timeSettings, sunIntensity: sunIntensity, animations: animations, mouseMove: mouseMove, id: id, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose }));
13
17
  export default Cirrus;
@@ -13,8 +13,8 @@ import VolumetricCloudsPass from './VolumetricCloudsPass.js';
13
13
  * Cost scales with depth, since the fine step is derived from it, so a shallow step
14
14
  * budget on a 9 km tower gives kilometre steps and loses the structure. Floored here.
15
15
  */
16
- const Cumulonimbus = ({ deck, cirrus, quality, skySettings, timeSettings, sunIntensity, animations, mouseMove, id, onObjectReady, onObjectDispose }) => {
16
+ const Cumulonimbus = ({ deck, cirrus, quality, thunder, skySettings, timeSettings, sunIntensity, animations, mouseMove, id, onObjectReady, onObjectDispose }) => {
17
17
  const resolvedQuality = useMemo(() => ({ ...quality, steps: Math.max(quality.steps, 96) }), [quality]);
18
- return (_jsx(VolumetricCloudsPass, { deck: deck, cirrus: cirrus, quality: resolvedQuality, skySettings: skySettings, timeSettings: timeSettings, sunIntensity: sunIntensity, animations: animations, mouseMove: mouseMove, id: id, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose }));
18
+ return (_jsx(VolumetricCloudsPass, { deck: deck, cirrus: cirrus, quality: resolvedQuality, thunder: thunder, skySettings: skySettings, timeSettings: timeSettings, sunIntensity: sunIntensity, animations: animations, mouseMove: mouseMove, id: id, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose }));
19
19
  };
20
20
  export default Cumulonimbus;
@@ -10,8 +10,8 @@ import VolumetricCloudsPass from './VolumetricCloudsPass.js';
10
10
  * inside the shader anyway, so leaving a stale value in the config would do nothing
11
11
  * visible while making the panel read as though it should.
12
12
  */
13
- const Cumulus = ({ deck, cirrus, quality, skySettings, timeSettings, sunIntensity, animations, mouseMove, id, onObjectReady, onObjectDispose }) => {
13
+ const Cumulus = ({ deck, cirrus, quality, thunder, skySettings, timeSettings, sunIntensity, animations, mouseMove, id, onObjectReady, onObjectDispose }) => {
14
14
  const resolved = useMemo(() => ({ ...deck, anvil: 0, precipitation: 0 }), [deck]);
15
- return (_jsx(VolumetricCloudsPass, { deck: resolved, cirrus: cirrus, quality: quality, skySettings: skySettings, timeSettings: timeSettings, sunIntensity: sunIntensity, animations: animations, mouseMove: mouseMove, id: id, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose }));
15
+ return (_jsx(VolumetricCloudsPass, { deck: resolved, cirrus: cirrus, quality: quality, thunder: thunder, skySettings: skySettings, timeSettings: timeSettings, sunIntensity: sunIntensity, animations: animations, mouseMove: mouseMove, id: id, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose }));
16
16
  };
17
17
  export default Cumulus;
@@ -8,9 +8,9 @@ import VolumetricCloudsPass from './VolumetricCloudsPass.js';
8
8
  * small and a large step budget is wasted; it is capped here. Anvil and
9
9
  * precipitation are off for the same reason as cumulus.
10
10
  */
11
- const Stratocumulus = ({ deck, cirrus, quality, skySettings, timeSettings, sunIntensity, animations, mouseMove, id, onObjectReady, onObjectDispose }) => {
11
+ const Stratocumulus = ({ deck, cirrus, quality, thunder, skySettings, timeSettings, sunIntensity, animations, mouseMove, id, onObjectReady, onObjectDispose }) => {
12
12
  const resolved = useMemo(() => ({ ...deck, anvil: 0, precipitation: 0 }), [deck]);
13
13
  const resolvedQuality = useMemo(() => ({ ...quality, steps: Math.min(quality.steps, 112) }), [quality]);
14
- return (_jsx(VolumetricCloudsPass, { deck: resolved, cirrus: cirrus, quality: resolvedQuality, skySettings: skySettings, timeSettings: timeSettings, sunIntensity: sunIntensity, animations: animations, mouseMove: mouseMove, id: id, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose }));
14
+ return (_jsx(VolumetricCloudsPass, { deck: resolved, cirrus: cirrus, quality: resolvedQuality, thunder: thunder, skySettings: skySettings, timeSettings: timeSettings, sunIntensity: sunIntensity, animations: animations, mouseMove: mouseMove, id: id, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose }));
15
15
  };
16
16
  export default Stratocumulus;
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import type { ObjectReadyProps } from '../../types/objectReady';
3
3
  import type { SkySettings, TimeSettings } from '@3driseai/3drise-core';
4
- import type { CirrusLayerConfig, CloudDeckConfig, CloudsQualityConfig } from '../../types/cloudConfigs';
4
+ import type { CirrusLayerConfig, CloudDeckConfig, CloudsQualityConfig, ThunderConfig } from '../../types/cloudConfigs';
5
5
  import type { ObjectAnimations, MouseMoveInteractions } from '@3driseai/3drise-core';
6
6
  /**
7
7
  * The cloud render pipeline. The only component in this package that takes over
@@ -28,6 +28,9 @@ export interface VolumetricCloudsPassProps extends ObjectReadyProps {
28
28
  deck?: CloudDeckConfig | null;
29
29
  cirrus?: CirrusLayerConfig | null;
30
30
  quality: CloudsQualityConfig;
31
+ /** Lightning. Needs a deck — the bolt sits inside it and lights it from within —
32
+ * so it is ignored on the cirrus-only path. */
33
+ thunder?: ThunderConfig;
31
34
  skySettings?: SkySettings;
32
35
  timeSettings?: TimeSettings;
33
36
  /** Multiplies sun radiance for the clouds only. Matches SkySystem's sunIntensity. */
@@ -1 +1 @@
1
- {"version":3,"file":"VolumetricCloudsPass.d.ts","sourceRoot":"","sources":["../../../src/components/volumetricClouds/VolumetricCloudsPass.tsx"],"names":[],"mappings":"AACA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAIvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAQvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAExG,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAErF;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IAC/D,6EAA6E;IAC7E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,6DAA6D;IAC7D,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAkBD,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA0S7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"VolumetricCloudsPass.d.ts","sourceRoot":"","sources":["../../../src/components/volumetricClouds/VolumetricCloudsPass.tsx"],"names":[],"mappings":"AACA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAKvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAQvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEvH,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAErF;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IAC/D,6EAA6E;IAC7E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,mBAAmB,CAAC;IAC7B;oDACgD;IAChD,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,6DAA6D;IAC7D,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAkBD,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAyV7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -3,6 +3,7 @@ import * as THREE from 'three';
3
3
  import { useCallback, useEffect, useMemo, useRef } from 'react';
4
4
  import { useFrame, useThree } from '@react-three/fiber';
5
5
  import { useFrameRenderer } from '../../hooks/useFrameRenderer.js';
6
+ import { useThunder } from '../../hooks/useThunder.js';
6
7
  import { useObjectReady } from '../../hooks/useObjectReady.js';
7
8
  import { CLOUD_LIVE_KEY } from '../../hooks/useCloudLive.js';
8
9
  import { useCloudNoise } from '../../hooks/useCloudNoise.js';
@@ -11,7 +12,7 @@ import { CloudVolumeMaterial } from '../../shaders/clouds/CloudVolumeMaterial.js
11
12
  import { CirrusMaterial } from '../../shaders/clouds/CirrusMaterial.js';
12
13
  import { CloudResolveMaterial } from '../../shaders/clouds/CloudResolveMaterial.js';
13
14
  import { CloudCompositeMaterial } from '../../shaders/clouds/CloudCompositeMaterial.js';
14
- import { applyCameraUniforms, applyCirrusUniforms, applyDeckUniforms, applySunUniforms, } from '../../utils/cloudUniforms.js';
15
+ import { applyCameraUniforms, applyCirrusUniforms, applyDeckUniforms, applySunUniforms, applyThunderUniforms } from '../../utils/cloudUniforms.js';
15
16
  import { applyCloudsKeyframeAnimations, applyCloudsMouseMove, resolveDeckConfig, } from '../../utils/handleCloudsAnimations.js';
16
17
  const quad = new THREE.PlaneGeometry(2, 2);
17
18
  const orthoCam = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
@@ -26,7 +27,7 @@ function emptyTexture() {
26
27
  tex.needsUpdate = true;
27
28
  return tex;
28
29
  }
29
- const VolumetricCloudsPass = ({ id = '3drise-clouds', deck, cirrus, quality, skySettings, timeSettings, sunIntensity = 1, animations, mouseMove, onObjectReady, onObjectDispose, }) => {
30
+ const VolumetricCloudsPass = ({ id = '3drise-clouds', deck, cirrus, quality, thunder, skySettings, timeSettings, sunIntensity = 1, animations, mouseMove, onObjectReady, onObjectDispose, }) => {
30
31
  const gl = useThree((s) => s.gl);
31
32
  const scene = useThree((s) => s.scene);
32
33
  const size = useThree((s) => s.size);
@@ -111,12 +112,16 @@ const VolumetricCloudsPass = ({ id = '3drise-clouds', deck, cirrus, quality, sky
111
112
  // frame loop reads it on top of the config, so the deck responds without a Redux
112
113
  // dispatch per frame. Cleared when the config identity changes, which is exactly
113
114
  // slider-end committing the same value.
115
+ const thunderDriver = useThunder();
116
+ const boltRef = useRef(null);
114
117
  const liveDeck = useRef({});
118
+ const liveThunder = useRef({});
115
119
  const liveCirrus = useRef({});
116
120
  const liveQuality = useRef({});
117
121
  useEffect(() => { liveDeck.current = {}; }, [deck]);
118
122
  useEffect(() => { liveCirrus.current = {}; }, [cirrus]);
119
123
  useEffect(() => { liveQuality.current = {}; }, [quality]);
124
+ useEffect(() => { liveThunder.current = {}; }, [thunder]);
120
125
  // The handle a controller reaches through findById(id).userData. Set in an effect,
121
126
  // never as a userData JSX prop: R3F replaces the whole userData object on every
122
127
  // render, which would drop the handle the moment anything above re-renders.
@@ -125,6 +130,7 @@ const VolumetricCloudsPass = ({ id = '3drise-clouds', deck, cirrus, quality, sky
125
130
  deck: (key, value) => { liveDeck.current[key] = value; },
126
131
  cirrus: (key, value) => { liveCirrus.current[key] = value; },
127
132
  quality: (key, value) => { liveQuality.current[key] = value; },
133
+ thunder: (key, value) => { liveThunder.current[key] = value; },
128
134
  }), []);
129
135
  useEffect(() => {
130
136
  const root = rootRef.current;
@@ -159,6 +165,29 @@ const VolumetricCloudsPass = ({ id = '3drise-clouds', deck, cirrus, quality, sky
159
165
  frame.current += 1;
160
166
  time.current += Math.min(delta, 0.05);
161
167
  updateSun();
168
+ // Lightning is scheduled on the CPU because three things need the same event:
169
+ // the emission uniforms, the scene light, and the temporal blend below.
170
+ const liveThunderCfg = (thunder
171
+ ? { ...thunder, ...liveThunder.current }
172
+ : undefined);
173
+ const bolt = (liveThunderCfg && deck)
174
+ ? thunderDriver.update(delta, deck, liveThunderCfg, camera)
175
+ : null;
176
+ // The scene light is what stops the flash looking like it happens in a
177
+ // separate universe from the ground. decay 0 / distance 0 on purpose: a bolt
178
+ // 2 km up with physical falloff contributes nothing at ground level, and what
179
+ // a distant flash actually does is brighten everything at once.
180
+ if (boltRef.current) {
181
+ const lit = bolt && liveThunderCfg
182
+ ? bolt.intensity * liveThunderCfg.sceneFlash
183
+ : 0;
184
+ boltRef.current.intensity = lit;
185
+ boltRef.current.visible = lit > 0.001;
186
+ if (lit > 0.001) {
187
+ boltRef.current.position.copy(bolt.position);
188
+ boltRef.current.color.set(liveThunderCfg.color);
189
+ }
190
+ }
162
191
  // ---- 1. scene ----
163
192
  const sceneRT = (frame.current & 1) ? targets.sceneA : targets.sceneB;
164
193
  const prevTarget = gl.getRenderTarget();
@@ -204,6 +233,12 @@ const VolumetricCloudsPass = ({ id = '3drise-clouds', deck, cirrus, quality, sky
204
233
  const liveQual = { ...quality, ...liveQuality.current };
205
234
  const m = passes.deckMat;
206
235
  applyDeckUniforms(m, live, liveQual);
236
+ if (liveThunderCfg && bolt) {
237
+ applyThunderUniforms(m, liveThunderCfg, bolt.intensity, bolt.position);
238
+ }
239
+ else if (m.uniforms.u_flash) {
240
+ m.uniforms.u_flash.value = 0;
241
+ }
207
242
  m.uniforms.u_base.value = noise.base;
208
243
  m.uniforms.u_detail.value = noise.detail;
209
244
  m.uniforms.u_weather.value = noise.weather;
@@ -236,6 +271,13 @@ const VolumetricCloudsPass = ({ id = '3drise-clouds', deck, cirrus, quality, sky
236
271
  // and a high history weight smears. Ease off, and restore it once still.
237
272
  const moved = camera.position.distanceToSquared(prevCamPos.current) > 1e-8
238
273
  || Math.abs(camera.quaternion.dot(prevCamQuat.current)) < 0.9999995;
274
+ // Deliberately NOT special-cased for lightning. The resolve clamps history
275
+ // to the 3x3 neighbourhood of the current frame before blending, and that
276
+ // clamp already handles a flash in both directions: dark history is pulled
277
+ // up when the bolt fires, and the bright frame is clamped away the moment
278
+ // it stops. Dropping the blend during a flash — which is what this used to
279
+ // do — throws away the temporal accumulation that turns the march's
280
+ // sub-pixel jitter into detail, and the whole deck visibly shakes.
239
281
  r.u_blend.value = moved ? Math.min(quality.temporalBlend, 0.7) : quality.temporalBlend;
240
282
  gl.setRenderTarget(destination);
241
283
  gl.render(passes.resolveScene, orthoCam);
@@ -293,6 +335,6 @@ const VolumetricCloudsPass = ({ id = '3drise-clouds', deck, cirrus, quality, sky
293
335
  useFrameRenderer(renderCompleteFrame);
294
336
  // An empty group, but a real one: it carries userData.id so findById() can reach
295
337
  // this layer, and the live handle so a controller can drive it without Redux.
296
- return _jsx("group", { ref: rootRef });
338
+ return (_jsx("group", { ref: rootRef, children: _jsx("pointLight", { ref: boltRef, visible: false, intensity: 0, distance: 0, decay: 0 }) }));
297
339
  };
298
340
  export default VolumetricCloudsPass;
@@ -1,6 +1,6 @@
1
1
  import type { MouseMoveInteractions, ObjectAnimations, SkySettings, TimeSettings } from '@3driseai/3drise-core';
2
2
  import type { ObjectReadyProps } from '../../types/objectReady';
3
- import type { CirrusLayerConfig, CloudDeckConfig, CloudsQualityConfig } from '../../types/cloudConfigs';
3
+ import type { CirrusLayerConfig, CloudDeckConfig, CloudsQualityConfig, ThunderConfig } from '../../types/cloudConfigs';
4
4
  /** Shared by every layer component. The generator hands all four the same shape. */
5
5
  export interface CloudLayerProps extends ObjectReadyProps {
6
6
  /** Scene id, so a controller can findById() the layer and drive it live. */
@@ -8,6 +8,8 @@ export interface CloudLayerProps extends ObjectReadyProps {
8
8
  deck: CloudDeckConfig;
9
9
  cirrus: CirrusLayerConfig | null;
10
10
  quality: CloudsQualityConfig;
11
+ /** Lightning. Absent or disabled means the flash path never runs. */
12
+ thunder?: ThunderConfig;
11
13
  skySettings?: SkySettings;
12
14
  timeSettings?: TimeSettings;
13
15
  sunIntensity?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/volumetricClouds/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAExG,oFAAoF;AACpF,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACrD,4EAA4E;IAC5E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACjC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACrC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/volumetricClouds/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEvH,oFAAoF;AACpF,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACrD,4EAA4E;IAC5E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACjC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,qEAAqE;IACrE,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACrC"}
@@ -25,6 +25,8 @@ export { useCubeCamera } from './useCubeCamera';
25
25
  export type { UseEnvironmentProbeOptions } from './useEnvironmentProbe';
26
26
  export { useEnvironmentProbe } from './useEnvironmentProbe';
27
27
  export { useFrameRenderer } from './useFrameRenderer';
28
+ export type { ThunderDriver, ThunderState } from './useThunder';
29
+ export { createThunderDriver, useThunder } from './useThunder';
28
30
  export type { UseEnvironmentEffectsOptions } from './useEnvironmentEffects';
29
31
  export { useEnvironmentEffects } from './useEnvironmentEffects';
30
32
  export { useFrameAnimations } from './useFrameAnimations';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,7 @@ export { useContinuousEffects } from './useContinuousEffects.js';
17
17
  export { useCubeCamera } from './useCubeCamera.js';
18
18
  export { useEnvironmentProbe } from './useEnvironmentProbe.js';
19
19
  export { useFrameRenderer } from './useFrameRenderer.js';
20
+ export { createThunderDriver, useThunder } from './useThunder.js';
20
21
  export { useEnvironmentEffects } from './useEnvironmentEffects.js';
21
22
  export { useFrameAnimations } from './useFrameAnimations.js';
22
23
  export { useFrameEffects } from './useFrameEffects.js';
@@ -18,6 +18,7 @@ export interface CloudLiveHandle {
18
18
  deck(key: string, value: number | boolean): void;
19
19
  cirrus(key: string, value: number | boolean): void;
20
20
  quality(key: string, value: number | boolean): void;
21
+ thunder(key: string, value: number | boolean | string): void;
21
22
  }
22
23
  /**
23
24
  * Safe before the layer mounts and after it unmounts: the handle is resolved at call
@@ -1 +1 @@
1
- {"version":3,"file":"useCloudLive.d.ts","sourceRoot":"","sources":["../../src/hooks/useCloudLive.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGtC;;;;;;;;;;;;GAYG;AAEH,mFAAmF;AACnF,eAAO,MAAM,cAAc,gBAAgB,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC5B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACjD,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACnD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;CACvD;AAID;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,GAAG,eAAe,CAkB/E;AAED,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"useCloudLive.d.ts","sourceRoot":"","sources":["../../src/hooks/useCloudLive.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGtC;;;;;;;;;;;;GAYG;AAEH,mFAAmF;AACnF,eAAO,MAAM,cAAc,gBAAgB,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC5B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACjD,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACnD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IACpD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,IAAI,CAAC;CAChE;AAID;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,GAAG,eAAe,CAmB/E;AAED,eAAe,YAAY,CAAC"}
@@ -15,7 +15,7 @@ import { findById } from '../utils/sceneQuery.js';
15
15
  */
16
16
  /** Where VolumetricCloudsPass stashes its handle on the root object's userData. */
17
17
  export const CLOUD_LIVE_KEY = '__cloudLive';
18
- const NOOP = { deck: () => { }, cirrus: () => { }, quality: () => { } };
18
+ const NOOP = { deck: () => { }, cirrus: () => { }, quality: () => { }, thunder: () => { } };
19
19
  /**
20
20
  * Safe before the layer mounts and after it unmounts: the handle is resolved at call
21
21
  * time, not at hook time, so a controller opened before the scene is ready still works
@@ -37,6 +37,7 @@ export function useCloudLive(target) {
37
37
  deck: (key, value) => resolve().deck(key, value),
38
38
  cirrus: (key, value) => resolve().cirrus(key, value),
39
39
  quality: (key, value) => resolve().quality(key, value),
40
+ thunder: (key, value) => resolve().thunder(key, value),
40
41
  };
41
42
  }, [target]);
42
43
  }
@@ -0,0 +1,37 @@
1
+ import * as THREE from 'three';
2
+ import type { CloudDeckConfig, ThunderConfig } from '../types/cloudConfigs';
3
+ /**
4
+ * Flash scheduling for lightning.
5
+ *
6
+ * On the CPU rather than as a time hash in the shader, because three consumers need
7
+ * the *same* event: the volumetric emission uniforms, the scene light, and the
8
+ * temporal resolve (which has to stop blending while a flash is on screen, or the
9
+ * flash both under-reads and then ghosts for twenty frames). A GPU-side hash can
10
+ * drive only the first of those.
11
+ *
12
+ * Two things make it read as lightning rather than a blinking lamp:
13
+ *
14
+ * - **Exponential spacing.** Storms do not fire on a metronome. Waiting an
15
+ * exponentially distributed interval around the mean rate is the actual arrival
16
+ * process, and it costs one log().
17
+ * - **Multi-stroke flicker.** A single bolt is 2-5 return strokes over ~200 ms, each
18
+ * a fast attack and exponential decay. One clean flash looks synthetic; the
19
+ * stutter is what the eye recognises.
20
+ */
21
+ export interface ThunderState {
22
+ /** 0 when dark. Peaks at ThunderConfig.intensity. */
23
+ intensity: number;
24
+ /** Bolt position in world space. Only meaningful while intensity > 0. */
25
+ position: THREE.Vector3;
26
+ /** True while the bolt is actually emitting light. */
27
+ active: boolean;
28
+ }
29
+ export interface ThunderDriver {
30
+ /** Advance by delta seconds and return the current state. */
31
+ update(dt: number, deck: CloudDeckConfig, cfg: ThunderConfig, camera: THREE.Camera): ThunderState;
32
+ }
33
+ export declare function createThunderDriver(): ThunderDriver;
34
+ /** One driver per mounted pass, stable across renders. */
35
+ export declare function useThunder(): ThunderDriver;
36
+ export default useThunder;
37
+ //# sourceMappingURL=useThunder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useThunder.d.ts","sourceRoot":"","sources":["../../src/hooks/useThunder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE5E;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,YAAY;IACzB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC;IACxB,sDAAsD;IACtD,MAAM,EAAE,OAAO,CAAC;CACnB;AAID,MAAM,WAAW,aAAa;IAC1B,6DAA6D;IAC7D,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC;CACrG;AAuED,wBAAgB,mBAAmB,IAAI,aAAa,CAiEnD;AAED,0DAA0D;AAC1D,wBAAgB,UAAU,IAAI,aAAa,CAM1C;AAED,eAAe,UAAU,CAAC"}