@3driseai/3drise-viewer 0.1.21 → 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.
Files changed (107) hide show
  1. package/dist/components/CreatedObject.d.ts.map +1 -1
  2. package/dist/components/CreatedObject.js +6 -1
  3. package/dist/components/LightObject.d.ts.map +1 -1
  4. package/dist/components/LightObject.js +24 -9
  5. package/dist/components/ModelPrimitive.d.ts.map +1 -1
  6. package/dist/components/ModelPrimitive.js +10 -1
  7. package/dist/components/ObjectNode.js +1 -1
  8. package/dist/components/ParticlesGenerator.d.ts +3 -1
  9. package/dist/components/ParticlesGenerator.d.ts.map +1 -1
  10. package/dist/components/ParticlesGenerator.js +2 -2
  11. package/dist/components/ProjectLoader.js +1 -1
  12. package/dist/components/SceneBuilder.d.ts.map +1 -1
  13. package/dist/components/SceneBuilder.js +5 -1
  14. package/dist/components/fluorescentTube/FluorescentTube.d.ts +14 -0
  15. package/dist/components/fluorescentTube/FluorescentTube.d.ts.map +1 -0
  16. package/dist/components/fluorescentTube/FluorescentTube.js +262 -0
  17. package/dist/components/fluorescentTube/index.d.ts +5 -0
  18. package/dist/components/fluorescentTube/index.d.ts.map +1 -0
  19. package/dist/components/fluorescentTube/index.js +3 -0
  20. package/dist/components/fluorescentTube/tubeMaterials.d.ts +34 -0
  21. package/dist/components/fluorescentTube/tubeMaterials.d.ts.map +1 -0
  22. package/dist/components/fluorescentTube/tubeMaterials.js +185 -0
  23. package/dist/components/index.d.ts +4 -0
  24. package/dist/components/index.d.ts.map +1 -1
  25. package/dist/components/index.js +4 -0
  26. package/dist/components/ledArray/LedArray.d.ts +14 -0
  27. package/dist/components/ledArray/LedArray.d.ts.map +1 -0
  28. package/dist/components/ledArray/LedArray.js +243 -0
  29. package/dist/components/ledArray/index.d.ts +7 -0
  30. package/dist/components/ledArray/index.d.ts.map +1 -0
  31. package/dist/components/ledArray/index.js +4 -0
  32. package/dist/components/ledArray/ledLayout.d.ts +22 -0
  33. package/dist/components/ledArray/ledLayout.d.ts.map +1 -0
  34. package/dist/components/ledArray/ledLayout.js +59 -0
  35. package/dist/components/ledArray/ledMaterials.d.ts +14 -0
  36. package/dist/components/ledArray/ledMaterials.d.ts.map +1 -0
  37. package/dist/components/ledArray/ledMaterials.js +151 -0
  38. package/dist/components/lightBulb/LightBulb.d.ts +16 -0
  39. package/dist/components/lightBulb/LightBulb.d.ts.map +1 -0
  40. package/dist/components/lightBulb/LightBulb.js +141 -0
  41. package/dist/components/lightBulb/bulbMaterials.d.ts +27 -0
  42. package/dist/components/lightBulb/bulbMaterials.d.ts.map +1 -0
  43. package/dist/components/lightBulb/bulbMaterials.js +133 -0
  44. package/dist/components/lightBulb/bulbShapes.d.ts +15 -0
  45. package/dist/components/lightBulb/bulbShapes.d.ts.map +1 -0
  46. package/dist/components/lightBulb/bulbShapes.js +180 -0
  47. package/dist/components/lightBulb/index.d.ts +5 -0
  48. package/dist/components/lightBulb/index.d.ts.map +1 -0
  49. package/dist/components/lightBulb/index.js +3 -0
  50. package/dist/components/lightBulb/kelvin.d.ts +7 -0
  51. package/dist/components/lightBulb/kelvin.d.ts.map +1 -0
  52. package/dist/components/lightBulb/kelvin.js +21 -0
  53. package/dist/components/lightBulb/types.d.ts +2 -0
  54. package/dist/components/lightBulb/types.d.ts.map +1 -0
  55. package/dist/components/lightBulb/types.js +1 -0
  56. package/dist/components/particles/BioluminescentWakeParticles.d.ts +3 -1
  57. package/dist/components/particles/BioluminescentWakeParticles.d.ts.map +1 -1
  58. package/dist/components/particles/BioluminescentWakeParticles.js +7 -3
  59. package/dist/components/particles/BrownianDustParticles.d.ts +3 -1
  60. package/dist/components/particles/BrownianDustParticles.d.ts.map +1 -1
  61. package/dist/components/particles/BrownianDustParticles.js +7 -3
  62. package/dist/components/particles/ChladniSandParticles.d.ts +3 -1
  63. package/dist/components/particles/ChladniSandParticles.d.ts.map +1 -1
  64. package/dist/components/particles/ChladniSandParticles.js +7 -3
  65. package/dist/components/particles/ElectronOrbitalsParticles.d.ts +3 -1
  66. package/dist/components/particles/ElectronOrbitalsParticles.d.ts.map +1 -1
  67. package/dist/components/particles/ElectronOrbitalsParticles.js +7 -3
  68. package/dist/components/particles/FaradayPowderParticles.d.ts +3 -1
  69. package/dist/components/particles/FaradayPowderParticles.d.ts.map +1 -1
  70. package/dist/components/particles/FaradayPowderParticles.js +7 -3
  71. package/dist/components/particles/GrinderSparksParticles.d.ts +3 -1
  72. package/dist/components/particles/GrinderSparksParticles.d.ts.map +1 -1
  73. package/dist/components/particles/GrinderSparksParticles.js +7 -3
  74. package/dist/components/particles/NeonRainParticles.d.ts +3 -1
  75. package/dist/components/particles/NeonRainParticles.d.ts.map +1 -1
  76. package/dist/components/particles/NeonRainParticles.js +7 -3
  77. package/dist/components/particles/TidalStreamParticles.d.ts +3 -1
  78. package/dist/components/particles/TidalStreamParticles.d.ts.map +1 -1
  79. package/dist/components/particles/TidalStreamParticles.js +7 -3
  80. package/dist/components/spotBeam/SpotBeam.d.ts +14 -0
  81. package/dist/components/spotBeam/SpotBeam.d.ts.map +1 -0
  82. package/dist/components/spotBeam/SpotBeam.js +244 -0
  83. package/dist/components/spotBeam/beamPatterns.d.ts +11 -0
  84. package/dist/components/spotBeam/beamPatterns.d.ts.map +1 -0
  85. package/dist/components/spotBeam/beamPatterns.js +106 -0
  86. package/dist/components/spotBeam/index.d.ts +7 -0
  87. package/dist/components/spotBeam/index.d.ts.map +1 -0
  88. package/dist/components/spotBeam/index.js +4 -0
  89. package/dist/components/spotBeam/spotBeamMaterials.d.ts +16 -0
  90. package/dist/components/spotBeam/spotBeamMaterials.d.ts.map +1 -0
  91. package/dist/components/spotBeam/spotBeamMaterials.js +307 -0
  92. package/dist/hooks/index.d.ts +4 -1
  93. package/dist/hooks/index.d.ts.map +1 -1
  94. package/dist/hooks/index.js +3 -1
  95. package/dist/hooks/useConfigLive.d.ts +2 -2
  96. package/dist/hooks/useConfigLive.d.ts.map +1 -1
  97. package/dist/hooks/useConfigLive.js +1 -1
  98. package/dist/hooks/useLightConfig.d.ts +25 -0
  99. package/dist/hooks/useLightConfig.d.ts.map +1 -0
  100. package/dist/hooks/useLightConfig.js +125 -0
  101. package/dist/hooks/useLightLive.d.ts +12 -0
  102. package/dist/hooks/useLightLive.d.ts.map +1 -0
  103. package/dist/hooks/useLightLive.js +53 -0
  104. package/dist/hooks/useShadowApplication.d.ts +8 -0
  105. package/dist/hooks/useShadowApplication.d.ts.map +1 -1
  106. package/dist/hooks/useShadowApplication.js +33 -9
  107. 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;AAUnE,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,CAmF9C,CAAC;AAEH,eAAe,aAAa,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
