@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.
Files changed (102) 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 -13
  5. package/dist/components/ModelPrimitive.d.ts.map +1 -1
  6. package/dist/components/ModelPrimitive.js +4 -6
  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/candle/Candle.d.ts +14 -0
  15. package/dist/components/candle/Candle.d.ts.map +1 -0
  16. package/dist/components/candle/Candle.js +249 -0
  17. package/dist/components/candle/candleGeometry.d.ts +23 -0
  18. package/dist/components/candle/candleGeometry.d.ts.map +1 -0
  19. package/dist/components/candle/candleGeometry.js +73 -0
  20. package/dist/components/candle/candleMaterials.d.ts +24 -0
  21. package/dist/components/candle/candleMaterials.d.ts.map +1 -0
  22. package/dist/components/candle/candleMaterials.js +433 -0
  23. package/dist/components/candle/flameMotion.d.ts +64 -0
  24. package/dist/components/candle/flameMotion.d.ts.map +1 -0
  25. package/dist/components/candle/flameMotion.js +138 -0
  26. package/dist/components/candle/index.d.ts +9 -0
  27. package/dist/components/candle/index.d.ts.map +1 -0
  28. package/dist/components/candle/index.js +6 -0
  29. package/dist/components/fluorescentTube/FluorescentTube.d.ts +14 -0
  30. package/dist/components/fluorescentTube/FluorescentTube.d.ts.map +1 -0
  31. package/dist/components/fluorescentTube/FluorescentTube.js +262 -0
  32. package/dist/components/fluorescentTube/index.d.ts +5 -0
  33. package/dist/components/fluorescentTube/index.d.ts.map +1 -0
  34. package/dist/components/fluorescentTube/index.js +3 -0
  35. package/dist/components/fluorescentTube/tubeMaterials.d.ts +34 -0
  36. package/dist/components/fluorescentTube/tubeMaterials.d.ts.map +1 -0
  37. package/dist/components/fluorescentTube/tubeMaterials.js +185 -0
  38. package/dist/components/index.d.ts +4 -0
  39. package/dist/components/index.d.ts.map +1 -1
  40. package/dist/components/index.js +4 -0
  41. package/dist/components/ledArray/LedArray.d.ts +14 -0
  42. package/dist/components/ledArray/LedArray.d.ts.map +1 -0
  43. package/dist/components/ledArray/LedArray.js +243 -0
  44. package/dist/components/ledArray/index.d.ts +7 -0
  45. package/dist/components/ledArray/index.d.ts.map +1 -0
  46. package/dist/components/ledArray/index.js +4 -0
  47. package/dist/components/ledArray/ledLayout.d.ts +22 -0
  48. package/dist/components/ledArray/ledLayout.d.ts.map +1 -0
  49. package/dist/components/ledArray/ledLayout.js +59 -0
  50. package/dist/components/ledArray/ledMaterials.d.ts +14 -0
  51. package/dist/components/ledArray/ledMaterials.d.ts.map +1 -0
  52. package/dist/components/ledArray/ledMaterials.js +151 -0
  53. package/dist/components/lightBulb/LightBulb.d.ts.map +1 -1
  54. package/dist/components/lightBulb/LightBulb.js +10 -13
  55. package/dist/components/particles/BioluminescentWakeParticles.d.ts +3 -1
  56. package/dist/components/particles/BioluminescentWakeParticles.d.ts.map +1 -1
  57. package/dist/components/particles/BioluminescentWakeParticles.js +7 -3
  58. package/dist/components/particles/BrownianDustParticles.d.ts +3 -1
  59. package/dist/components/particles/BrownianDustParticles.d.ts.map +1 -1
  60. package/dist/components/particles/BrownianDustParticles.js +7 -3
  61. package/dist/components/particles/ChladniSandParticles.d.ts +3 -1
  62. package/dist/components/particles/ChladniSandParticles.d.ts.map +1 -1
  63. package/dist/components/particles/ChladniSandParticles.js +7 -3
  64. package/dist/components/particles/ElectronOrbitalsParticles.d.ts +3 -1
  65. package/dist/components/particles/ElectronOrbitalsParticles.d.ts.map +1 -1
  66. package/dist/components/particles/ElectronOrbitalsParticles.js +7 -3
  67. package/dist/components/particles/FaradayPowderParticles.d.ts +3 -1
  68. package/dist/components/particles/FaradayPowderParticles.d.ts.map +1 -1
  69. package/dist/components/particles/FaradayPowderParticles.js +7 -3
  70. package/dist/components/particles/GrinderSparksParticles.d.ts +3 -1
  71. package/dist/components/particles/GrinderSparksParticles.d.ts.map +1 -1
  72. package/dist/components/particles/GrinderSparksParticles.js +7 -3
  73. package/dist/components/particles/NeonRainParticles.d.ts +3 -1
  74. package/dist/components/particles/NeonRainParticles.d.ts.map +1 -1
  75. package/dist/components/particles/NeonRainParticles.js +7 -3
  76. package/dist/components/particles/TidalStreamParticles.d.ts +3 -1
  77. package/dist/components/particles/TidalStreamParticles.d.ts.map +1 -1
  78. package/dist/components/particles/TidalStreamParticles.js +7 -3
  79. package/dist/components/spotBeam/SpotBeam.d.ts +14 -0
  80. package/dist/components/spotBeam/SpotBeam.d.ts.map +1 -0
  81. package/dist/components/spotBeam/SpotBeam.js +244 -0
  82. package/dist/components/spotBeam/beamPatterns.d.ts +11 -0
  83. package/dist/components/spotBeam/beamPatterns.d.ts.map +1 -0
  84. package/dist/components/spotBeam/beamPatterns.js +106 -0
  85. package/dist/components/spotBeam/index.d.ts +7 -0
  86. package/dist/components/spotBeam/index.d.ts.map +1 -0
  87. package/dist/components/spotBeam/index.js +4 -0
  88. package/dist/components/spotBeam/spotBeamMaterials.d.ts +16 -0
  89. package/dist/components/spotBeam/spotBeamMaterials.d.ts.map +1 -0
  90. package/dist/components/spotBeam/spotBeamMaterials.js +307 -0
  91. package/dist/hooks/index.d.ts +2 -0
  92. package/dist/hooks/index.d.ts.map +1 -1
  93. package/dist/hooks/index.js +1 -0
  94. package/dist/hooks/useLightConfig.d.ts +25 -0
  95. package/dist/hooks/useLightConfig.d.ts.map +1 -0
  96. package/dist/hooks/useLightConfig.js +125 -0
  97. package/dist/hooks/useLightLive.d.ts.map +1 -1
  98. package/dist/hooks/useLightLive.js +12 -2
  99. package/dist/hooks/useShadowApplication.d.ts +4 -4
  100. package/dist/hooks/useShadowApplication.d.ts.map +1 -1
  101. package/dist/hooks/useShadowApplication.js +25 -10
  102. 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;AAO7D,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAGnE,KAAK,gBAAgB,GAAG,gBAAgB,GAAG;IACvC,aAAa,EAAE,qBAAqB,CAAC;CACxC,CAAC;AAkDF,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAmB3C,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;AAUnE,KAAK,gBAAgB,GAAG,gBAAgB,GAAG;IACvC,aAAa,EAAE,qBAAqB,CAAC;CACxC,CAAC;AA+CF,QAAA,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA+B3C,CAAC;AACF,eAAe,WAAW,CAAC"}
