@3driseai/3drise-viewer 0.1.22 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CreatedObject.d.ts.map +1 -1
- package/dist/components/CreatedObject.js +6 -1
- package/dist/components/LightObject.d.ts.map +1 -1
- package/dist/components/LightObject.js +24 -13
- package/dist/components/ModelPrimitive.d.ts.map +1 -1
- package/dist/components/ModelPrimitive.js +4 -6
- package/dist/components/ObjectNode.js +1 -1
- package/dist/components/ParticlesGenerator.d.ts +3 -1
- package/dist/components/ParticlesGenerator.d.ts.map +1 -1
- package/dist/components/ParticlesGenerator.js +2 -2
- package/dist/components/ProjectLoader.js +1 -1
- package/dist/components/SceneBuilder.d.ts.map +1 -1
- package/dist/components/SceneBuilder.js +5 -1
- package/dist/components/candle/Candle.d.ts +14 -0
- package/dist/components/candle/Candle.d.ts.map +1 -0
- package/dist/components/candle/Candle.js +249 -0
- package/dist/components/candle/candleGeometry.d.ts +23 -0
- package/dist/components/candle/candleGeometry.d.ts.map +1 -0
- package/dist/components/candle/candleGeometry.js +73 -0
- package/dist/components/candle/candleMaterials.d.ts +24 -0
- package/dist/components/candle/candleMaterials.d.ts.map +1 -0
- package/dist/components/candle/candleMaterials.js +433 -0
- package/dist/components/candle/flameMotion.d.ts +64 -0
- package/dist/components/candle/flameMotion.d.ts.map +1 -0
- package/dist/components/candle/flameMotion.js +138 -0
- package/dist/components/candle/index.d.ts +9 -0
- package/dist/components/candle/index.d.ts.map +1 -0
- package/dist/components/candle/index.js +6 -0
- package/dist/components/fluorescentTube/FluorescentTube.d.ts +14 -0
- package/dist/components/fluorescentTube/FluorescentTube.d.ts.map +1 -0
- package/dist/components/fluorescentTube/FluorescentTube.js +262 -0
- package/dist/components/fluorescentTube/index.d.ts +5 -0
- package/dist/components/fluorescentTube/index.d.ts.map +1 -0
- package/dist/components/fluorescentTube/index.js +3 -0
- package/dist/components/fluorescentTube/tubeMaterials.d.ts +34 -0
- package/dist/components/fluorescentTube/tubeMaterials.d.ts.map +1 -0
- package/dist/components/fluorescentTube/tubeMaterials.js +185 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -0
- package/dist/components/ledArray/LedArray.d.ts +14 -0
- package/dist/components/ledArray/LedArray.d.ts.map +1 -0
- package/dist/components/ledArray/LedArray.js +243 -0
- package/dist/components/ledArray/index.d.ts +7 -0
- package/dist/components/ledArray/index.d.ts.map +1 -0
- package/dist/components/ledArray/index.js +4 -0
- package/dist/components/ledArray/ledLayout.d.ts +22 -0
- package/dist/components/ledArray/ledLayout.d.ts.map +1 -0
- package/dist/components/ledArray/ledLayout.js +59 -0
- package/dist/components/ledArray/ledMaterials.d.ts +14 -0
- package/dist/components/ledArray/ledMaterials.d.ts.map +1 -0
- package/dist/components/ledArray/ledMaterials.js +151 -0
- package/dist/components/lightBulb/LightBulb.d.ts.map +1 -1
- package/dist/components/lightBulb/LightBulb.js +10 -13
- package/dist/components/particles/BioluminescentWakeParticles.d.ts +3 -1
- package/dist/components/particles/BioluminescentWakeParticles.d.ts.map +1 -1
- package/dist/components/particles/BioluminescentWakeParticles.js +7 -3
- package/dist/components/particles/BrownianDustParticles.d.ts +3 -1
- package/dist/components/particles/BrownianDustParticles.d.ts.map +1 -1
- package/dist/components/particles/BrownianDustParticles.js +7 -3
- package/dist/components/particles/ChladniSandParticles.d.ts +3 -1
- package/dist/components/particles/ChladniSandParticles.d.ts.map +1 -1
- package/dist/components/particles/ChladniSandParticles.js +7 -3
- package/dist/components/particles/ElectronOrbitalsParticles.d.ts +3 -1
- package/dist/components/particles/ElectronOrbitalsParticles.d.ts.map +1 -1
- package/dist/components/particles/ElectronOrbitalsParticles.js +7 -3
- package/dist/components/particles/FaradayPowderParticles.d.ts +3 -1
- package/dist/components/particles/FaradayPowderParticles.d.ts.map +1 -1
- package/dist/components/particles/FaradayPowderParticles.js +7 -3
- package/dist/components/particles/GrinderSparksParticles.d.ts +3 -1
- package/dist/components/particles/GrinderSparksParticles.d.ts.map +1 -1
- package/dist/components/particles/GrinderSparksParticles.js +7 -3
- package/dist/components/particles/NeonRainParticles.d.ts +3 -1
- package/dist/components/particles/NeonRainParticles.d.ts.map +1 -1
- package/dist/components/particles/NeonRainParticles.js +7 -3
- package/dist/components/particles/TidalStreamParticles.d.ts +3 -1
- package/dist/components/particles/TidalStreamParticles.d.ts.map +1 -1
- package/dist/components/particles/TidalStreamParticles.js +7 -3
- package/dist/components/spotBeam/SpotBeam.d.ts +14 -0
- package/dist/components/spotBeam/SpotBeam.d.ts.map +1 -0
- package/dist/components/spotBeam/SpotBeam.js +244 -0
- package/dist/components/spotBeam/beamPatterns.d.ts +11 -0
- package/dist/components/spotBeam/beamPatterns.d.ts.map +1 -0
- package/dist/components/spotBeam/beamPatterns.js +106 -0
- package/dist/components/spotBeam/index.d.ts +7 -0
- package/dist/components/spotBeam/index.d.ts.map +1 -0
- package/dist/components/spotBeam/index.js +4 -0
- package/dist/components/spotBeam/spotBeamMaterials.d.ts +16 -0
- package/dist/components/spotBeam/spotBeamMaterials.d.ts.map +1 -0
- package/dist/components/spotBeam/spotBeamMaterials.js +307 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useLightConfig.d.ts +25 -0
- package/dist/hooks/useLightConfig.d.ts.map +1 -0
- package/dist/hooks/useLightConfig.js +125 -0
- package/dist/hooks/useLightLive.d.ts.map +1 -1
- package/dist/hooks/useLightLive.js +12 -2
- package/dist/hooks/useShadowApplication.d.ts +4 -4
- package/dist/hooks/useShadowApplication.d.ts.map +1 -1
- package/dist/hooks/useShadowApplication.js +25 -10
- package/package.json +2 -2
|
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { useFrame } from '@react-three/fiber';
|
|
5
5
|
import { GENERATIVE_PARTICLES_ANIMATABLE } from '@3driseai/3drise-core';
|
|
6
|
-
import { applyConfigKeyframeAnimations } from '../../utils/configAnimations.js';
|
|
6
|
+
import { applyConfigKeyframeAnimations, applyConfigMouseMove } from '../../utils/configAnimations.js';
|
|
7
7
|
import { createPointerUniforms, stepPointerField, usePointerField } from '../../utils/pointerField.js';
|
|
8
8
|
import { createTrailUniforms, applyTrailConfig, createCpuTrail } from '../../utils/particleTrails.js';
|
|
9
9
|
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
@@ -737,7 +737,7 @@ const DEFAULTS = {
|
|
|
737
737
|
const ANIMATABLE = new Set(GENERATIVE_PARTICLES_ANIMATABLE.brownianDust.map((p) => p.value));
|
|
738
738
|
/** Knobs that rebuild the simulation: they change on commit, never through the live path. */
|
|
739
739
|
const STRUCTURAL = ['particleCount'];
|
|
740
|
-
const BrownianDustParticles = ({ id, size, config, animations, onObjectReady, onObjectDispose }) => {
|
|
740
|
+
const BrownianDustParticles = ({ id, size, config, animations, mouseMove, onObjectReady, onObjectDispose }) => {
|
|
741
741
|
const rootRef = useRef(null);
|
|
742
742
|
useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
|
|
743
743
|
const s = size > 0 ? size : 1;
|
|
@@ -769,8 +769,12 @@ const BrownianDustParticles = ({ id, size, config, animations, onObjectReady, on
|
|
|
769
769
|
const pointer = usePointerField();
|
|
770
770
|
useFrame((state, delta) => {
|
|
771
771
|
instance.update(Math.min(delta, 0.1), state.camera, pointer);
|
|
772
|
-
|
|
772
|
+
// Keyframes first, pointer on top: a knob bound to both follows the cursor.
|
|
773
|
+
const pointerBound = !!mouseMove?.enabled && !!mouseMove.properties?.length;
|
|
774
|
+
if (keyframes?.length || pointerBound) {
|
|
773
775
|
applyConfigKeyframeAnimations(keyframes, animFrom.current, cfg, animOverride.current, performance.now() - animStart.current, ANIMATABLE);
|
|
776
|
+
if (pointerBound)
|
|
777
|
+
applyConfigMouseMove(mouseMove.properties, cfg, animOverride.current, state.pointer, ANIMATABLE);
|
|
774
778
|
push(live.current);
|
|
775
779
|
}
|
|
776
780
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ObjectAnimations, ChladniSandParticlesConfig } from '@3driseai/3drise-core';
|
|
2
|
+
import type { ObjectAnimations, ChladniSandParticlesConfig, MouseMoveDomain } from '@3driseai/3drise-core';
|
|
3
3
|
import type { ObjectReadyProps } from '../../types/objectReady';
|
|
4
4
|
export interface ChladniSandParticlesProps extends ObjectReadyProps {
|
|
5
|
+
/** Pointer bindings for this object's config knobs (mouseMove.particles). */
|
|
6
|
+
mouseMove?: MouseMoveDomain;
|
|
5
7
|
/** Scene object id: the controller finds the live-edit handle by it. */
|
|
6
8
|
id?: string;
|
|
7
9
|
/** World size of the object (units); the object's transform scales it further. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChladniSandParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/ChladniSandParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"ChladniSandParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/ChladniSandParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAM3G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IAC/D,6EAA6E;IAC7E,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,0BAA0B,CAAC;IACnC,sFAAsF;IACtF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAsvBD,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAqD7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { useFrame } from '@react-three/fiber';
|
|
5
5
|
import { GENERATIVE_PARTICLES_ANIMATABLE } from '@3driseai/3drise-core';
|
|
6
|
-
import { applyConfigKeyframeAnimations } from '../../utils/configAnimations.js';
|
|
6
|
+
import { applyConfigKeyframeAnimations, applyConfigMouseMove } from '../../utils/configAnimations.js';
|
|
7
7
|
import { createPointerUniforms, stepPointerField, usePointerField } from '../../utils/pointerField.js';
|
|
8
8
|
import { createTrailUniforms, applyTrailConfig, createCpuTrail } from '../../utils/particleTrails.js';
|
|
9
9
|
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
@@ -870,7 +870,7 @@ const DEFAULTS = {
|
|
|
870
870
|
const STRUCTURAL = ['particleCount', 'seed', 'quality'];
|
|
871
871
|
/** Knobs an animation (or mouse move) may drive per frame — generated from applyConfig. */
|
|
872
872
|
const ANIMATABLE = new Set(GENERATIVE_PARTICLES_ANIMATABLE.chladniSand.map((p) => p.value));
|
|
873
|
-
const ChladniSandParticles = ({ id, size, config, animations, onObjectReady, onObjectDispose }) => {
|
|
873
|
+
const ChladniSandParticles = ({ id, size, config, animations, mouseMove, onObjectReady, onObjectDispose }) => {
|
|
874
874
|
const rootRef = useRef(null);
|
|
875
875
|
useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
|
|
876
876
|
const s = size > 0 ? size : 1;
|
|
@@ -902,8 +902,12 @@ const ChladniSandParticles = ({ id, size, config, animations, onObjectReady, onO
|
|
|
902
902
|
const pointer = usePointerField();
|
|
903
903
|
useFrame((state, delta) => {
|
|
904
904
|
instance.update(Math.min(delta, 0.1), state.camera, pointer);
|
|
905
|
-
|
|
905
|
+
// Keyframes first, pointer on top: a knob bound to both follows the cursor.
|
|
906
|
+
const pointerBound = !!mouseMove?.enabled && !!mouseMove.properties?.length;
|
|
907
|
+
if (keyframes?.length || pointerBound) {
|
|
906
908
|
applyConfigKeyframeAnimations(keyframes, animFrom.current, cfg, animOverride.current, performance.now() - animStart.current, ANIMATABLE);
|
|
909
|
+
if (pointerBound)
|
|
910
|
+
applyConfigMouseMove(mouseMove.properties, cfg, animOverride.current, state.pointer, ANIMATABLE);
|
|
907
911
|
push(live.current);
|
|
908
912
|
}
|
|
909
913
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ObjectAnimations, ElectronOrbitalsParticlesConfig } from '@3driseai/3drise-core';
|
|
2
|
+
import type { ObjectAnimations, ElectronOrbitalsParticlesConfig, MouseMoveDomain } from '@3driseai/3drise-core';
|
|
3
3
|
import type { ObjectReadyProps } from '../../types/objectReady';
|
|
4
4
|
export interface ElectronOrbitalsParticlesProps extends ObjectReadyProps {
|
|
5
|
+
/** Pointer bindings for this object's config knobs (mouseMove.particles). */
|
|
6
|
+
mouseMove?: MouseMoveDomain;
|
|
5
7
|
/** Scene object id: the controller finds the live-edit handle by it. */
|
|
6
8
|
id?: string;
|
|
7
9
|
/** World size of the object (units); the object's transform scales it further. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ElectronOrbitalsParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/ElectronOrbitalsParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"ElectronOrbitalsParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/ElectronOrbitalsParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,+BAA+B,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAMhH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,8BAA+B,SAAQ,gBAAgB;IACpE,6EAA6E;IAC7E,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,+BAA+B,CAAC;IACxC,sFAAsF;IACtF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AA0xBD,QAAA,MAAM,yBAAyB,EAAE,KAAK,CAAC,EAAE,CAAC,8BAA8B,CAqDvE,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { useFrame } from '@react-three/fiber';
|
|
5
5
|
import { GENERATIVE_PARTICLES_ANIMATABLE } from '@3driseai/3drise-core';
|
|
6
|
-
import { applyConfigKeyframeAnimations } from '../../utils/configAnimations.js';
|
|
6
|
+
import { applyConfigKeyframeAnimations, applyConfigMouseMove } from '../../utils/configAnimations.js';
|
|
7
7
|
import { createPointerUniforms, stepPointerField, usePointerField } from '../../utils/pointerField.js';
|
|
8
8
|
import { createTrailUniforms, applyTrailConfig, createCpuTrail } from '../../utils/particleTrails.js';
|
|
9
9
|
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
@@ -924,7 +924,7 @@ const DEFAULTS = {
|
|
|
924
924
|
const STRUCTURAL = ['particleCount', 'stateA', 'stateB', 'seed', 'quality'];
|
|
925
925
|
/** Knobs an animation (or mouse move) may drive per frame — generated from applyConfig. */
|
|
926
926
|
const ANIMATABLE = new Set(GENERATIVE_PARTICLES_ANIMATABLE.electronOrbitals.map((p) => p.value));
|
|
927
|
-
const ElectronOrbitalsParticles = ({ id, size, config, animations, onObjectReady, onObjectDispose }) => {
|
|
927
|
+
const ElectronOrbitalsParticles = ({ id, size, config, animations, mouseMove, onObjectReady, onObjectDispose }) => {
|
|
928
928
|
const rootRef = useRef(null);
|
|
929
929
|
useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
|
|
930
930
|
const s = size > 0 ? size : 1;
|
|
@@ -956,8 +956,12 @@ const ElectronOrbitalsParticles = ({ id, size, config, animations, onObjectReady
|
|
|
956
956
|
const pointer = usePointerField();
|
|
957
957
|
useFrame((state, delta) => {
|
|
958
958
|
instance.update(Math.min(delta, 0.1), state.camera, pointer);
|
|
959
|
-
|
|
959
|
+
// Keyframes first, pointer on top: a knob bound to both follows the cursor.
|
|
960
|
+
const pointerBound = !!mouseMove?.enabled && !!mouseMove.properties?.length;
|
|
961
|
+
if (keyframes?.length || pointerBound) {
|
|
960
962
|
applyConfigKeyframeAnimations(keyframes, animFrom.current, cfg, animOverride.current, performance.now() - animStart.current, ANIMATABLE);
|
|
963
|
+
if (pointerBound)
|
|
964
|
+
applyConfigMouseMove(mouseMove.properties, cfg, animOverride.current, state.pointer, ANIMATABLE);
|
|
961
965
|
push(live.current);
|
|
962
966
|
}
|
|
963
967
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ObjectAnimations, FaradayPowderParticlesConfig } from '@3driseai/3drise-core';
|
|
2
|
+
import type { ObjectAnimations, FaradayPowderParticlesConfig, MouseMoveDomain } from '@3driseai/3drise-core';
|
|
3
3
|
import type { ObjectReadyProps } from '../../types/objectReady';
|
|
4
4
|
export interface FaradayPowderParticlesProps extends ObjectReadyProps {
|
|
5
|
+
/** Pointer bindings for this object's config knobs (mouseMove.particles). */
|
|
6
|
+
mouseMove?: MouseMoveDomain;
|
|
5
7
|
/** Scene object id: the controller finds the live-edit handle by it. */
|
|
6
8
|
id?: string;
|
|
7
9
|
/** World size of the object (units); the object's transform scales it further. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FaradayPowderParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/FaradayPowderParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"FaradayPowderParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/FaradayPowderParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAM7G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,2BAA4B,SAAQ,gBAAgB;IACjE,6EAA6E;IAC7E,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,4BAA4B,CAAC;IACrC,sFAAsF;IACtF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAypBD,QAAA,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CAqDjE,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { useFrame } from '@react-three/fiber';
|
|
5
5
|
import { GENERATIVE_PARTICLES_ANIMATABLE } from '@3driseai/3drise-core';
|
|
6
|
-
import { applyConfigKeyframeAnimations } from '../../utils/configAnimations.js';
|
|
6
|
+
import { applyConfigKeyframeAnimations, applyConfigMouseMove } from '../../utils/configAnimations.js';
|
|
7
7
|
import { createPointerUniforms, stepPointerField, usePointerField } from '../../utils/pointerField.js';
|
|
8
8
|
import { createTrailUniforms, applyTrailConfig, createCpuTrail } from '../../utils/particleTrails.js';
|
|
9
9
|
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
@@ -741,7 +741,7 @@ const DEFAULTS = {
|
|
|
741
741
|
const STRUCTURAL = ['particleCount', 'seed', 'quality'];
|
|
742
742
|
/** Knobs an animation (or mouse move) may drive per frame — generated from applyConfig. */
|
|
743
743
|
const ANIMATABLE = new Set(GENERATIVE_PARTICLES_ANIMATABLE.faradayPowder.map((p) => p.value));
|
|
744
|
-
const FaradayPowderParticles = ({ id, size, config, animations, onObjectReady, onObjectDispose }) => {
|
|
744
|
+
const FaradayPowderParticles = ({ id, size, config, animations, mouseMove, onObjectReady, onObjectDispose }) => {
|
|
745
745
|
const rootRef = useRef(null);
|
|
746
746
|
useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
|
|
747
747
|
const s = size > 0 ? size : 1;
|
|
@@ -773,8 +773,12 @@ const FaradayPowderParticles = ({ id, size, config, animations, onObjectReady, o
|
|
|
773
773
|
const pointer = usePointerField();
|
|
774
774
|
useFrame((state, delta) => {
|
|
775
775
|
instance.update(Math.min(delta, 0.1), state.camera, pointer);
|
|
776
|
-
|
|
776
|
+
// Keyframes first, pointer on top: a knob bound to both follows the cursor.
|
|
777
|
+
const pointerBound = !!mouseMove?.enabled && !!mouseMove.properties?.length;
|
|
778
|
+
if (keyframes?.length || pointerBound) {
|
|
777
779
|
applyConfigKeyframeAnimations(keyframes, animFrom.current, cfg, animOverride.current, performance.now() - animStart.current, ANIMATABLE);
|
|
780
|
+
if (pointerBound)
|
|
781
|
+
applyConfigMouseMove(mouseMove.properties, cfg, animOverride.current, state.pointer, ANIMATABLE);
|
|
778
782
|
push(live.current);
|
|
779
783
|
}
|
|
780
784
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ObjectAnimations, GrinderSparksParticlesConfig } from '@3driseai/3drise-core';
|
|
2
|
+
import type { ObjectAnimations, GrinderSparksParticlesConfig, MouseMoveDomain } from '@3driseai/3drise-core';
|
|
3
3
|
import type { ObjectReadyProps } from '../../types/objectReady';
|
|
4
4
|
export interface GrinderSparksParticlesProps extends ObjectReadyProps {
|
|
5
|
+
/** Pointer bindings for this object's config knobs (mouseMove.particles). */
|
|
6
|
+
mouseMove?: MouseMoveDomain;
|
|
5
7
|
/** Scene object id: the controller finds the live-edit handle by it. */
|
|
6
8
|
id?: string;
|
|
7
9
|
/** World size of the object (units); the object's transform scales it further. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrinderSparksParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/GrinderSparksParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"GrinderSparksParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/GrinderSparksParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAM7G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,2BAA4B,SAAQ,gBAAgB;IACjE,6EAA6E;IAC7E,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,4BAA4B,CAAC;IACrC,sFAAsF;IACtF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AA8pBD,QAAA,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CAqDjE,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { useFrame } from '@react-three/fiber';
|
|
5
5
|
import { GENERATIVE_PARTICLES_ANIMATABLE } from '@3driseai/3drise-core';
|
|
6
|
-
import { applyConfigKeyframeAnimations } from '../../utils/configAnimations.js';
|
|
6
|
+
import { applyConfigKeyframeAnimations, applyConfigMouseMove } from '../../utils/configAnimations.js';
|
|
7
7
|
import { createPointerUniforms, stepPointerField, usePointerField } from '../../utils/pointerField.js';
|
|
8
8
|
import { createTrailUniforms, applyTrailConfig, createCpuTrail } from '../../utils/particleTrails.js';
|
|
9
9
|
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
@@ -726,7 +726,7 @@ const DEFAULTS = {
|
|
|
726
726
|
const STRUCTURAL = ['particleCount', 'seed', 'quality'];
|
|
727
727
|
/** Knobs an animation (or mouse move) may drive per frame — generated from applyConfig. */
|
|
728
728
|
const ANIMATABLE = new Set(GENERATIVE_PARTICLES_ANIMATABLE.grinderSparks.map((p) => p.value));
|
|
729
|
-
const GrinderSparksParticles = ({ id, size, config, animations, onObjectReady, onObjectDispose }) => {
|
|
729
|
+
const GrinderSparksParticles = ({ id, size, config, animations, mouseMove, onObjectReady, onObjectDispose }) => {
|
|
730
730
|
const rootRef = useRef(null);
|
|
731
731
|
useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
|
|
732
732
|
const s = size > 0 ? size : 1;
|
|
@@ -758,8 +758,12 @@ const GrinderSparksParticles = ({ id, size, config, animations, onObjectReady, o
|
|
|
758
758
|
const pointer = usePointerField();
|
|
759
759
|
useFrame((state, delta) => {
|
|
760
760
|
instance.update(Math.min(delta, 0.1), state.camera, pointer);
|
|
761
|
-
|
|
761
|
+
// Keyframes first, pointer on top: a knob bound to both follows the cursor.
|
|
762
|
+
const pointerBound = !!mouseMove?.enabled && !!mouseMove.properties?.length;
|
|
763
|
+
if (keyframes?.length || pointerBound) {
|
|
762
764
|
applyConfigKeyframeAnimations(keyframes, animFrom.current, cfg, animOverride.current, performance.now() - animStart.current, ANIMATABLE);
|
|
765
|
+
if (pointerBound)
|
|
766
|
+
applyConfigMouseMove(mouseMove.properties, cfg, animOverride.current, state.pointer, ANIMATABLE);
|
|
763
767
|
push(live.current);
|
|
764
768
|
}
|
|
765
769
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ObjectAnimations, NeonRainParticlesConfig } from '@3driseai/3drise-core';
|
|
2
|
+
import type { ObjectAnimations, NeonRainParticlesConfig, MouseMoveDomain } from '@3driseai/3drise-core';
|
|
3
3
|
import type { ObjectReadyProps } from '../../types/objectReady';
|
|
4
4
|
export interface NeonRainParticlesProps extends ObjectReadyProps {
|
|
5
|
+
/** Pointer bindings for this object's config knobs (mouseMove.particles). */
|
|
6
|
+
mouseMove?: MouseMoveDomain;
|
|
5
7
|
/** Scene object id: the controller finds the live-edit handle by it. */
|
|
6
8
|
id?: string;
|
|
7
9
|
/** World size of the object (units); the object's transform scales it further. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NeonRainParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/NeonRainParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"NeonRainParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/NeonRainParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAMxG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC5D,6EAA6E;IAC7E,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,uBAAuB,CAAC;IAChC,sFAAsF;IACtF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAinBD,QAAA,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAqDvD,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { useFrame } from '@react-three/fiber';
|
|
5
5
|
import { GENERATIVE_PARTICLES_ANIMATABLE } from '@3driseai/3drise-core';
|
|
6
|
-
import { applyConfigKeyframeAnimations } from '../../utils/configAnimations.js';
|
|
6
|
+
import { applyConfigKeyframeAnimations, applyConfigMouseMove } from '../../utils/configAnimations.js';
|
|
7
7
|
import { createPointerUniforms, stepPointerField, usePointerField } from '../../utils/pointerField.js';
|
|
8
8
|
import { createTrailUniforms, applyTrailConfig, createCpuTrail } from '../../utils/particleTrails.js';
|
|
9
9
|
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
@@ -674,7 +674,7 @@ const DEFAULTS = {
|
|
|
674
674
|
const STRUCTURAL = ['particleCount', 'seed', 'quality'];
|
|
675
675
|
/** Knobs an animation (or mouse move) may drive per frame — generated from applyConfig. */
|
|
676
676
|
const ANIMATABLE = new Set(GENERATIVE_PARTICLES_ANIMATABLE.neonRain.map((p) => p.value));
|
|
677
|
-
const NeonRainParticles = ({ id, size, config, animations, onObjectReady, onObjectDispose }) => {
|
|
677
|
+
const NeonRainParticles = ({ id, size, config, animations, mouseMove, onObjectReady, onObjectDispose }) => {
|
|
678
678
|
const rootRef = useRef(null);
|
|
679
679
|
useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
|
|
680
680
|
const s = size > 0 ? size : 1;
|
|
@@ -706,8 +706,12 @@ const NeonRainParticles = ({ id, size, config, animations, onObjectReady, onObje
|
|
|
706
706
|
const pointer = usePointerField();
|
|
707
707
|
useFrame((state, delta) => {
|
|
708
708
|
instance.update(Math.min(delta, 0.1), state.camera, pointer);
|
|
709
|
-
|
|
709
|
+
// Keyframes first, pointer on top: a knob bound to both follows the cursor.
|
|
710
|
+
const pointerBound = !!mouseMove?.enabled && !!mouseMove.properties?.length;
|
|
711
|
+
if (keyframes?.length || pointerBound) {
|
|
710
712
|
applyConfigKeyframeAnimations(keyframes, animFrom.current, cfg, animOverride.current, performance.now() - animStart.current, ANIMATABLE);
|
|
713
|
+
if (pointerBound)
|
|
714
|
+
applyConfigMouseMove(mouseMove.properties, cfg, animOverride.current, state.pointer, ANIMATABLE);
|
|
711
715
|
push(live.current);
|
|
712
716
|
}
|
|
713
717
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ObjectAnimations, TidalStreamParticlesConfig } from '@3driseai/3drise-core';
|
|
2
|
+
import type { ObjectAnimations, TidalStreamParticlesConfig, MouseMoveDomain } from '@3driseai/3drise-core';
|
|
3
3
|
import type { ObjectReadyProps } from '../../types/objectReady';
|
|
4
4
|
export interface TidalStreamParticlesProps extends ObjectReadyProps {
|
|
5
|
+
/** Pointer bindings for this object's config knobs (mouseMove.particles). */
|
|
6
|
+
mouseMove?: MouseMoveDomain;
|
|
5
7
|
/** Scene object id: the controller finds the live-edit handle by it. */
|
|
6
8
|
id?: string;
|
|
7
9
|
/** World size of the object (units); the object's transform scales it further. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TidalStreamParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/TidalStreamParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"TidalStreamParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/TidalStreamParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAM3G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IAC/D,6EAA6E;IAC7E,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,0BAA0B,CAAC;IACnC,sFAAsF;IACtF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAmtBD,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAqD7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { useFrame } from '@react-three/fiber';
|
|
5
5
|
import { GENERATIVE_PARTICLES_ANIMATABLE } from '@3driseai/3drise-core';
|
|
6
|
-
import { applyConfigKeyframeAnimations } from '../../utils/configAnimations.js';
|
|
6
|
+
import { applyConfigKeyframeAnimations, applyConfigMouseMove } from '../../utils/configAnimations.js';
|
|
7
7
|
import { createPointerUniforms, stepPointerField, usePointerField } from '../../utils/pointerField.js';
|
|
8
8
|
import { createTrailUniforms, applyTrailConfig, createCpuTrail } from '../../utils/particleTrails.js';
|
|
9
9
|
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
@@ -768,7 +768,7 @@ const DEFAULTS = {
|
|
|
768
768
|
const ANIMATABLE = new Set(GENERATIVE_PARTICLES_ANIMATABLE.tidalStream.map((p) => p.value));
|
|
769
769
|
/** Knobs that rebuild the simulation: they change on commit, never through the live path. */
|
|
770
770
|
const STRUCTURAL = ['particleCount', 'satelliteCount', 'orbitEccentricity', 'streamAge', 'discStars'];
|
|
771
|
-
const TidalStreamParticles = ({ id, size, config, animations, onObjectReady, onObjectDispose }) => {
|
|
771
|
+
const TidalStreamParticles = ({ id, size, config, animations, mouseMove, onObjectReady, onObjectDispose }) => {
|
|
772
772
|
const rootRef = useRef(null);
|
|
773
773
|
useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
|
|
774
774
|
const s = size > 0 ? size : 1;
|
|
@@ -800,8 +800,12 @@ const TidalStreamParticles = ({ id, size, config, animations, onObjectReady, onO
|
|
|
800
800
|
const pointer = usePointerField();
|
|
801
801
|
useFrame((state, delta) => {
|
|
802
802
|
instance.update(Math.min(delta, 0.1), state.camera, pointer);
|
|
803
|
-
|
|
803
|
+
// Keyframes first, pointer on top: a knob bound to both follows the cursor.
|
|
804
|
+
const pointerBound = !!mouseMove?.enabled && !!mouseMove.properties?.length;
|
|
805
|
+
if (keyframes?.length || pointerBound) {
|
|
804
806
|
applyConfigKeyframeAnimations(keyframes, animFrom.current, cfg, animOverride.current, performance.now() - animStart.current, ANIMATABLE);
|
|
807
|
+
if (pointerBound)
|
|
808
|
+
applyConfigMouseMove(mouseMove.properties, cfg, animOverride.current, state.pointer, ANIMATABLE);
|
|
805
809
|
push(live.current);
|
|
806
810
|
}
|
|
807
811
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { AnimationOptions, CreatedObjectSettings, MouseMoveDomain } from '@3driseai/3drise-core';
|
|
3
|
+
import type { ObjectReadyProps } from '../../types/objectReady';
|
|
4
|
+
export type SpotBeamProps = ObjectReadyProps & {
|
|
5
|
+
/** Scene object id: the light controller finds the live-edit handle by it. */
|
|
6
|
+
id?: string;
|
|
7
|
+
config: Record<string, any>;
|
|
8
|
+
animations?: AnimationOptions[];
|
|
9
|
+
mouseMove?: MouseMoveDomain;
|
|
10
|
+
settings: CreatedObjectSettings | Record<string, any>;
|
|
11
|
+
};
|
|
12
|
+
declare const SpotBeam: React.FC<SpotBeamProps>;
|
|
13
|
+
export default SpotBeam;
|
|
14
|
+
//# sourceMappingURL=SpotBeam.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpotBeam.d.ts","sourceRoot":"","sources":["../../../src/components/spotBeam/SpotBeam.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAqC,MAAM,OAAO,CAAC;AAK1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAItG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAOhE,MAAM,MAAM,aAAa,GAAG,gBAAgB,GAAG;IAC3C,8EAA8E;IAC9E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAChC,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACzD,CAAC;AAuBF,QAAA,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CAwOrC,CAAC;AACF,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// SpotBeam — a spot light you can see: pattern, beam and pool
|
|
4
|
+
// =============================================================================
|
|
5
|
+
//
|
|
6
|
+
// Rendered by LightObject for `config.type === 'spotBeam'`. This is a light, not
|
|
7
|
+
// a lamp: the geometry is a small emitter you can grab in the editor (and can
|
|
8
|
+
// switch off entirely), because the object is meant to be dropped inside whatever
|
|
9
|
+
// fixture the scene already has.
|
|
10
|
+
//
|
|
11
|
+
// The work is in the optics:
|
|
12
|
+
// - a procedural mask on SpotLight.map shapes the pool (slot, bars, blinds,
|
|
13
|
+
// window, leaves) — see beamPatterns.ts;
|
|
14
|
+
// - the beam samples that same mask, so the haze in the air and the light on
|
|
15
|
+
// the floor are the same shape;
|
|
16
|
+
// - the beam is nested cone shells in one InstancedMesh, dense on the axis.
|
|
17
|
+
//
|
|
18
|
+
// The object's origin is the light. It aims at `targetPosition` in world space,
|
|
19
|
+
// the same contract the plain spot light uses.
|
|
20
|
+
//
|
|
21
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
22
|
+
import { useFrame, useThree } from '@react-three/fiber';
|
|
23
|
+
import { CircleGeometry, Color, ConeGeometry, InstancedBufferAttribute, Matrix4, Object3D, SphereGeometry, Vector3 } from 'three';
|
|
24
|
+
import { SELF_OWNED_MATERIAL_KEY } from '@3driseai/3drise-core';
|
|
25
|
+
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
26
|
+
import { useLightShadow } from '../../hooks/useLightShadow.js';
|
|
27
|
+
import { useLightConfig } from '../../hooks/useLightConfig.js';
|
|
28
|
+
import { createBeamPattern } from './beamPatterns.js';
|
|
29
|
+
import { createSpotBeamMaterials, disposeSpotBeamMaterials } from './spotBeamMaterials.js';
|
|
30
|
+
import { kelvinToColor } from '../lightBulb/kelvin.js';
|
|
31
|
+
/** Cone shells: more is smoother haze, and each one is a draw. */
|
|
32
|
+
const LAYERS = 10;
|
|
33
|
+
const num = (v, fallback) => (typeof v === 'number' ? v : fallback);
|
|
34
|
+
const hash = (n) => {
|
|
35
|
+
const s = Math.sin(n * 127.1) * 43758.5453;
|
|
36
|
+
return s - Math.floor(s);
|
|
37
|
+
};
|
|
38
|
+
const noise1 = (x) => {
|
|
39
|
+
const i = Math.floor(x), f = x - i, u = f * f * (3 - 2 * f);
|
|
40
|
+
return hash(i) * (1 - u) + hash(i + 1) * u;
|
|
41
|
+
};
|
|
42
|
+
const COLD = new Color(0.06, 0.02, 0.0);
|
|
43
|
+
const EMBER = new Color(1.0, 0.4, 0.08);
|
|
44
|
+
const WHITE = new Color(1, 1, 1);
|
|
45
|
+
const scratchVec = new Vector3();
|
|
46
|
+
const scratchScale = new Vector3();
|
|
47
|
+
const scratchMatrix = new Matrix4();
|
|
48
|
+
const SpotBeam = ({ id, config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
|
|
49
|
+
const scene = useThree((s) => s.scene);
|
|
50
|
+
const groupRef = useRef(null);
|
|
51
|
+
const lightRef = useRef(null);
|
|
52
|
+
const targetRef = useRef(new Object3D());
|
|
53
|
+
const beamRef = useRef(null);
|
|
54
|
+
const soloRef = useRef(null);
|
|
55
|
+
useObjectReady(groupRef, settings, onObjectReady, onObjectDispose);
|
|
56
|
+
useLightShadow(lightRef, config.shadow);
|
|
57
|
+
const { merged } = useLightConfig(groupRef, lightRef, id, config, animations, mouseMove);
|
|
58
|
+
// --- geometry: a unit cone for the beam, a small emitter ---
|
|
59
|
+
const geometry = useMemo(() => {
|
|
60
|
+
// apex at the origin, opening along +Y, rim radius == height so the
|
|
61
|
+
// shader can read x/y as the projector's coordinate
|
|
62
|
+
const cone = new ConeGeometry(1, 1, 56, 18, true);
|
|
63
|
+
cone.rotateX(Math.PI);
|
|
64
|
+
cone.translate(0, 0.5, 0);
|
|
65
|
+
const lens = new CircleGeometry(0.16, 40);
|
|
66
|
+
lens.rotateX(-Math.PI / 2);
|
|
67
|
+
const dot = new SphereGeometry(0.05, 20, 12);
|
|
68
|
+
return { cone, lens, dot };
|
|
69
|
+
}, []);
|
|
70
|
+
useEffect(() => () => {
|
|
71
|
+
geometry.cone.dispose();
|
|
72
|
+
geometry.lens.dispose();
|
|
73
|
+
geometry.dot.dispose();
|
|
74
|
+
}, [geometry]);
|
|
75
|
+
const mats = useMemo(() => createSpotBeamMaterials(), []);
|
|
76
|
+
useEffect(() => () => disposeSpotBeamMaterials(mats), [mats]);
|
|
77
|
+
// --- the projected mask: shapes the pool and the haze together ---
|
|
78
|
+
const pattern = (config.pattern ?? 'round');
|
|
79
|
+
const patternSoftness = Math.min(Math.max(num(config.patternSoftness, 0.25), 0), 1);
|
|
80
|
+
const patternRotation = num(config.patternRotation, 0);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const texture = createBeamPattern(pattern, patternSoftness, patternRotation);
|
|
83
|
+
mats.layered.uniforms.uPattern.value = texture;
|
|
84
|
+
mats.volumetric.uniforms.uPattern.value = texture;
|
|
85
|
+
if (lightRef.current)
|
|
86
|
+
lightRef.current.map = texture;
|
|
87
|
+
return () => {
|
|
88
|
+
if (lightRef.current?.map === texture)
|
|
89
|
+
lightRef.current.map = null;
|
|
90
|
+
if (mats.layered.uniforms.uPattern.value === texture)
|
|
91
|
+
mats.layered.uniforms.uPattern.value = null;
|
|
92
|
+
if (mats.volumetric.uniforms.uPattern.value === texture)
|
|
93
|
+
mats.volumetric.uniforms.uPattern.value = null;
|
|
94
|
+
texture.dispose();
|
|
95
|
+
};
|
|
96
|
+
}, [mats, pattern, patternSoftness, patternRotation]);
|
|
97
|
+
// One shell per layer, fraction 0 (axis) → 1 (rim).
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
const beam = beamRef.current;
|
|
100
|
+
if (!beam)
|
|
101
|
+
return;
|
|
102
|
+
const fracs = new Float32Array(LAYERS);
|
|
103
|
+
for (let i = 0; i < LAYERS; i++)
|
|
104
|
+
fracs[i] = (i + 1) / LAYERS;
|
|
105
|
+
beam.geometry.setAttribute('aFrac', new InstancedBufferAttribute(fracs, 1));
|
|
106
|
+
mats.layered.uniforms.uLayers.value = LAYERS;
|
|
107
|
+
}, [mats]);
|
|
108
|
+
const lightColor = useMemo(() => ((config.colorMode ?? 'temperature') === 'color'
|
|
109
|
+
? new Color(config.color || '#ffffff')
|
|
110
|
+
: kelvinToColor(config.temperature ?? 3600)), [config.colorMode, config.color, config.temperature]);
|
|
111
|
+
// targetPosition is world space, like the plain spot light's.
|
|
112
|
+
const [tx, ty, tz] = (config.targetPosition ?? [0, 0, 0]);
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
const light = lightRef.current;
|
|
115
|
+
if (!light)
|
|
116
|
+
return;
|
|
117
|
+
const target = targetRef.current;
|
|
118
|
+
target.position.set(tx, ty, tz);
|
|
119
|
+
target.updateMatrixWorld();
|
|
120
|
+
light.target = target;
|
|
121
|
+
scene.add(target);
|
|
122
|
+
return () => { scene.remove(target); };
|
|
123
|
+
}, [scene, tx, ty, tz]);
|
|
124
|
+
// The light owns its materials and its shadow flags.
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
const group = groupRef.current;
|
|
127
|
+
if (!group)
|
|
128
|
+
return;
|
|
129
|
+
group.userData[SELF_OWNED_MATERIAL_KEY] = true;
|
|
130
|
+
return () => { delete group.userData[SELF_OWNED_MATERIAL_KEY]; };
|
|
131
|
+
}, []);
|
|
132
|
+
// Anything the user puts in front of this light is usually centimetres away.
|
|
133
|
+
const shadowNear = config.shadow?.cameraNear;
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
const light = lightRef.current;
|
|
136
|
+
if (!light || shadowNear !== undefined)
|
|
137
|
+
return;
|
|
138
|
+
light.shadow.camera.near = 0.05;
|
|
139
|
+
light.shadow.camera.updateProjectionMatrix();
|
|
140
|
+
}, [shadowNear]);
|
|
141
|
+
const emitter = (config.emitter ?? 'lens');
|
|
142
|
+
// Which beam the user picked. Each mode is a different mesh, so this rebuilds
|
|
143
|
+
// rather than tweens: off, one shell, nested shells, or a raymarched volume.
|
|
144
|
+
const beamMode = (config.beamMode ?? 'soft');
|
|
145
|
+
const power = useRef({ level: (config.on ?? true) ? 1 : 0, base: config.intensity ?? 1, written: Number.NaN, seed: Math.random() * 1000 });
|
|
146
|
+
const scratch = useMemo(() => ({ heat: new Color(), glow: new Color() }), []);
|
|
147
|
+
useFrame(({ clock, camera }, delta) => {
|
|
148
|
+
const light = lightRef.current;
|
|
149
|
+
const group = groupRef.current;
|
|
150
|
+
if (!light || !group)
|
|
151
|
+
return;
|
|
152
|
+
const cfg = merged.current;
|
|
153
|
+
const p = power.current;
|
|
154
|
+
const on = cfg.on ?? true;
|
|
155
|
+
const warmup = Math.max(num(cfg.warmup, 0.3), 0);
|
|
156
|
+
const flicker = Math.min(Math.max(num(cfg.flicker, 0), 0), 1);
|
|
157
|
+
const beamAmount = Math.min(Math.max(num(cfg.beam, 1), 0), 3);
|
|
158
|
+
const haloAmount = Math.min(Math.max(num(cfg.halo, 1), 0), 3);
|
|
159
|
+
const baseIntensity = Math.max(num(config.intensity, 1), 1e-4);
|
|
160
|
+
if (light.intensity !== p.written)
|
|
161
|
+
p.base = light.intensity;
|
|
162
|
+
const target = on ? 1 : 0;
|
|
163
|
+
const tau = target > p.level ? warmup : warmup * 2.2;
|
|
164
|
+
p.level = tau <= 0 ? target : p.level + (target - p.level) * (1 - Math.exp(-Math.min(delta, 0.1) * 3 / tau));
|
|
165
|
+
if (Math.abs(target - p.level) < 1e-4)
|
|
166
|
+
p.level = target;
|
|
167
|
+
let flick = 1;
|
|
168
|
+
if (flicker > 0) {
|
|
169
|
+
const t = clock.elapsedTime + p.seed;
|
|
170
|
+
flick = 1 - flicker * (0.12 * noise1(t * 11) + 0.55 * Math.pow(noise1(t * 2.7), 4));
|
|
171
|
+
}
|
|
172
|
+
const output = Math.pow(p.level, 2.0) * flick;
|
|
173
|
+
light.intensity = p.base * output;
|
|
174
|
+
p.written = light.intensity;
|
|
175
|
+
const glowK = Math.min(output * (p.base / baseIntensity), 3);
|
|
176
|
+
const k = p.level * flick;
|
|
177
|
+
// Emitter: ember while it warms, then the light's own colour.
|
|
178
|
+
const heat = scratch.heat;
|
|
179
|
+
if (k < 0.5)
|
|
180
|
+
heat.copy(COLD).lerp(EMBER, k / 0.5);
|
|
181
|
+
else
|
|
182
|
+
heat.copy(EMBER).lerp(scratch.glow.copy(light.color).lerp(WHITE, 0.55), (k - 0.5) / 0.5);
|
|
183
|
+
mats.lens.color.copy(heat).multiplyScalar(0.05 + glowK * 2.2);
|
|
184
|
+
const glow = scratch.glow.copy(light.color);
|
|
185
|
+
mats.halo.uniforms.uColor.value.copy(glow);
|
|
186
|
+
mats.halo.uniforms.uStrength.value = haloAmount * glowK * 0.55;
|
|
187
|
+
const penumbra = num(cfg.penumbra, 0.3);
|
|
188
|
+
const noise = Math.min(Math.max(num(cfg.beamNoise, 0.35), 0), 1);
|
|
189
|
+
const active = beamMode === 'layered' ? mats.layered : beamMode === 'volumetric' ? mats.volumetric : mats.soft;
|
|
190
|
+
active.uniforms.uColor.value.copy(glow);
|
|
191
|
+
active.uniforms.uPenumbra.value = penumbra;
|
|
192
|
+
if (beamMode === 'layered') {
|
|
193
|
+
mats.layered.uniforms.uNoise.value = noise;
|
|
194
|
+
mats.layered.uniforms.uTime.value = clock.elapsedTime;
|
|
195
|
+
mats.layered.uniforms.uStrength.value = beamAmount * glowK * 0.45;
|
|
196
|
+
}
|
|
197
|
+
else if (beamMode === 'volumetric') {
|
|
198
|
+
mats.volumetric.uniforms.uNoise.value = noise;
|
|
199
|
+
mats.volumetric.uniforms.uTime.value = clock.elapsedTime;
|
|
200
|
+
mats.volumetric.uniforms.uSteps.value = num(cfg.beamSteps, 28);
|
|
201
|
+
mats.volumetric.uniforms.uStrength.value = beamAmount * glowK * 0.9;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
mats.soft.uniforms.uStrength.value = beamAmount * glowK * 0.5;
|
|
205
|
+
}
|
|
206
|
+
// Size and aim whichever beam mesh is mounted: as wide as the cone, as
|
|
207
|
+
// long as the throw, pointing down the light's own axis.
|
|
208
|
+
const mesh = beamMode === 'layered' ? beamRef.current : soloRef.current;
|
|
209
|
+
if (mesh) {
|
|
210
|
+
const reach = targetRef.current.position.distanceTo(group.getWorldPosition(scratchVec)) || 6;
|
|
211
|
+
const worldScale = group.getWorldScale(scratchScale).x || 1;
|
|
212
|
+
const length = Math.max((reach / worldScale) * num(cfg.beamLength, 1), 0.1);
|
|
213
|
+
const radius = Math.tan(Math.min(num(cfg.angle, Math.PI / 6), 1.45)) * length;
|
|
214
|
+
const visible = beamAmount > 0 && glowK > 0.01;
|
|
215
|
+
mesh.visible = visible;
|
|
216
|
+
group.worldToLocal(scratchVec.copy(targetRef.current.position));
|
|
217
|
+
mesh.quaternion.identity();
|
|
218
|
+
mesh.up.set(0, 0, 1);
|
|
219
|
+
mesh.lookAt(scratchVec);
|
|
220
|
+
mesh.rotateX(Math.PI / 2); // the cone opens along +Y, lookAt aims +Z
|
|
221
|
+
if (beamMode === 'layered') {
|
|
222
|
+
for (let i = 0; i < LAYERS; i++) {
|
|
223
|
+
scratchMatrix.makeScale(radius, length, radius);
|
|
224
|
+
mesh.setMatrixAt(i, scratchMatrix);
|
|
225
|
+
}
|
|
226
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
mesh.scale.set(radius, length, radius);
|
|
230
|
+
}
|
|
231
|
+
if (beamMode === 'volumetric' && visible) {
|
|
232
|
+
// The raymarch runs in the cone's own space, so the camera goes there too.
|
|
233
|
+
mesh.updateMatrixWorld();
|
|
234
|
+
mats.volumetric.uniforms.uCamera.value.copy(camera.position);
|
|
235
|
+
mesh.worldToLocal(mats.volumetric.uniforms.uCamera.value);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
return (_jsxs("group", { ref: groupRef, children: [_jsx("spotLight", { ref: lightRef,
|
|
240
|
+
// THREE.SpotLight's constructor parks itself at (0, 1, 0); left alone
|
|
241
|
+
// the light sits a unit away from the object it belongs to.
|
|
242
|
+
position: [0, 0, 0], color: lightColor, intensity: config.intensity ?? 1, angle: config.angle ?? Math.PI / 6, penumbra: config.penumbra ?? 0.3, decay: config.decay ?? 1, distance: config.distance ?? 0 }), emitter === 'lens' && _jsx("mesh", { geometry: geometry.lens, material: mats.lens, renderOrder: 2 }), emitter === 'dot' && _jsx("mesh", { geometry: geometry.dot, material: mats.lens, renderOrder: 2 }), beamMode === 'layered' && (_jsx("instancedMesh", { ref: beamRef, args: [geometry.cone, mats.layered, LAYERS], renderOrder: 3, frustumCulled: false })), (beamMode === 'soft' || beamMode === 'volumetric') && (_jsx("mesh", { ref: soloRef, geometry: geometry.cone, material: beamMode === 'soft' ? mats.soft : mats.volumetric, renderOrder: 3, frustumCulled: false })), _jsx("mesh", { material: mats.halo, renderOrder: 4, frustumCulled: false, children: _jsx("planeGeometry", { args: [2, 2] }) })] }));
|
|
243
|
+
};
|
|
244
|
+
export default SpotBeam;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Texture } from 'three';
|
|
2
|
+
export type BeamPattern = 'round' | 'slot' | 'bars' | 'blinds' | 'window' | 'leaves';
|
|
3
|
+
export declare const BEAM_PATTERNS: BeamPattern[];
|
|
4
|
+
/**
|
|
5
|
+
* Build the mask. `rotation` is baked in rather than applied as a uv transform:
|
|
6
|
+
* three samples a spot light's map through the light frustum and ignores the
|
|
7
|
+
* texture matrix, so rotating it any other way would turn the haze without
|
|
8
|
+
* turning the pool.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createBeamPattern(pattern: BeamPattern, softness: number, rotationDeg: number): Texture;
|
|
11
|
+
//# sourceMappingURL=beamPatterns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"beamPatterns.d.ts","sourceRoot":"","sources":["../../../src/components/spotBeam/beamPatterns.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAErC,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAErF,eAAO,MAAM,aAAa,EAAE,WAAW,EAA4D,CAAC;AA+DpG;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAyBtG"}
|