@3driseai/3drise-viewer 0.1.22 → 0.1.23
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 +20 -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/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 +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +3 -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 +9 -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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreatedObject.d.ts","sourceRoot":"","sources":["../../src/components/CreatedObject.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"CreatedObject.d.ts","sourceRoot":"","sources":["../../src/components/CreatedObject.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAWnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,UAAU,kBAAmB,SAAQ,gBAAgB;IACjD,aAAa,EAAE,qBAAqB,CAAC;IACrC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,qBAAqB,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;CACpH;AAGD,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAuF9C,CAAC;AAEH,eAAe,aAAa,CAAC"}
|
|
@@ -4,6 +4,7 @@ import { ModelMaterialPassContext } from "./ModelMaterialPassContext.js";
|
|
|
4
4
|
import { DEFAULT_EDGES_SETTINGS } from "@3driseai/3drise-core";
|
|
5
5
|
import { useMaterialApplication } from "../hooks/useMaterialApplication.js";
|
|
6
6
|
import { useShadowApplication } from "../hooks/useShadowApplication.js";
|
|
7
|
+
import { resolveShadowFlags } from '@3driseai/3drise-core';
|
|
7
8
|
import { useTransformControls } from "../hooks/useTransformControls.js";
|
|
8
9
|
import { RigidBody } from '@react-three/rapier';
|
|
9
10
|
import { cameraManager } from "../utils/CameraSingleton.js";
|
|
@@ -34,7 +35,11 @@ const CreatedObject = memo(({ createdObject, children, shouldApply3driseMaterial
|
|
|
34
35
|
// (useNodeOverrides in ModelPrimitive) re-apply on top of it.
|
|
35
36
|
const [materialPass, bumpMaterialPass] = useReducer((n) => n + 1, 0);
|
|
36
37
|
useMaterialApplication(meshRef, createdObject.materialSettings, shouldApply3driseMaterial, bumpMaterialPass);
|
|
37
|
-
|
|
38
|
+
// An object with no explicit flags takes its type's defaults (core's
|
|
39
|
+
// resolveShadowFlags): saved objects carry none, and reading them as false
|
|
40
|
+
// switched every shadow in every project off.
|
|
41
|
+
const shadowFlags = resolveShadowFlags(createdObject.type, createdObject.meshSettings);
|
|
42
|
+
useShadowApplication(meshRef, shadowFlags.castShadow, shadowFlags.receiveShadow);
|
|
38
43
|
const anchoredPosition = cameraManager.getAnchorPosition(createdObject.meshSettings.anchor?.anchor || 'center', 0, createdObject.meshSettings.anchor?.offset || { y: 0, x: 0, z: 0 }, createdObject.meshSettings.anchor?.padding || 0);
|
|
39
44
|
useEffect(() => {
|
|
40
45
|
if (meshRef.current) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LightObject.d.ts","sourceRoot":"","sources":["../../src/components/LightObject.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"LightObject.d.ts","sourceRoot":"","sources":["../../src/components/LightObject.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAK7D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAQnE,KAAK,gBAAgB,GAAG,gBAAgB,GAAG;IACvC,aAAa,EAAE,qBAAqB,CAAC;CACxC,CAAC;AA+CF,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA4B3C,CAAC;AACF,eAAe,WAAW,CAAC"}
|
|
@@ -3,17 +3,17 @@ import { useObjectReady } from '../hooks/useObjectReady.js';
|
|
|
3
3
|
import { useEffect, useRef } from 'react';
|
|
4
4
|
import { useThree } from '@react-three/fiber';
|
|
5
5
|
import { useLightShadow } from '../hooks/useLightShadow.js';
|
|
6
|
-
import {
|
|
7
|
-
import { useLightMouseMove } from '../hooks/useLightMouseMove.js';
|
|
8
|
-
import { useLightLive } from '../hooks/useLightLive.js';
|
|
6
|
+
import { useLightConfig } from '../hooks/useLightConfig.js';
|
|
9
7
|
import LightBulb from './lightBulb/LightBulb.js';
|
|
8
|
+
import SpotBeam from './spotBeam/SpotBeam.js';
|
|
9
|
+
import LedArray from './ledArray/LedArray.js';
|
|
10
|
+
import FluorescentTube from './fluorescentTube/FluorescentTube.js';
|
|
10
11
|
const DirectionalLightR3F = ({ config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
|
|
11
12
|
const lightRef = useRef(null);
|
|
12
13
|
useObjectReady(lightRef, settings, onObjectReady, onObjectDispose);
|
|
13
14
|
useLightShadow(lightRef, config.shadow);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
useLightLive(lightRef, lightRef, settings?.id, config);
|
|
15
|
+
// Keyframes, pointer bindings and live drags, merged onto the light.
|
|
16
|
+
useLightConfig(lightRef, lightRef, settings?.id, config, animations, mouseMove);
|
|
17
17
|
return (_jsx("directionalLight", { ref: lightRef, color: config.color || '#ffffff', intensity: config.intensity ?? 1 }));
|
|
18
18
|
};
|
|
19
19
|
const SpotLightR3F = ({ config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
|
|
@@ -40,18 +40,16 @@ const SpotLightR3F = ({ config, animations, mouseMove, settings, onObjectReady,
|
|
|
40
40
|
};
|
|
41
41
|
}, [scene, tx, ty, tz]);
|
|
42
42
|
useLightShadow(lightRef, config.shadow);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return (_jsx("spotLight", { ref: lightRef, color: config.color || '#ffffff', intensity: config.intensity ?? 1, angle: config.angle ?? Math.PI / 4, penumbra: config.penumbra ?? 0.1, decay: config.decay ?? 1, distance: config.distance ?? 0 }));
|
|
43
|
+
// Keyframes, pointer bindings and live drags, merged onto the light.
|
|
44
|
+
useLightConfig(lightRef, lightRef, settings?.id, config, animations, mouseMove);
|
|
45
|
+
return (_jsx("spotLight", { ref: lightRef, position: [0, 0, 0], color: config.color || '#ffffff', intensity: config.intensity ?? 1, angle: config.angle ?? Math.PI / 4, penumbra: config.penumbra ?? 0.1, decay: config.decay ?? 1, distance: config.distance ?? 0 }));
|
|
47
46
|
};
|
|
48
47
|
const PointLightR3F = ({ config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
|
|
49
48
|
const lightRef = useRef(null);
|
|
50
49
|
useObjectReady(lightRef, settings, onObjectReady, onObjectDispose);
|
|
51
50
|
useLightShadow(lightRef, config.shadow);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
useLightLive(lightRef, lightRef, settings?.id, config);
|
|
51
|
+
// Keyframes, pointer bindings and live drags, merged onto the light.
|
|
52
|
+
useLightConfig(lightRef, lightRef, settings?.id, config, animations, mouseMove);
|
|
55
53
|
return (_jsx("pointLight", { ref: lightRef, color: config.color || '#ffffff', intensity: config.intensity ?? 1, decay: config.decay ?? 1, distance: config.distance ?? 0 }));
|
|
56
54
|
};
|
|
57
55
|
const LightObject = ({ createdObject, onObjectReady, onObjectDispose }) => {
|
|
@@ -67,6 +65,15 @@ const LightObject = ({ createdObject, onObjectReady, onObjectDispose }) => {
|
|
|
67
65
|
return _jsx(SpotLightR3F, { config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
|
|
68
66
|
case 'point':
|
|
69
67
|
return _jsx(PointLightR3F, { config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
|
|
68
|
+
// An array of LED dies: instant on, PWM ripple, optional diffuser.
|
|
69
|
+
case 'ledArray':
|
|
70
|
+
return _jsx(LedArray, { id: createdObject.id, config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
|
|
71
|
+
// A bank of tubes lit by a rect-area light, with the ballast's behaviour.
|
|
72
|
+
case 'fluorescentTube':
|
|
73
|
+
return _jsx(FluorescentTube, { id: createdObject.id, config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
|
|
74
|
+
// A spot light you can see: procedural gobo + a beam in the air.
|
|
75
|
+
case 'spotBeam':
|
|
76
|
+
return _jsx(SpotBeam, { id: createdObject.id, config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
|
|
70
77
|
// A visible bulb: point light + glass/filament/base + self-drawn glow.
|
|
71
78
|
case 'lightBulb':
|
|
72
79
|
return _jsx(LightBulb, { id: createdObject.id, config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModelPrimitive.d.ts","sourceRoot":"","sources":["../../src/components/ModelPrimitive.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAMnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,UAAU,mBAAoB,SAAQ,gBAAgB;IAClD,aAAa,EAAE,qBAAqB,CAAC;IACrC,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,
|
|
1
|
+
{"version":3,"file":"ModelPrimitive.d.ts","sourceRoot":"","sources":["../../src/components/ModelPrimitive.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAMnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,UAAU,mBAAoB,SAAQ,gBAAgB;IAClD,aAAa,EAAE,qBAAqB,CAAC;IACrC,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA0ChD,CAAC;AAIH,eAAe,cAAc,CAAC"}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { memo, useCallback, useMemo, useRef } from 'react';
|
|
3
3
|
import { useModelObject } from '../hooks/useModelObject.js';
|
|
4
4
|
import { useNodeOverrides } from '../hooks/useNodeOverrides.js';
|
|
5
|
-
import { getNodeOverrides, peekModelAsset } from '@3driseai/3drise-core';
|
|
5
|
+
import { getNodeOverrides, peekModelAsset, resolveShadowFlags } from '@3driseai/3drise-core';
|
|
6
6
|
import { useObjectReadyReporter } from '../hooks/useObjectReady.js';
|
|
7
7
|
import { applyShadowFlags } from '../hooks/useShadowApplication.js';
|
|
8
8
|
/**
|
|
@@ -44,11 +44,9 @@ const ModelPrimitive = memo(({ createdObject, slot = 0, onObjectReady, onObjectD
|
|
|
44
44
|
// change. A model that suspended attaches after that mount, so it gets them here.
|
|
45
45
|
// Read through a ref: a changing callback ref would detach/attach the object and
|
|
46
46
|
// fire a spurious onObjectDispose / onObjectReady pair on every shadow toggle.
|
|
47
|
-
const shadowFlags = useRef({});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
receive: createdObject.meshSettings?.receiveShadow,
|
|
51
|
-
};
|
|
47
|
+
const shadowFlags = useRef({ cast: false, receive: false });
|
|
48
|
+
const resolved = resolveShadowFlags(createdObject.type, createdObject.meshSettings);
|
|
49
|
+
shadowFlags.current = { cast: resolved.castShadow, receive: resolved.receiveShadow };
|
|
52
50
|
const setObjectRef = useCallback((instance) => {
|
|
53
51
|
if (instance) {
|
|
54
52
|
applyShadowFlags(instance, shadowFlags.current.cast, shadowFlags.current.receive);
|
|
@@ -93,7 +93,7 @@ const ObjectNode = ({ node, onObjectReady, onObjectDispose, handleObjectClick })
|
|
|
93
93
|
particles: (obj, onObjectReady) => {
|
|
94
94
|
// Generative particle objects (config.type) render through ParticlesGenerator;
|
|
95
95
|
// the legacy shape particles keep their own path.
|
|
96
|
-
return (_jsx(ParticlesGenerator, { settings: { id: obj.id, config: obj.config, animations: obj.animations }, ...relabel(obj) }, obj.id));
|
|
96
|
+
return (_jsx(ParticlesGenerator, { settings: { id: obj.id, config: obj.config, animations: obj.animations }, mouseMove: obj.mouseMove?.particles, ...relabel(obj) }, obj.id));
|
|
97
97
|
// if (isGenerativeParticlesConfig(obj.config))
|
|
98
98
|
// return (<ParticlesGenerator key={obj.id} settings={{ config: obj.config }} {...relabel(obj)} />);
|
|
99
99
|
// const particlesSettings = obj;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { GenerativeParticlesSettings } from '@3driseai/3drise-core';
|
|
2
|
+
import type { GenerativeParticlesSettings, MouseMoveDomain } from '@3driseai/3drise-core';
|
|
3
3
|
import type { ObjectReadyProps } from '../types/objectReady';
|
|
4
4
|
export interface ParticlesGeneratorProps extends ObjectReadyProps {
|
|
5
5
|
settings: GenerativeParticlesSettings;
|
|
6
|
+
/** Pointer bindings for the object's own knobs (createdObject.mouseMove.particles). */
|
|
7
|
+
mouseMove?: MouseMoveDomain;
|
|
6
8
|
}
|
|
7
9
|
/** Standalone generative particle objects (type 'particles'), switched on config.type. */
|
|
8
10
|
declare const ParticlesGenerator: React.FC<ParticlesGeneratorProps>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ParticlesGenerator.d.ts","sourceRoot":"","sources":["../../src/components/ParticlesGenerator.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"ParticlesGenerator.d.ts","sourceRoot":"","sources":["../../src/components/ParticlesGenerator.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,2BAA2B,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAU7D,MAAM,WAAW,uBAAwB,SAAQ,gBAAgB;IAC7D,QAAQ,EAAE,2BAA2B,CAAC;IACtC,uFAAuF;IACvF,SAAS,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED,0FAA0F;AAC1F,QAAA,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAgBzD,CAAC;AACF,eAAe,kBAAkB,CAAC"}
|
|
@@ -8,12 +8,12 @@ import ChladniSandParticles from './particles/ChladniSandParticles.js';
|
|
|
8
8
|
import FaradayPowderParticles from './particles/FaradayPowderParticles.js';
|
|
9
9
|
import NeonRainParticles from './particles/NeonRainParticles.js';
|
|
10
10
|
/** Standalone generative particle objects (type 'particles'), switched on config.type. */
|
|
11
|
-
const ParticlesGenerator = ({ settings, onObjectReady, onObjectDispose }) => {
|
|
11
|
+
const ParticlesGenerator = ({ settings, mouseMove, onObjectReady, onObjectDispose }) => {
|
|
12
12
|
const { config, animations } = settings;
|
|
13
13
|
if (!config?.enabled)
|
|
14
14
|
return null;
|
|
15
15
|
const size = config.size ?? 1;
|
|
16
|
-
const ready = { onObjectReady, onObjectDispose };
|
|
16
|
+
const ready = { onObjectReady, onObjectDispose, mouseMove };
|
|
17
17
|
switch (config.type) {
|
|
18
18
|
case 'tidalStream': return _jsx(TidalStreamParticles, { id: settings.id, size: size, config: config, animations: animations, ...ready });
|
|
19
19
|
case 'brownianDust': return _jsx(BrownianDustParticles, { id: settings.id, size: size, config: config, animations: animations, ...ready });
|
|
@@ -22,7 +22,7 @@ const ProjectLoader = ({ projectId, onObjectClick, onObjectHover, onElementClick
|
|
|
22
22
|
"Content-Type": "application/json",
|
|
23
23
|
},
|
|
24
24
|
});
|
|
25
|
-
console.log(response)
|
|
25
|
+
// console.log(response)
|
|
26
26
|
if (!response.ok) {
|
|
27
27
|
if (response.status === 404) {
|
|
28
28
|
throw new Error("Project not found");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SceneBuilder.d.ts","sourceRoot":"","sources":["../../src/components/SceneBuilder.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAQ/D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,GAAG,gBAAgB,
|
|
1
|
+
{"version":3,"file":"SceneBuilder.d.ts","sourceRoot":"","sources":["../../src/components/SceneBuilder.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAQ/D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,GAAG,gBAAgB,CAuIhE,CAAC;AACF,eAAe,YAAY,CAAC"}
|
|
@@ -71,7 +71,11 @@ const SceneBuilder = ({ project, width = "100%", height = "100vh", style = {}, c
|
|
|
71
71
|
const timeSettings = sceneSettings.timeSettings || sceneSettings.scene?.timeSettings || {};
|
|
72
72
|
const lighting = project.lights && project.lights.length > 0 ? project.lights : [{ ...LIGHTING_PRESETS.ambient.config, enabled: true, showHelpers: false }];
|
|
73
73
|
const controls = camera.controls;
|
|
74
|
-
|
|
74
|
+
// `shadows` defaults to ON, matching the studio's canvas defaults. A project
|
|
75
|
+
// whose sceneSettings were saved before the canvas block existed carries no
|
|
76
|
+
// `shadows` key, and defaulting to false left <Canvas> with shadowMap disabled
|
|
77
|
+
// — every castShadow / receiveShadow flag in the scene then does nothing.
|
|
78
|
+
const { shadows = true, antialias = true, dpr = window.devicePixelRatio, gl = {}, backgroundColor = "#000000" } = sceneCanvas;
|
|
75
79
|
const { position, orientation, fov, near, far } = camera;
|
|
76
80
|
const containerStyle = {
|
|
77
81
|
width,
|
|
@@ -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 FluorescentTubeProps = 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 FluorescentTube: React.FC<FluorescentTubeProps>;
|
|
13
|
+
export default FluorescentTube;
|
|
14
|
+
//# sourceMappingURL=FluorescentTube.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FluorescentTube.d.ts","sourceRoot":"","sources":["../../../src/components/fluorescentTube/FluorescentTube.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAqC,MAAM,OAAO,CAAC;AAM1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAItG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAIhE,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG;IAClD,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;AAgCF,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA4OnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// FluorescentTube — a rect-area light shaped like the tubes that make it
|
|
4
|
+
// =============================================================================
|
|
5
|
+
//
|
|
6
|
+
// Rendered by LightObject for `config.type === 'fluorescentTube'`.
|
|
7
|
+
//
|
|
8
|
+
// The light itself is a THREE.RectAreaLight sized to the bank of tubes, because
|
|
9
|
+
// that is the character of a fluorescent fixture: a long soft source that wraps
|
|
10
|
+
// round what it lights instead of throwing one hard shadow. Three's rect-area
|
|
11
|
+
// light cannot cast a shadow, so when the config asks for one a point light sits
|
|
12
|
+
// at the middle of the fixture carrying `shadowStrength` of the output — set it
|
|
13
|
+
// to 0 for a pure area light with no shadow at all. (A row of shadow-casting
|
|
14
|
+
// point lights would be six shadow renders each; that is the trade.)
|
|
15
|
+
//
|
|
16
|
+
// Everything else here is the behaviour of a real tube:
|
|
17
|
+
// - `startup: 'stutter'` strikes the arc the way a magnetic ballast does —
|
|
18
|
+
// two or three false starts before it catches, more as the tube ages;
|
|
19
|
+
// - mercury vapour then takes `warmup` seconds to reach full output;
|
|
20
|
+
// - `hum` is the 100/120 Hz ripple of a magnetic ballast;
|
|
21
|
+
// - `greenShift` is the phosphor's green spike: halophosphate tubes have a
|
|
22
|
+
// CRI around 80 and read distinctly green next to daylight;
|
|
23
|
+
// - `age` dims it, blackens the cathode ends, lengthens the strike and adds
|
|
24
|
+
// flicker, all from one knob.
|
|
25
|
+
//
|
|
26
|
+
// NOTE: a rect-area light only lights MeshStandardMaterial and
|
|
27
|
+
// MeshPhysicalMaterial. On a Phong or Lambert surface only the shadow point
|
|
28
|
+
// light shows.
|
|
29
|
+
//
|
|
30
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
31
|
+
import { useFrame } from '@react-three/fiber';
|
|
32
|
+
import { Color, CylinderGeometry, PlaneGeometry } from 'three';
|
|
33
|
+
import { RectAreaLightUniformsLib } from 'three/examples/jsm/lights/RectAreaLightUniformsLib.js';
|
|
34
|
+
import { SELF_OWNED_MATERIAL_KEY } from '@3driseai/3drise-core';
|
|
35
|
+
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
36
|
+
import { useLightShadow } from '../../hooks/useLightShadow.js';
|
|
37
|
+
import { useLightConfig } from '../../hooks/useLightConfig.js';
|
|
38
|
+
import { kelvinToColor } from '../lightBulb/kelvin.js';
|
|
39
|
+
import { createTubeMaterials, disposeTubeMaterials, HALO_CORE, SLEEVE_CORE } from './tubeMaterials.js';
|
|
40
|
+
const num = (v, fallback) => (typeof v === 'number' && Number.isFinite(v) ? v : fallback);
|
|
41
|
+
const clamp01 = (v) => Math.min(Math.max(v, 0), 1);
|
|
42
|
+
const clampInt = (v, lo, hi, d) => Math.min(Math.max(typeof v === 'number' && Number.isFinite(v) ? Math.round(v) : d, lo), hi);
|
|
43
|
+
const hash = (n) => {
|
|
44
|
+
const s = Math.sin(n * 127.1) * 43758.5453;
|
|
45
|
+
return s - Math.floor(s);
|
|
46
|
+
};
|
|
47
|
+
const noise1 = (x) => {
|
|
48
|
+
const i = Math.floor(x), f = x - i, u = f * f * (3 - 2 * f);
|
|
49
|
+
return hash(i) * (1 - u) + hash(i + 1) * u;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The strike. Returns the level while the ballast is still trying, and how long
|
|
53
|
+
* the whole attempt lasts. An older tube needs more goes and takes longer.
|
|
54
|
+
*/
|
|
55
|
+
function strike(t, age, seed) {
|
|
56
|
+
const tries = 2 + Math.round(age * 3);
|
|
57
|
+
const unit = 0.2 * (1 + age * 1.5);
|
|
58
|
+
const duration = tries * unit;
|
|
59
|
+
if (t >= duration)
|
|
60
|
+
return { level: 1, duration };
|
|
61
|
+
const i = Math.floor(t / unit);
|
|
62
|
+
const f = (t % unit) / unit;
|
|
63
|
+
// a bright flash at the top of each attempt, then the glow dies back
|
|
64
|
+
const level = f < 0.3 ? 0.45 + 0.45 * hash(i + seed) : 0.03 + 0.05 * hash(i * 7 + seed);
|
|
65
|
+
return { level, duration };
|
|
66
|
+
}
|
|
67
|
+
const FluorescentTube = ({ id, config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
|
|
68
|
+
const groupRef = useRef(null);
|
|
69
|
+
const lightRef = useRef(null);
|
|
70
|
+
const shadowLightRef = useRef(null);
|
|
71
|
+
const tubesRef = useRef(null);
|
|
72
|
+
// Rect-area lights need their BRDF lookup tables uploaded once per page.
|
|
73
|
+
useEffect(() => { RectAreaLightUniformsLib.init(); }, []);
|
|
74
|
+
useObjectReady(groupRef, settings, onObjectReady, onObjectDispose);
|
|
75
|
+
// The shadow settings belong to the point light — the area light has no shadow.
|
|
76
|
+
useLightShadow(shadowLightRef, config.shadow);
|
|
77
|
+
const { merged } = useLightConfig(groupRef, lightRef, id, config, animations, mouseMove);
|
|
78
|
+
const tubeCount = clampInt(config.tubes, 1, 4, 1);
|
|
79
|
+
const tubeSpacing = Math.max(num(config.tubeSpacing, 0.14), 0.02);
|
|
80
|
+
const length = Math.max(num(config.length, 1.2), 0.1);
|
|
81
|
+
const diameter = Math.min(Math.max(num(config.diameter, 0.026), 0.006), 0.12);
|
|
82
|
+
const geometry = useMemo(() => {
|
|
83
|
+
// Built along Y and laid down on X, so uv.y still runs end to end and the
|
|
84
|
+
// shaders can darken the cathodes.
|
|
85
|
+
const glass = new CylinderGeometry(diameter / 2, diameter / 2, length, 24, 1, true);
|
|
86
|
+
glass.rotateZ(Math.PI / 2);
|
|
87
|
+
const cap = new CylinderGeometry(diameter * 0.58, diameter * 0.58, diameter * 0.9, 18);
|
|
88
|
+
cap.rotateZ(Math.PI / 2);
|
|
89
|
+
const quad = new PlaneGeometry(2, 2);
|
|
90
|
+
return { glass, cap, quad };
|
|
91
|
+
}, [diameter, length]);
|
|
92
|
+
useEffect(() => () => {
|
|
93
|
+
geometry.glass.dispose();
|
|
94
|
+
geometry.cap.dispose();
|
|
95
|
+
geometry.quad.dispose();
|
|
96
|
+
}, [geometry]);
|
|
97
|
+
const mats = useMemo(() => createTubeMaterials(), []);
|
|
98
|
+
useEffect(() => () => disposeTubeMaterials(mats), [mats]);
|
|
99
|
+
// The light owns its materials and its shadow flags.
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const group = groupRef.current;
|
|
102
|
+
if (!group)
|
|
103
|
+
return;
|
|
104
|
+
group.userData[SELF_OWNED_MATERIAL_KEY] = true;
|
|
105
|
+
return () => { delete group.userData[SELF_OWNED_MATERIAL_KEY]; };
|
|
106
|
+
}, []);
|
|
107
|
+
// The tubes sit centimetres from the shadow light.
|
|
108
|
+
const shadowNear = config.shadow?.cameraNear;
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
const light = shadowLightRef.current;
|
|
111
|
+
if (!light || shadowNear !== undefined)
|
|
112
|
+
return;
|
|
113
|
+
light.shadow.camera.near = 0.05;
|
|
114
|
+
light.shadow.camera.updateProjectionMatrix();
|
|
115
|
+
}, [shadowNear]);
|
|
116
|
+
const colorMode = (config.colorMode ?? 'temperature');
|
|
117
|
+
const lightColor = useMemo(() => (colorMode === 'color'
|
|
118
|
+
? new Color(config.color || '#ffffff')
|
|
119
|
+
: kelvinToColor(config.temperature ?? 4000)), [colorMode, config.color, config.temperature]);
|
|
120
|
+
// The bank of tubes is the emitting rectangle.
|
|
121
|
+
const bankDepth = (tubeCount - 1) * tubeSpacing + diameter * 2.2;
|
|
122
|
+
const startup = (config.startup ?? 'stutter');
|
|
123
|
+
const power = useRef({
|
|
124
|
+
level: 0,
|
|
125
|
+
base: config.intensity ?? 1,
|
|
126
|
+
written: Number.NaN,
|
|
127
|
+
seed: Math.floor(Math.random() * 997),
|
|
128
|
+
/**
|
|
129
|
+
* Seconds since the switch was thrown; -1 once the tube is settled. It
|
|
130
|
+
* starts at 0 even for a light that loads switched on, so a fixture set
|
|
131
|
+
* to 'stutter' strikes when the scene opens — that is the whole reason
|
|
132
|
+
* to pick that mode.
|
|
133
|
+
*/
|
|
134
|
+
since: 0,
|
|
135
|
+
wasOn: config.on ?? true,
|
|
136
|
+
});
|
|
137
|
+
const scratch = useMemo(() => ({ glow: new Color(), tint: new Color() }), []);
|
|
138
|
+
useFrame(({ clock }, delta) => {
|
|
139
|
+
const light = lightRef.current;
|
|
140
|
+
if (!light)
|
|
141
|
+
return;
|
|
142
|
+
const cfg = merged.current;
|
|
143
|
+
const p = power.current;
|
|
144
|
+
const dt = Math.min(delta, 0.1);
|
|
145
|
+
if (light.intensity !== p.written)
|
|
146
|
+
p.base = light.intensity;
|
|
147
|
+
const on = cfg.on ?? true;
|
|
148
|
+
const age = clamp01(num(cfg.age, 0.15));
|
|
149
|
+
const warmup = Math.max(num(cfg.warmup, 1.2), 0);
|
|
150
|
+
if (on !== p.wasOn) {
|
|
151
|
+
p.wasOn = on;
|
|
152
|
+
p.since = 0;
|
|
153
|
+
p.seed = Math.floor(Math.random() * 997);
|
|
154
|
+
}
|
|
155
|
+
if (p.since >= 0)
|
|
156
|
+
p.since += dt;
|
|
157
|
+
if (!on) {
|
|
158
|
+
// The phosphor keeps glowing for a moment after the arc goes out.
|
|
159
|
+
p.level += (0 - p.level) * (1 - Math.exp(-dt / 0.12));
|
|
160
|
+
if (p.level < 1e-4)
|
|
161
|
+
p.level = 0;
|
|
162
|
+
}
|
|
163
|
+
else if (startup === 'stutter' && p.since >= 0) {
|
|
164
|
+
const s = strike(p.since, age, p.seed);
|
|
165
|
+
if (p.since < s.duration) {
|
|
166
|
+
p.level = s.level;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
// Struck. Mercury vapour now climbs to full output.
|
|
170
|
+
const t = warmup <= 0 ? 1 : 1 - Math.exp(-(p.since - s.duration) * 2.6 / warmup);
|
|
171
|
+
p.level = t;
|
|
172
|
+
if (t > 0.999) {
|
|
173
|
+
p.level = 1;
|
|
174
|
+
p.since = -1;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
const tau = warmup * (startup === 'instant' ? 0.35 : 1);
|
|
180
|
+
p.level = tau <= 0 ? 1 : p.level + (1 - p.level) * (1 - Math.exp(-dt * 2.6 / tau));
|
|
181
|
+
if (p.level > 0.999) {
|
|
182
|
+
p.level = 1;
|
|
183
|
+
p.since = -1;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const t = clock.elapsedTime;
|
|
187
|
+
// Ballast hum: 100 Hz ripple, deeper on a tired tube.
|
|
188
|
+
const hum = clamp01(num(cfg.hum, 0.3));
|
|
189
|
+
const ripple = hum > 0 ? 1 - hum * 0.04 * (0.5 + 0.5 * Math.sin(t * Math.PI * 2 * 100)) : 1;
|
|
190
|
+
const flicker = clamp01(num(cfg.flicker, 0)) + age * 0.18;
|
|
191
|
+
let flick = 1;
|
|
192
|
+
if (flicker > 0.001) {
|
|
193
|
+
const ft = t + p.seed;
|
|
194
|
+
flick = 1 - Math.min(flicker, 1) * (0.12 * noise1(ft * 9) + 0.5 * Math.pow(noise1(ft * 2.3), 5));
|
|
195
|
+
}
|
|
196
|
+
// An aged tube simply puts out less light.
|
|
197
|
+
const output = p.level * ripple * flick * (1 - age * 0.35);
|
|
198
|
+
light.intensity = p.base * output;
|
|
199
|
+
p.written = light.intensity;
|
|
200
|
+
// Colour: rebuilt from the config every frame, then pushed towards the
|
|
201
|
+
// phosphor's green spike. It has to be rebuilt rather than nudged — a lerp
|
|
202
|
+
// applied to the light's own colour compounds, and after a second of frames
|
|
203
|
+
// the tube is pure green whatever the temperature says.
|
|
204
|
+
const tint = scratch.tint;
|
|
205
|
+
if ((cfg.colorMode ?? 'temperature') === 'color')
|
|
206
|
+
tint.set(cfg.color || '#ffffff');
|
|
207
|
+
else
|
|
208
|
+
kelvinToColor(num(cfg.temperature, 4000), tint);
|
|
209
|
+
const greenShift = clamp01(num(cfg.greenShift, 0.35));
|
|
210
|
+
if (greenShift > 0) {
|
|
211
|
+
// A channel gain rather than a blend towards a green: the spike is the
|
|
212
|
+
// phosphor putting out more green and less of everything else at the
|
|
213
|
+
// same colour temperature, and gains stay visible at small amounts
|
|
214
|
+
// where a blend towards a fixed colour does not.
|
|
215
|
+
tint.setRGB(tint.r * (1 - 0.32 * greenShift), tint.g * (1 + 0.18 * greenShift), tint.b * (1 - 0.26 * greenShift));
|
|
216
|
+
}
|
|
217
|
+
light.color.copy(tint);
|
|
218
|
+
light.width = length;
|
|
219
|
+
light.height = bankDepth;
|
|
220
|
+
const baseIntensity = Math.max(num(config.intensity, 1), 1e-4);
|
|
221
|
+
const glowK = Math.min(output * (p.base / baseIntensity), 3);
|
|
222
|
+
const glow = scratch.glow.copy(light.color);
|
|
223
|
+
// The shadow-casting core, carrying its share of the output.
|
|
224
|
+
const shadowLight = shadowLightRef.current;
|
|
225
|
+
if (shadowLight) {
|
|
226
|
+
const share = clamp01(num(cfg.shadowStrength, 0.4));
|
|
227
|
+
shadowLight.intensity = p.base * output * share;
|
|
228
|
+
shadowLight.color.copy(light.color);
|
|
229
|
+
// A fixture this size never casts a hard edge.
|
|
230
|
+
if (config.shadow?.radius === undefined)
|
|
231
|
+
shadowLight.shadow.radius = 5;
|
|
232
|
+
}
|
|
233
|
+
mats.phosphor.uniforms.uColor.value.copy(glow);
|
|
234
|
+
mats.phosphor.uniforms.uStrength.value = 0.05 + glowK * 1.9;
|
|
235
|
+
mats.phosphor.uniforms.uAge.value = age;
|
|
236
|
+
mats.sleeve.uniforms.uColor.value.copy(glow);
|
|
237
|
+
mats.sleeve.uniforms.uStrength.value = glowK * 0.45;
|
|
238
|
+
mats.sleeve.uniforms.uAge.value = age;
|
|
239
|
+
// The quad runs past the tube so the glow can wrap its ends; the shader's
|
|
240
|
+
// core fraction is what lines the lit part back up with the glass.
|
|
241
|
+
mats.sleeve.uniforms.uHalfLength.value = length / 2 / SLEEVE_CORE;
|
|
242
|
+
mats.sleeve.uniforms.uHalfWidth.value = diameter * 8;
|
|
243
|
+
const halo = Math.max(num(cfg.halo, 1), 0);
|
|
244
|
+
mats.halo.uniforms.uColor.value.copy(glow);
|
|
245
|
+
mats.halo.uniforms.uStrength.value = halo * glowK * 0.2;
|
|
246
|
+
// The wash is the same capsule, wider and far softer — air, not a tube.
|
|
247
|
+
mats.halo.uniforms.uHalfLength.value = length / 2 / HALO_CORE;
|
|
248
|
+
mats.halo.uniforms.uHalfWidth.value = Math.max(bankDepth, diameter * 10) * 2.2;
|
|
249
|
+
const bank = tubesRef.current;
|
|
250
|
+
if (bank)
|
|
251
|
+
bank.visible = (config.emitter ?? 'tubes') !== 'none';
|
|
252
|
+
});
|
|
253
|
+
// The point source exists whenever it carries any of the output; whether it
|
|
254
|
+
// *casts* is useLightShadow's business. Keeping those separate means toggling
|
|
255
|
+
// shadows off changes the shadows and not the brightness of the room.
|
|
256
|
+
const coreOn = num(config.shadowStrength, 0.4) > 0;
|
|
257
|
+
const offsets = useMemo(() => Array.from({ length: tubeCount }, (_, i) => (i - (tubeCount - 1) / 2) * tubeSpacing), [tubeCount, tubeSpacing]);
|
|
258
|
+
return (_jsxs("group", { ref: groupRef, children: [_jsx("rectAreaLight", { ref: lightRef,
|
|
259
|
+
// The emitting face looks down; width runs along the tubes.
|
|
260
|
+
rotation: [-Math.PI / 2, 0, 0], width: length, height: bankDepth, color: lightColor, intensity: config.intensity ?? 1 }), coreOn && (_jsx("pointLight", { ref: shadowLightRef, color: lightColor, intensity: 0, decay: config.decay ?? 1, distance: config.distance ?? 0 })), _jsx("group", { ref: tubesRef, children: offsets.map((z, i) => (_jsxs("group", { position: [0, 0, z], children: [_jsx("mesh", { geometry: geometry.glass, material: mats.phosphor, renderOrder: 2 }), _jsx("mesh", { geometry: geometry.quad, material: mats.sleeve, renderOrder: 3, frustumCulled: false }), _jsx("mesh", { geometry: geometry.cap, material: mats.cap, position: [-length / 2, 0, 0] }), _jsx("mesh", { geometry: geometry.cap, material: mats.cap, position: [length / 2, 0, 0] })] }, i))) }), _jsx("mesh", { geometry: geometry.quad, material: mats.halo, renderOrder: 5, frustumCulled: false })] }));
|
|
261
|
+
};
|
|
262
|
+
export default FluorescentTube;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as FluorescentTube } from './FluorescentTube';
|
|
2
|
+
export type { FluorescentTubeProps } from './FluorescentTube';
|
|
3
|
+
export { createTubeMaterials, disposeTubeMaterials } from './tubeMaterials';
|
|
4
|
+
export type { TubeMaterials } from './tubeMaterials';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/fluorescentTube/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5E,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { MeshStandardMaterial, ShaderMaterial } from 'three';
|
|
2
|
+
export interface TubeMaterials {
|
|
3
|
+
/** The lit phosphor, with blackened ends as it ages. */
|
|
4
|
+
phosphor: ShaderMaterial;
|
|
5
|
+
/** The air just outside the glass: a camera-facing sleeve. */
|
|
6
|
+
sleeve: ShaderMaterial;
|
|
7
|
+
/** Cathode end caps. */
|
|
8
|
+
cap: MeshStandardMaterial;
|
|
9
|
+
/** A soft camera-facing wash for the fixture as a whole. */
|
|
10
|
+
halo: ShaderMaterial;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The haze around the glass. Not a bigger cylinder — a quad that pivots about
|
|
14
|
+
* the tube's own axis to face the camera, which is the only way a line source
|
|
15
|
+
* keeps an even glow from every angle. A fatter cylinder shows its own far wall
|
|
16
|
+
* as a second bright edge; this does not.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Both glows are quads that pivot about the tube's own axis to face the camera.
|
|
20
|
+
* A fatter cylinder would show its own far wall as a second bright edge, and a
|
|
21
|
+
* plain screen-space billboard would stretch along the screen instead of along
|
|
22
|
+
* the tube — which is wrong the moment the fixture is not horizontal.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Distance to the tube's *line* rather than to its centre, normalised: 0 along
|
|
26
|
+
* the core, 1 at the quad's rim. Both glows are capsules round the fixture, so
|
|
27
|
+
* they wrap the ends instead of being cut off at them.
|
|
28
|
+
*/
|
|
29
|
+
/** Fraction of each quad that is the tube itself; the rest is the rounded end. */
|
|
30
|
+
export declare const SLEEVE_CORE = 0.86;
|
|
31
|
+
export declare const HALO_CORE = 0.75;
|
|
32
|
+
export declare function createTubeMaterials(): TubeMaterials;
|
|
33
|
+
export declare function disposeTubeMaterials(m: TubeMaterials): void;
|
|
34
|
+
//# sourceMappingURL=tubeMaterials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tubeMaterials.d.ts","sourceRoot":"","sources":["../../../src/components/fluorescentTube/tubeMaterials.ts"],"names":[],"mappings":"AAYA,OAAO,EAAuC,oBAAoB,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAElG,MAAM,WAAW,aAAa;IAC1B,wDAAwD;IACxD,QAAQ,EAAE,cAAc,CAAC;IACzB,8DAA8D;IAC9D,MAAM,EAAE,cAAc,CAAC;IACvB,wBAAwB;IACxB,GAAG,EAAE,oBAAoB,CAAC;IAC1B,4DAA4D;IAC5D,IAAI,EAAE,cAAc,CAAC;CACxB;AAsDD;;;;;GAKG;AACH;;;;;GAKG;AACH;;;;GAIG;AACH,kFAAkF;AAClF,eAAO,MAAM,WAAW,OAAO,CAAC;AAChC,eAAO,MAAM,SAAS,OAAO,CAAC;AA+F9B,wBAAgB,mBAAmB,IAAI,aAAa,CAOnD;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAK3D"}
|