@@ -3,17 +3,18 @@ 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';
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';
11
+ import Candle from './candle/Candle.js';
10
12
  const DirectionalLightR3F = ({ config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
11
13
  const lightRef = useRef(null);
12
14
  useObjectReady(lightRef, settings, onObjectReady, onObjectDispose);
13
15
  useLightShadow(lightRef, config.shadow);
14
- useLightAnimation(lightRef, animations);
15
- useLightMouseMove(lightRef, mouseMove, { lightConfig: config });
16
- useLightLive(lightRef, lightRef, settings?.id, config);
16
+ // Keyframes, pointer bindings and live drags, merged onto the light.
17
+ useLightConfig(lightRef, lightRef, settings?.id, config, animations, mouseMove);
17
18
  return (_jsx("directionalLight", { ref: lightRef, color: config.color || '#ffffff', intensity: config.intensity ?? 1 }));
18
19
  };
19
20
  const SpotLightR3F = ({ config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
@@ -40,18 +41,16 @@ const SpotLightR3F = ({ config, animations, mouseMove, settings, onObjectReady,
40
41
  };
41
42
  }, [scene, tx, ty, tz]);
42
43
  useLightShadow(lightRef, config.shadow);
43
- useLightAnimation(lightRef, animations);
44
- useLightMouseMove(lightRef, mouseMove, { lightConfig: config });
45
- useLightLive(lightRef, lightRef, settings?.id, config);
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 }));
44
+ // Keyframes, pointer bindings and live drags, merged onto the light.
45
+ useLightConfig(lightRef, lightRef, settings?.id, config, animations, mouseMove);
46
+ 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
47
  };