- useShadowApplication(meshRef, createdObject.meshSettings.castShadow, createdObject.meshSettings.receiveShadow);
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;AAM7D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnE,KAAK,gBAAgB,GAAG,gBAAgB,GAAG;IACvC,aAAa,EAAE,qBAAqB,CAAC;CACxC,CAAC;AA+CF,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAgB3C,CAAC;AACF,eAAe,WAAW,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,14 +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 { useLightAnimation } from '../hooks/useLightAnimation.js';
7
- import { useLightMouseMove } from '../hooks/useLightMouseMove.js';
6
+ import { useLightConfig } from '../hooks/useLightConfig.js';
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';
8
11
  const DirectionalLightR3F = ({ config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
9
12
  const lightRef = useRef(null);
10
13
  useObjectReady(lightRef, settings, onObjectReady, onObjectDispose);
11
14
  useLightShadow(lightRef, config.shadow);
12
- useLightAnimation(lightRef, animations);
13
- useLightMouseMove(lightRef, mouseMove, { lightConfig: config });
15
+ // Keyframes, pointer bindings and live drags, merged onto the light.
16
+ useLightConfig(lightRef, lightRef, settings?.id, config, animations, mouseMove);
14
17
  return (_jsx("directionalLight", { ref: lightRef, color: config.color || '#ffffff', intensity: config.intensity ?? 1 }));
15
18
  };
16
19
  const SpotLightR3F = ({ config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
@@ -37,16 +40,16 @@ const SpotLightR3F = ({ config, animations, mouseMove, settings, onObjectReady,
37
40
  };
38
41
  }, [scene, tx, ty, tz]);
39
42
  useLightShadow(lightRef, config.shadow);
40
- useLightAnimation(lightRef, animations);
41
- useLightMouseMove(lightRef, mouseMove, { lightConfig: config });
42
- 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 }));
43
46
  };