48
48
  const PointLightR3F = ({ config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
49
49
  const lightRef = useRef(null);
50
50
  useObjectReady(lightRef, settings, onObjectReady, onObjectDispose);
51
51
  useLightShadow(lightRef, config.shadow);
52
- useLightAnimation(lightRef, animations);
53
- useLightMouseMove(lightRef, mouseMove, { lightConfig: config });
54
- useLightLive(lightRef, lightRef, settings?.id, config);
52
+ // Keyframes, pointer bindings and live drags, merged onto the light.
53
+ useLightConfig(lightRef, lightRef, settings?.id, config, animations, mouseMove);
55
54
  return (_jsx("pointLight", { ref: lightRef, color: config.color || '#ffffff', intensity: config.intensity ?? 1, decay: config.decay ?? 1, distance: config.distance ?? 0 }));
56
55
  };
57
56
  const LightObject = ({ createdObject, onObjectReady, onObjectDispose }) => {
@@ -67,6 +66,18 @@ const LightObject = ({ createdObject, onObjectReady, onObjectDispose }) => {
67
66
  return _jsx(SpotLightR3F, { config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
68
67
  case 'point':
69
68
  return _jsx(PointLightR3F, { config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
69
+ // A raymarched flame with its own physics, and the wax under it.
70
+ case 'candle':
71
+ return _jsx(Candle, { id: createdObject.id, config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
72
+ // An array of LED dies: instant on, PWM ripple, optional diffuser.
73
+ case 'ledArray':
74
+ return _jsx(LedArray, { id: createdObject.id, config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
75
+ // A bank of tubes lit by a rect-area light, with the ballast's behaviour.
76
+ case 'fluorescentTube':
77
+ return _jsx(FluorescentTube, { id: createdObject.id, config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
78
+ // A spot light you can see: procedural gobo + a beam in the air.
79
+ case 'spotBeam':
80
+ return _jsx(SpotBeam, { id: createdObject.id, config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
70
81
  // A visible bulb: point light + glass/filament/base + self-drawn glow.
71
82
  case 'lightBulb':
72
83
  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,CA4ChD,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,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
- shadowFlags.current = {
49
- cast: createdObject.meshSettings?.castShadow,
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;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 CandleProps = 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 Candle: React.FC<CandleProps>;
13
+ export default Candle;
14
+ //# sourceMappingURL=Candle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Candle.d.ts","sourceRoot":"","sources":["../../../src/components/candle/Candle.tsx"],"names":[],"mappings":"AAiBA,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;AAQhE,MAAM,MAAM,WAAW,GAAG,gBAAgB,GAAG;IACzC,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;AAYF,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CA+RjC,CAAC;AAKF,eAAe,MAAM,CAAC"}
@@ -0,0 +1,249 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // =============================================================================
3
+ // Candle — a real flame, and the wax it is standing on
4
+ // =============================================================================
5
+ //
6
+ // Rendered by LightObject for `config.type === 'candle'`.
7
+ //
8
+ // The light is a PointLight sitting at the flame's *luminous centroid* — about
9
+ // 40% of the way up, not at the wick — and it moves with the flame every frame.
10
+ // That is what makes candlelight read as alive: the shadows sway, rather than
11
+ // just dimming in place.
12
+ //
13
+ // The flame itself is raymarched (see candleMaterials) and its motion comes from
14
+ // flameMotion, which models the buoyancy oscillation, the pink-noise wander of
15
+ // room air, guttering, and the ignition. Switching the candle on lights it: the
16
+ // wick catches small and blue, flares, and settles. Switching it off snuffs it
17
+ // and leaves a wisp of smoke.
18
+ //
19
+ import { useEffect, useMemo, useRef } from 'react';
20
+ import { useFrame } from '@react-three/fiber';
21
+ import { BoxGeometry, Color, PlaneGeometry, Vector3 } from 'three';
22
+ import { SELF_OWNED_MATERIAL_KEY } from '@3driseai/3drise-core';
23
+ import { useObjectReady } from '../../hooks/useObjectReady.js';
24
+ import { useLightShadow } from '../../hooks/useLightShadow.js';
25
+ import { useLightConfig } from '../../hooks/useLightConfig.js';
26
+ import { kelvinToColor } from '../lightBulb/kelvin.js';
27
+ import { createCandleMaterials, disposeCandleMaterials } from './candleMaterials.js';
28
+ import { buildCandle, wickTipPosition } from './candleGeometry.js';
29
+ import { createFlameMotion, stepFlameMotion } from './flameMotion.js';
30
+ const num = (v, d) => (typeof v === 'number' && Number.isFinite(v) ? v : d);
31
+ const clamp01 = (v) => Math.min(Math.max(v, 0), 1);
32
+ /** Headroom above the flame's nominal height, so draught can stretch it. */
33
+ const BOX_TALL = 1.55;
34
+ // Kept tight to the flame on purpose: the march is over the whole box, so a
35
+ // roomy box spends most of its steps on empty air and bands where the flame is
36
+ // thin. Wide enough for the lean and the undulation, and no wider.
37
+ const BOX_WIDE = 0.85;
38
+ const Candle = ({ id, config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
39
+ const groupRef = useRef(null);
40
+ const lightRef = useRef(null);
41
+ const waxLightRef = useRef(null);
42
+ const flameRef = useRef(null);
43
+ const billboardRef = useRef(null);
44
+ const smokeRef = useRef(null);
45
+ const haloRef = useRef(null);
46
+ const emberRef = useRef(null);
47
+ useObjectReady(groupRef, settings, onObjectReady, onObjectDispose);
48
+ useLightShadow(lightRef, config.shadow);
49
+ const { merged } = useLightConfig(groupRef, lightRef, id, config, animations, mouseMove);
50
+ const body = (config.body ?? 'pillar');
51
+ const bodyHeight = num(config.height, 0.14);
52
+ const bodyRadius = num(config.radius, 0.028);
53
+ const meltAmount = num(config.melt, 0.45);
54
+ // The wick's curl had been aimed by the draught-angle knob. That knob is
55
+ // gone, so it comes from the object's own id: stable across frames, and two
56
+ // candles side by side do not curl the same way.
57
+ const wickAngle = useMemo(() => {
58
+ let h = 2166136261;
59
+ for (const ch of String(id ?? 'candle'))
60
+ h = Math.imul(h ^ ch.charCodeAt(0), 16777619);
61
+ return ((h >>> 0) % 1000) / 1000 * Math.PI * 2;
62
+ }, [id]);
63
+ const flameHeight = Math.max(num(config.flameHeight, 0.055), 0.004);
64
+ const flameWidth = Math.max(num(config.flameWidth, 1), 0.15);
65
+ const flameMode = (config.flameMode ?? 'volumetric');
66
+ const candle = useMemo(() => buildCandle(body, bodyHeight, bodyRadius, meltAmount, wickAngle), [body, bodyHeight, bodyRadius, meltAmount, wickAngle]);
67
+ useEffect(() => () => candle.dispose(), [candle]);
68
+ const quads = useMemo(() => ({
69
+ box: new BoxGeometry(1, 1, 1),
70
+ quad: new PlaneGeometry(2, 2),
71
+ }), []);
72
+ useEffect(() => () => { quads.box.dispose(); quads.quad.dispose(); }, [quads]);
73
+ const mats = useMemo(() => createCandleMaterials(), []);
74
+ useEffect(() => () => disposeCandleMaterials(mats), [mats]);
75
+ const motion = useRef(createFlameMotion(config.on ?? true));
76
+ const scratch = useMemo(() => ({
77
+ body: new Color(), core: new Color(), fringe: new Color(), tint: new Color(), cam: new Vector3(),
78
+ }), []);
79
+ const power = useRef({ base: config.intensity ?? 1, written: Number.NaN });
80
+ // The light owns its materials and its shadow flags; the wax sets its own.
81
+ useEffect(() => {
82
+ const group = groupRef.current;
83
+ if (!group)
84
+ return;
85
+ group.userData[SELF_OWNED_MATERIAL_KEY] = true;
86
+ return () => { delete group.userData[SELF_OWNED_MATERIAL_KEY]; };
87
+ }, []);
88
+ // The wax is centimetres from the flame; the point shadow camera's default
89
+ // near of 0.5 would put the whole candle outside its own shadow map.
90
+ const shadowNear = config.shadow?.cameraNear;
91
+ useEffect(() => {
92
+ const light = lightRef.current;
93
+ if (!light || shadowNear !== undefined)
94
+ return;
95
+ light.shadow.camera.near = 0.01;
96
+ light.shadow.camera.updateProjectionMatrix();
97
+ }, [shadowNear]);
98
+ // Wax colour and thickness track the body, not the frame loop.
99
+ useEffect(() => {
100
+ mats.wax.color.set(config.waxColor || '#f3e7d2');
101
+ mats.melt.color.set(config.waxColor || '#f3e7d2');
102
+ mats.wax.thickness = bodyRadius * 2;
103
+ mats.wax.attenuationDistance = Math.max(bodyRadius * 2.2, 0.01);
104
+ }, [mats, config.waxColor, bodyRadius]);
105
+ const lightColor = useMemo(() => ((config.colorMode ?? 'temperature') === 'color'
106
+ ? new Color(config.color || '#ffb46b')
107
+ : kelvinToColor(config.temperature ?? 1850)), [config.colorMode, config.color, config.temperature]);
108
+ // The box has to hold the flame at this draught: wind both stretches it and
109
+ // blows it sideways, and a flame clipped by its own bounding box is the
110
+ // ugliest failure this component has. Sized from the committed draught, so
111
+ // animating that knob moves the flame inside a box that already fits.
112
+ const draughtBox = Math.min(Math.max(num(config.draught, 0.08), 0), 1);
113
+ const boxWideF = BOX_WIDE + draughtBox * 1.6;
114
+ const boxTallF = BOX_TALL + draughtBox * 1.1;
115
+ const boxH = flameHeight * boxTallF;
116
+ const boxW = flameHeight * boxWideF;
117
+ const emberPos = useMemo(() => wickTipPosition(candle, wickAngle, bodyHeight, bodyRadius, body, meltAmount), [candle, wickAngle, bodyHeight, bodyRadius, body, meltAmount]);
118
+ useFrame(({ clock, camera }, delta) => {
119
+ const light = lightRef.current;
120
+ const group = groupRef.current;
121
+ if (!light || !group)
122
+ return;
123
+ const cfg = merged.current;
124
+ if (light.intensity !== power.current.written)
125
+ power.current.base = light.intensity;
126
+ const t = clock.elapsedTime;
127
+ const m = stepFlameMotion(motion.current, {
128
+ flicker: clamp01(num(cfg.flicker, 0.5)),
129
+ movement: clamp01(num(cfg.movement, 1)),
130
+ flickerHz: num(cfg.flickerHz, 11),
131
+ draught: clamp01(num(cfg.draught, 0.08)),
132
+ ignite: Math.max(num(cfg.ignite, 1.6), 0),
133
+ on: cfg.on ?? true,
134
+ }, delta);
135
+ // --- the light -----------------------------------------------------
136
+ light.intensity = power.current.base * m.output;
137
+ power.current.written = light.intensity;
138
+ // Colour: rebuilt from the config each frame (a lerp onto the light's own
139
+ // colour compounds), then pulled a little warmer as it dims, the way a
140
+ // cooling flame does.
141
+ const tint = scratch.tint;
142
+ if ((cfg.colorMode ?? 'temperature') === 'color')
143
+ tint.set(cfg.color || '#ffb46b');
144
+ else
145
+ kelvinToColor(num(cfg.temperature, 1850) * (0.86 + 0.14 * m.output), tint);
146
+ light.color.copy(tint);
147
+ // The luminous centroid sits low in the flame, and leans with it.
148
+ const cy = flameHeight * 0.42 * m.level * m.stretch;
149
+ const lean = Math.pow(0.42, 1.7);
150
+ light.position.set(m.leanX * flameHeight * lean, candle.wickTop + cy, m.leanZ * flameHeight * lean);
151
+ const waxLight = waxLightRef.current;
152
+ if (waxLight) {
153
+ waxLight.intensity = power.current.base * m.output * clamp01(num(cfg.waxGlow, 0.3)) * 0.22;
154
+ waxLight.color.copy(tint);
155
+ }
156
+ // --- the flame ------------------------------------------------------
157
+ const active = flameMode === 'billboard' ? mats.flameBillboard : mats.flame;
158
+ const u = active.uniforms;
159
+ u.uTime.value = t;
160
+ u.uLevel.value = m.level;
161
+ u.uPulse.value = m.pulse;
162
+ u.uLean.value.set(m.leanX * (flameHeight / boxW), m.leanZ * (flameHeight / boxW));
163
+ u.uStretch.value = m.stretch * m.pulse;
164
+ u.uTurb.value = m.turbulence;
165
+ u.uPhase.value = m.phaseOut;
166
+ u.uSway.value = m.sway * (flameHeight / boxW);
167
+ u.uSwayDir.value.set(m.swayX, m.swayZ);
168
+ u.uSeed.value = m.noiseSeed;
169
+ u.uScroll.value = m.scroll;
170
+ u.uShed.value = m.shed;
171
+ u.uShedPhase.value = m.shedPhase;
172
+ u.uDetach.value = m.detach;
173
+ u.uGap.value = 0.05;
174
+ // A percentage of the flame's height, as the knob is named.
175
+ u.uBlueHeight.value = clamp01(num(cfg.blueBasePercentage, 16) / 100);
176
+ u.uRadius.value = (0.193 / boxWideF) * flameWidth;
177
+ u.uTop.value = 1 / boxTallF;
178
+ u.uBlue.value = clamp01(num(cfg.blue, 0.7));
179
+ u.uBlueBias.value = m.blueBias;
180
+ // Emission and absorption are integrated per object-space unit, and a
181
+ // wider box makes the flame smaller in those units — so both have to be
182
+ // scaled by the box or a blown flame quietly goes dim and thin.
183
+ const boxK = boxWideF / BOX_WIDE;
184
+ u.uSoot.value = clamp01(num(cfg.soot, 0.25)) * boxK;
185
+ u.uGain.value = num(cfg.flameGain, 5) * boxK;
186
+ u.uWhite.value = num(cfg.flameWhite, 3.5);
187
+ // Keep the step density roughly constant as the box grows with the wind.
188
+ u.uSteps.value = Math.min(num(cfg.flameSteps, 24) * Math.sqrt(boxK), 40);
189
+ // The body colour is the greybody at this temperature; the core is that
190
+ // pushed most of the way to white, which is what the hottest soot does.
191
+ scratch.body.copy(tint);
192
+ u.uBody.value.copy(scratch.body);
193
+ u.uCore.value.copy(scratch.core.copy(tint).lerp(WHITE, 0.72));
194
+ // Cooling soot at the fringe and the tip: a deep red long before it
195
+ // stops glowing.
196
+ u.uFringe.value.copy(scratch.fringe.copy(tint).lerp(EMBER, 0.78));
197
+ const flame = flameRef.current;
198
+ const billboard = billboardRef.current;
199
+ const lit = m.level > 0.002;
200
+ if (flame) {
201
+ flame.visible = lit && flameMode === 'volumetric';
202
+ if (flame.visible) {
203
+ // The raymarch runs in the box's own space, so the camera goes there.
204
+ flame.updateMatrixWorld();
205
+ const cam = scratch.cam.copy(camera.position);
206
+ flame.worldToLocal(cam);
207
+ mats.flame.uniforms.uCamera.value.copy(cam);
208
+ }
209
+ }
210
+ if (billboard)
211
+ billboard.visible = lit && flameMode === 'billboard';
212
+ // --- wick ember, halo, smoke ---------------------------------------
213
+ const ember = emberRef.current;
214
+ if (ember) {
215
+ ember.visible = lit;
216
+ mats.ember.color.copy(tint).multiplyScalar(0.2 + m.output * 1.1);
217
+ }
218
+ const halo = haloRef.current;
219
+ if (halo) {
220
+ const haloAmount = Math.max(num(cfg.halo, 1), 0);
221
+ halo.visible = haloAmount > 0.001 && lit;
222
+ halo.position.set(light.position.x, light.position.y, light.position.z);
223
+ mats.halo.uniforms.uColor.value.copy(tint);
224
+ mats.halo.uniforms.uStrength.value = haloAmount * m.output * 0.16;
225
+ // Tight: the quad's half-extent is uRadius in world units, so a value
226
+ // near the flame's own height already covers a lot of screen up close.
227
+ mats.halo.uniforms.uRadius.value = flameHeight * 0.7;
228
+ }
229
+ const smoke = smokeRef.current;
230
+ if (smoke) {
231
+ smoke.visible = m.smokeAge >= 0 && (cfg.smoke ?? true) !== false;
232
+ if (smoke.visible) {
233
+ mats.smoke.uniforms.uTime.value = t;
234
+ mats.smoke.uniforms.uAge.value = m.smokeAge;
235
+ // Face the camera about Y only: a rising column should not tip.
236
+ const c = scratch.cam.copy(camera.position);
237
+ smoke.parent?.worldToLocal(c);
238
+ smoke.rotation.y = Math.atan2(c.x - smoke.position.x, c.z - smoke.position.z);
239
+ }
240
+ }
241
+ });
242
+ const showBody = body !== 'none';
243
+ return (_jsxs("group", { ref: groupRef, children: [_jsx("pointLight", { ref: lightRef, color: lightColor, intensity: config.intensity ?? 1, decay: config.decay ?? 2, distance: config.distance ?? 0 }), showBody && (_jsx("pointLight", { ref: waxLightRef, position: [0, Math.max(candle.wickTop - bodyRadius * 0.6, 0), 0], intensity: 0, decay: 2, distance: Math.max(bodyHeight, 0.02) })), showBody && (_jsxs(_Fragment, { children: [_jsx("mesh", { geometry: candle.wall, material: mats.wax, castShadow: true, receiveShadow: true }), _jsx("mesh", { geometry: candle.pool, material: mats.melt, castShadow: true, receiveShadow: true }), _jsx("mesh", { geometry: candle.base, material: mats.wax }), _jsx("mesh", { geometry: candle.wick, material: mats.wick }), _jsx("mesh", { ref: emberRef, geometry: candle.ember, material: mats.ember, position: emberPos })] })), _jsx("mesh", { ref: flameRef, geometry: quads.box, material: mats.flame, scale: [boxW, boxH, boxW], position: [0, candle.wickTop + boxH / 2, 0], renderOrder: 4, frustumCulled: false }), _jsx("mesh", { ref: billboardRef, geometry: quads.quad, material: mats.flameBillboard, scale: [boxW, boxH, 1], position: [0, candle.wickTop + boxH / 2, 0], renderOrder: 4, frustumCulled: false }), _jsx("mesh", { ref: smokeRef, geometry: quads.quad, material: mats.smoke,
244
+ // Half-height is the scale, so the quad's foot lands on the wick.
245
+ scale: [flameHeight * 1.6, flameHeight * 2.4, 1], position: [0, candle.wickTop + flameHeight * 2.4, 0], renderOrder: 5, frustumCulled: false }), _jsx("mesh", { ref: haloRef, geometry: quads.quad, material: mats.halo, renderOrder: 6, frustumCulled: false })] }));
246
+ };
247
+ const WHITE = new Color(1, 1, 1);
248
+ const EMBER = new Color(1, 0.22, 0.02);
249
+ export default Candle;
@@ -0,0 +1,23 @@
1
+ import { CircleGeometry, CylinderGeometry, LatheGeometry, SphereGeometry, TubeGeometry } from 'three';
2
+ import type { BufferGeometry } from 'three';
3
+ export type CandleBody = 'pillar' | 'taper' | 'tealight' | 'none';
4
+ export declare const CANDLE_BODIES: CandleBody[];
5
+ export interface CandleGeometry {
6
+ /** Outer wall, open at both ends — the caps are the pool and the base. */
7
+ wall: CylinderGeometry;
8
+ /** The melt pool: rim down into the well. */
9
+ pool: LatheGeometry;
10
+ base: CircleGeometry;
11
+ wick: TubeGeometry;
12
+ ember: SphereGeometry;
13
+ /** Where the wick's tip is, and so where the flame stands. */
14
+ wickTop: number;
15
+ /** Radius at the top of the body, for the pool and the halo. */
16
+ topRadius: number;
17
+ dispose(): void;
18
+ }
19
+ export declare function buildCandle(body: CandleBody, heightRaw: number, radiusRaw: number, meltRaw: number, leanAngle: number): CandleGeometry;
20
+ /** The curled tip, in object space, for the ember. */
21
+ export declare function wickTipPosition(g: CandleGeometry, leanAngle: number, height: number, radius: number, body: CandleBody, melt: number): [number, number, number];
22
+ export type { BufferGeometry };
23
+ //# sourceMappingURL=candleGeometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candleGeometry.d.ts","sourceRoot":"","sources":["../../../src/components/candle/candleGeometry.ts"],"names":[],"mappings":"AAQA,OAAO,EACe,cAAc,EAAE,gBAAgB,EAAE,aAAa,EACjE,cAAc,EAAE,YAAY,EAC/B,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE5C,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;AAClE,eAAO,MAAM,aAAa,EAAE,UAAU,EAA4C,CAAC;AAEnF,MAAM,WAAW,cAAc;IAC3B,0EAA0E;IAC1E,IAAI,EAAE,gBAAgB,CAAC;IACvB,6CAA6C;IAC7C,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,cAAc,CAAC;IACtB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,IAAI,IAAI,CAAC;CACnB;AASD,wBAAgB,WAAW,CACvB,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAClB,cAAc,CAmDhB;AAED,sDAAsD;AACtD,wBAAgB,eAAe,CAAC,CAAC,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAO9J;AAED,YAAY,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,73 @@
1
+ // =============================================================================
2
+ // candleGeometry — the wax, the melt pool and the wick
3
+ // =============================================================================
4
+ //
5
+ // Rebuilt only when a structural knob changes. The melt pool is a lathe rather
6
+ // than a flat disc because the well is where the candle reads as lit from
7
+ // inside: the wax is thinnest there, so that is where the flame shows through.
8
+ //
9
+ import { CatmullRomCurve3, CircleGeometry, CylinderGeometry, LatheGeometry, SphereGeometry, TubeGeometry, Vector2, Vector3, } from 'three';
10
+ export const CANDLE_BODIES = ['pillar', 'taper', 'tealight', 'none'];
11
+ /** Proportions per body style, relative to the height and radius knobs. */
12
+ const STYLE = {
13
+ pillar: { top: 1, height: 1, radius: 1 },
14
+ taper: { top: 0.66, height: 1.9, radius: 0.55 },
15
+ tealight: { top: 1, height: 0.3, radius: 1.5 },
16
+ };
17
+ export function buildCandle(body, heightRaw, radiusRaw, meltRaw, leanAngle) {
18
+ const style = STYLE[(body === 'none' ? 'pillar' : body)];
19
+ const height = Math.max(heightRaw, 0.005) * style.height;
20
+ const rBase = Math.max(radiusRaw, 0.003) * style.radius;
21
+ const rTop = rBase * style.top;
22
+ const melt = Math.min(Math.max(meltRaw, 0), 1);
23
+ // How deep the well goes, and how far down the wick sits in it.
24
+ const well = Math.min(rTop * (0.12 + melt * 0.5), height * 0.5);
25
+ const wallTop = height;
26
+ const wall = new CylinderGeometry(rTop, rBase, wallTop, 48, 1, true);
27
+ wall.translate(0, wallTop / 2, 0);
28
+ // Inner-bottom → outer-top, so the normals face up and out of the dish.
29
+ const pool = new LatheGeometry([
30
+ new Vector2(0.0015, wallTop - well),
31
+ new Vector2(rTop * 0.34, wallTop - well * 0.86),
32
+ new Vector2(rTop * 0.68, wallTop - well * 0.42),
33
+ new Vector2(rTop * 0.93, wallTop - well * 0.08),
34
+ new Vector2(rTop, wallTop),
35
+ ], 48);
36
+ pool.computeVertexNormals();
37
+ const base = new CircleGeometry(rBase, 48);
38
+ base.rotateX(Math.PI / 2);
39
+ // A used wick is bent over and curled, not a straight bristle.
40
+ const wickBase = wallTop - well;
41
+ const wickLen = Math.max(rTop * 0.55, height * 0.045);
42
+ const dx = Math.cos(leanAngle), dz = Math.sin(leanAngle);
43
+ const wick = new TubeGeometry(new CatmullRomCurve3([
44
+ new Vector3(0, wickBase - wickLen * 0.25, 0),
45
+ new Vector3(dx * wickLen * 0.06, wickBase + wickLen * 0.35, dz * wickLen * 0.06),
46
+ new Vector3(dx * wickLen * 0.34, wickBase + wickLen * 0.78, dz * wickLen * 0.34),
47
+ new Vector3(dx * wickLen * 0.72, wickBase + wickLen * 0.92, dz * wickLen * 0.72),
48
+ ]), 22, Math.max(wickLen * 0.055, 0.0004), 6, false);
49
+ const ember = new SphereGeometry(Math.max(wickLen * 0.06, 0.0004), 10, 8);
50
+ return {
51
+ wall, pool, base, wick, ember,
52
+ // The flame stands part way up the wick, clear of the pool's rim — the
53
+ // blue base is the first thing lost if it sits down inside the well.
54
+ wickTop: wickBase + wickLen * 0.8,
55
+ topRadius: rTop,
56
+ dispose() {
57
+ wall.dispose();
58
+ pool.dispose();
59
+ base.dispose();
60
+ wick.dispose();
61
+ ember.dispose();
62
+ },
63
+ };
64
+ }
65
+ /** The curled tip, in object space, for the ember. */
66
+ export function wickTipPosition(g, leanAngle, height, radius, body, melt) {
67
+ const style = STYLE[(body === 'none' ? 'pillar' : body)];
68
+ const h = Math.max(height, 0.005) * style.height;
69
+ const rTop = Math.max(radius, 0.003) * style.radius * style.top;
70
+ const well = Math.min(rTop * (0.12 + Math.min(Math.max(melt, 0), 1) * 0.5), h * 0.5);
71
+ const wickLen = Math.max(rTop * 0.55, h * 0.045);
72
+ return [Math.cos(leanAngle) * wickLen * 0.72, h - well + wickLen * 0.92, Math.sin(leanAngle) * wickLen * 0.72];
73
+ }
@@ -0,0 +1,24 @@
1
+ import { MeshBasicMaterial, MeshPhysicalMaterial, ShaderMaterial } from 'three';
2
+ export type FlameMode = 'volumetric' | 'billboard';
3
+ export declare const FLAME_MODES: FlameMode[];
4
+ export interface CandleMaterials {
5
+ /** Raymarched flame volume. */
6
+ flame: ShaderMaterial;
7
+ /** The same field on a camera-facing quad, for crowds of candles. */
8
+ flameBillboard: ShaderMaterial;
9
+ /** Smoke wisp after it is snuffed. */
10
+ smoke: ShaderMaterial;
11
+ /** The candle body. */
12
+ wax: MeshPhysicalMaterial;
13
+ /** The melt pool: wetter, glossier, and it catches the flame above it. */
14
+ melt: MeshPhysicalMaterial;
15
+ /** Carbonised wick. */
16
+ wick: MeshBasicMaterial;
17
+ /** Glowing wick tip. */
18
+ ember: MeshBasicMaterial;
19
+ /** Tight camera-facing wash. */
20
+ halo: ShaderMaterial;
21
+ }
22
+ export declare function createCandleMaterials(): CandleMaterials;
23
+ export declare function disposeCandleMaterials(m: CandleMaterials): void;
24
+ //# sourceMappingURL=candleMaterials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candleMaterials.d.ts","sourceRoot":"","sources":["../../../src/components/candle/candleMaterials.ts"],"names":[],"mappings":"AAoBA,OAAO,EAC4C,iBAAiB,EAChE,oBAAoB,EAAE,cAAc,EACvC,MAAM,OAAO,CAAC;AAEf,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,WAAW,CAAC;AACnD,eAAO,MAAM,WAAW,EAAE,SAAS,EAAgC,CAAC;AAEpE,MAAM,WAAW,eAAe;IAC5B,+BAA+B;IAC/B,KAAK,EAAE,cAAc,CAAC;IACtB,qEAAqE;IACrE,cAAc,EAAE,cAAc,CAAC;IAC/B,sCAAsC;IACtC,KAAK,EAAE,cAAc,CAAC;IACtB,uBAAuB;IACvB,GAAG,EAAE,oBAAoB,CAAC;IAC1B,0EAA0E;IAC1E,IAAI,EAAE,oBAAoB,CAAC;IAC3B,uBAAuB;IACvB,IAAI,EAAE,iBAAiB,CAAC;IACxB,wBAAwB;IACxB,KAAK,EAAE,iBAAiB,CAAC;IACzB,gCAAgC;IAChC,IAAI,EAAE,cAAc,CAAC;CACxB;AAuYD,wBAAgB,qBAAqB,IAAI,eAAe,CAmBvD;AAED,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,eAAe,GAAG,IAAI,CAS/D"}