44
47
  const PointLightR3F = ({ config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
45
48
  const lightRef = useRef(null);
46
49
  useObjectReady(lightRef, settings, onObjectReady, onObjectDispose);
47
50
  useLightShadow(lightRef, config.shadow);
48
- useLightAnimation(lightRef, animations);
49
- useLightMouseMove(lightRef, mouseMove, { lightConfig: config });
51
+ // Keyframes, pointer bindings and live drags, merged onto the light.
52
+ useLightConfig(lightRef, lightRef, settings?.id, config, animations, mouseMove);
50
53
  return (_jsx("pointLight", { ref: lightRef, color: config.color || '#ffffff', intensity: config.intensity ?? 1, decay: config.decay ?? 1, distance: config.distance ?? 0 }));
51
54
  };
52
55
  const LightObject = ({ createdObject, onObjectReady, onObjectDispose }) => {
@@ -62,6 +65,18 @@ const LightObject = ({ createdObject, onObjectReady, onObjectDispose }) => {
62
65
  return _jsx(SpotLightR3F, { config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
63
66
  case 'point':
64
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 });
77
+ // A visible bulb: point light + glass/filament/base + self-drawn glow.
78
+ case 'lightBulb':
79
+ return _jsx(LightBulb, { id: createdObject.id, config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
65
80
  default:
66
81
  return null;
67
82
  }
@@ -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;AAKnE,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,CAkChD,CAAC;AAIH,eAAe,cAAc,CAAC"}
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,8 +2,9 @@ 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
+ import { applyShadowFlags } from '../hooks/useShadowApplication.js';
7
8
  /**
8
9
  * Mounts the Object3D behind a `type: 'model'` scene object, and hands it to the host
9
10
  * application.
@@ -39,8 +40,16 @@ const ModelPrimitive = memo(({ createdObject, slot = 0, onObjectReady, onObjectD
39
40
  const master = useMemo(() => (url ? peekModelAsset(url)?.scene ?? null : null), [url, object]);
40
41
  useNodeOverrides(object, getNodeOverrides(createdObject), master);
41
42
  const { report, reportDisposed } = useObjectReadyReporter(createdObject, onObjectReady, onObjectDispose);
43
+ // CreatedObject applies meshSettings' shadow flags on its own mount and on every
44
+ // change. A model that suspended attaches after that mount, so it gets them here.
45
+ // Read through a ref: a changing callback ref would detach/attach the object and
46
+ // fire a spurious onObjectDispose / onObjectReady pair on every shadow toggle.
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 };
42
50
  const setObjectRef = useCallback((instance) => {
43
51
  if (instance) {
52
+ applyShadowFlags(instance, shadowFlags.current.cast, shadowFlags.current.receive);
44
53
  attachedRef.current = instance;
45
54
  report(instance);
46
55
  return;
@@ -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;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAU7D,MAAM,WAAW,uBAAwB,SAAQ,gBAAgB;IAC7D,QAAQ,EAAE,2BAA2B,CAAC;CACzC;AAED,0FAA0F;AAC1F,QAAA,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAgBzD,CAAC;AACF,eAAe,kBAAkB,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,CAmIhE,CAAC;AACF,eAAe,YAAY,CAAC"}
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
- const { shadows = false, antialias = true, dpr = window.devicePixelRatio, gl = {}, backgroundColor = "#000000" } = sceneCanvas;
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,3 @@
1
+ // A bank of fluorescent tubes with a real rect-area light (config.type 'fluorescentTube').
2
+ export { default as FluorescentTube } from './FluorescentTube.js';
3
+ export { createTubeMaterials, disposeTubeMaterials } from './tubeMaterials.js';
@@ -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"}