@3driseai/3drise-viewer 0.1.23 → 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.
@@ -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;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"}
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"}
@@ -8,6 +8,7 @@ import LightBulb from './lightBulb/LightBulb.js';
8
8
  import SpotBeam from './spotBeam/SpotBeam.js';
9
9
  import LedArray from './ledArray/LedArray.js';
10
10
  import FluorescentTube from './fluorescentTube/FluorescentTube.js';
11
+ import Candle from './candle/Candle.js';
11
12
  const DirectionalLightR3F = ({ config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
12
13
  const lightRef = useRef(null);
13
14
  useObjectReady(lightRef, settings, onObjectReady, onObjectDispose);
@@ -65,6 +66,9 @@ const LightObject = ({ createdObject, onObjectReady, onObjectDispose }) => {
65
66
  return _jsx(SpotLightR3F, { config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
66
67
  case 'point':
67
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 });
68
72
  // An array of LED dies: instant on, PWM ripple, optional diffuser.
69
73
  case 'ledArray':
70
74
  return _jsx(LedArray, { id: createdObject.id, config: config, animations: animations, mouseMove: mouseMove, settings: createdObject, onObjectReady: onObjectReady, onObjectDispose: onObjectDispose });
@@ -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"}
@@ -0,0 +1,433 @@
1
+ // =============================================================================
2
+ // candleMaterials — the flame volume, the wax, the wick and the halo
3
+ // =============================================================================
4
+ //
5
+ // The flame is raymarched. A candle flame is not one glowing blob: it has three
6
+ // zones, and rendering only the third is what makes fake candles look fake.
7
+ //
8
+ // - a blue base around the wick, where there is plenty of oxygen. That light
9
+ // is chemiluminescence from CH and C2 radicals, NOT blackbody, so it is a
10
+ // separate additive term rather than the cold end of a temperature ramp;
11
+ // - a dark inner zone just above the wick: vaporised wax, dense but not yet
12
+ // burning, so high density and almost no emission;
13
+ // - the luminous yellow body: incandescent soot, a greybody, which is what the
14
+ // temperature ramp actually describes.
15
+ //
16
+ // The viewer has no tone mapping, so the shader carries its own knee (extended
17
+ // Reinhard, per channel). Without it the whole flame clips to a white blob and
18
+ // the blue base disappears entirely — the single biggest risk to this looking
19
+ // real, so the knee is not optional.
20
+ //
21
+ import { AdditiveBlending, BackSide, Color, DoubleSide, MeshBasicMaterial, MeshPhysicalMaterial, ShaderMaterial, Vector2, Vector3, } from 'three';
22
+ export const FLAME_MODES = ['volumetric', 'billboard'];
23
+ const OUTPUT = /* glsl */ `
24
+ #include <tonemapping_fragment>
25
+ #include <colorspace_fragment>
26
+ `;
27
+ /** Value noise + 3-octave fbm, shared by the flame and the smoke. */
28
+ const NOISE = /* glsl */ `
29
+ float hash13(vec3 p) {
30
+ p = fract(p * 0.1031);
31
+ p += dot(p, p.zyx + 31.32);
32
+ return fract((p.x + p.y) * p.z);
33
+ }
34
+ float vnoise(vec3 p) {
35
+ vec3 i = floor(p), f = fract(p);
36
+ f = f * f * (3.0 - 2.0 * f);
37
+ float n000 = hash13(i), n100 = hash13(i + vec3(1,0,0));
38
+ float n010 = hash13(i + vec3(0,1,0)), n110 = hash13(i + vec3(1,1,0));
39
+ float n001 = hash13(i + vec3(0,0,1)), n101 = hash13(i + vec3(1,0,1));
40
+ float n011 = hash13(i + vec3(0,1,1)), n111 = hash13(i + vec3(1,1,1));
41
+ return mix(mix(mix(n000, n100, f.x), mix(n010, n110, f.x), f.y),
42
+ mix(mix(n001, n101, f.x), mix(n011, n111, f.x), f.y), f.z);
43
+ }
44
+ float fbm(vec3 p) {
45
+ return vnoise(p) * 0.55 + vnoise(p * 2.03) * 0.3 + vnoise(p * 4.11) * 0.15;
46
+ }
47
+ `;
48
+ /**
49
+ * The flame's shape and emission, in the box's own space (-0.5..0.5 on each
50
+ * axis, the flame standing on the bottom face). Shared by both modes so the
51
+ * billboard is the same flame, only cheaper.
52
+ */
53
+ const FLAME_FIELD = /* glsl */ `
54
+ uniform float uTime;
55
+ uniform float uLevel; // 0 out, 1 established
56
+ uniform float uPulse; // buoyancy oscillation, around 1
57
+ uniform float uPhase; // that oscillation's phase, for the travelling wave
58
+ uniform vec2 uLean; // steady wind bend, box units
59
+ uniform vec2 uSwayDir; // the plane it is swaying in; drifts
60
+ uniform float uSway; // amplitude of the undulation running up the flame
61
+ uniform float uSeed; // per candle, so two of them never match
62
+ uniform float uScroll; // how fast the eddies travel up. 0 freezes them
63
+ uniform float uShed; // how hard the neck pinches before it sheds
64
+ uniform float uShedPhase;
65
+ uniform float uDetach; // how far wind has lifted the root off the wick
66
+ uniform float uStretch;
67
+ uniform float uTurb;
68
+ uniform float uRadius; // flame half-width at its widest, box units
69
+ uniform float uTop; // where the tip sits in the box, 0..1
70
+ uniform float uGap; // quench gap: the flame does not touch the wick
71
+ uniform float uBlueHeight; // fraction of the flame that reads blue
72
+ uniform float uBlue;
73
+ uniform float uBlueBias;
74
+ uniform float uSoot;
75
+ uniform vec3 uFringe; // cooling soot at the edges and the tip
76
+ uniform vec3 uBody; // greybody at this temperature
77
+ uniform vec3 uCore; // white-hot core
78
+ uniform vec3 uBlueColor;
79
+ uniform float uGain;
80
+ uniform float uWhite; // knee white point
81
+
82
+ /** Teardrop: rounded at the base, widest about a quarter up, pinching out. */
83
+ float profile(float y) {
84
+ return pow(max(y, 0.0), 0.38) * pow(max(1.0 - y, 0.0), 1.0) / 0.40;
85
+ }
86
+
87
+ /**
88
+ * The flame's centreline. Wind bends it, but the part that makes a candle
89
+ * look alive is the second term: the oscillation's phase lags with height,
90
+ * so a wave travels UP the flame and it undulates like a ribbon instead of
91
+ * tilting rigidly. A flame that only leans is the giveaway of a fake one.
92
+ */
93
+ vec2 centreline(float y) {
94
+ vec2 bend = uLean * pow(y, 1.6);
95
+ float ph = uPhase - y * 4.2;
96
+ // Elliptical, in a plane that drifts: a flame that rocks along one fixed
97
+ // axis reads as a mechanism.
98
+ vec2 d1 = uSwayDir;
99
+ vec2 d2 = vec2(-uSwayDir.y, uSwayDir.x);
100
+ vec2 wave = (d1 * sin(ph) + d2 * sin(ph * 0.61 + uSeed) * 0.55)
101
+ * uSway * smoothstep(0.04, 0.7, y);
102
+ return bend + wave;
103
+ }
104
+
105
+ /** Density, plus the two coordinates the emission needs. */
106
+ float flameDensity(vec3 p, out float y, out float r) {
107
+ float top = min(uTop * uLevel * uStretch, 1.0);
108
+ y = (p.y + 0.5) / max(top, 1e-3);
109
+ r = 999.0;
110
+ if (y <= 0.0 || y >= 1.0 || uLevel <= 0.001) return 0.0;
111
+
112
+ vec2 q = p.xz - centreline(y);
113
+
114
+ // The neck: a pinch that travels up the flame and repeats, so the tip
115
+ // necks down and sheds a parcel of gas rather than sitting still.
116
+ float pinchAt = 0.42 + fract(uShedPhase) * 0.62;
117
+ float pinch = uShed * exp(-pow((y - pinchAt) / 0.11, 2.0));
118
+ float width = uRadius * uLevel * profile(y) * (1.0 - pinch);
119
+ if (width <= 1e-4) return 0.0;
120
+ r = length(q) / width;
121
+ if (r > 2.2) return 0.0;
122
+
123
+ // Wind lifts the root clear of the wick before it blows the flame out.
124
+ float root = smoothstep(0.0, 0.04 + uDetach * 0.26, y);
125
+
126
+ // Eddies travel upward, and the gas accelerates as it rises, so the
127
+ // noise coordinate is compressed with height: features stretch as they
128
+ // go, which is what the upper half of a real flame does.
129
+ float turb = uTurb * smoothstep(0.08, 0.8, y);
130
+ float ny = pow(y, 0.72) * 6.5 - uTime * uScroll + uSeed * 13.0;
131
+ float n = (fbm(vec3(q * 24.0 + uSeed * 2.7, ny)) - 0.5) * 2.0;
132
+ return smoothstep(1.0, 0.42, r + n * turb) * root;
133
+ }
134
+
135
+ /** The blue collar: low down and on the oxygen-rich surface, wrapping under
136
+ the yellow rather than sitting inside it. */
137
+ float collar(float y, float r) {
138
+ // As the blue reaches further up it also fills in: a flame with plenty of
139
+ // oxygen is blue through, not just at its skin.
140
+ float shell = smoothstep(0.60 * (1.0 - uBlueHeight * 0.85), 1.05, r);
141
+ return smoothstep(max(uBlueHeight, 0.001), 0.0, y) * shell;
142
+ }
143
+
144
+ vec3 flameEmission(float d, float y, float r) {
145
+ // The luminous zone starts ABOVE the wick — there is a quench layer the
146
+ // flame does not reach into, and the gap is clearly visible in a macro
147
+ // shot as a dark collar under the yellow.
148
+ float yl = clamp((y - uGap) / max(1.0 - uGap, 1e-3), 0.0, 1.0);
149
+
150
+ // Soot has to be carried up and heated before it glows, so the luminous
151
+ // zone is the UPPER half. Getting this backwards makes a flame read as a
152
+ // glowing blob on a stick.
153
+ float hot = smoothstep(0.05, 0.42, yl) * (1.0 - smoothstep(0.70, 1.06, yl));
154
+ // The dark zone: unburnt vapour on the axis, just above the wick.
155
+ float dark = (1.0 - smoothstep(0.0, 0.78, r)) * (1.0 - smoothstep(0.02, 0.34, yl));
156
+ float lum = d * (0.10 + 2.0 * hot) * (1.0 - 0.92 * dark)
157
+ * smoothstep(0.0, max(0.3, uBlueHeight * 1.15), yl);
158
+
159
+ // Three stops, not two. Soot cools at the outer fringe and at the tip,
160
+ // and drops to a deep red long before it stops glowing — leaving that
161
+ // out is what makes a flame look like an airbrushed gradient.
162
+ // Mostly radial: the fringe is the cool outside of the sheet, not a
163
+ // horizontal stripe across it.
164
+ float cool = clamp(smoothstep(0.58, 1.05, r) * 0.8 + smoothstep(0.80, 1.1, yl) * 0.45, 0.0, 1.0);
165
+ vec3 col = mix(uBody, uFringe, cool);
166
+ col = mix(col, uCore, smoothstep(0.5, 1.4, lum));
167
+
168
+ // The blue is faint next to the yellow and the first thing the knee
169
+ // eats, so it is weighted up rather than left proportionate and invisible.
170
+ float blue = uBlue * uBlueBias * collar(y, r) * d;
171
+ return col * lum + uBlueColor * blue * 7.5;
172
+ }
173
+
174
+ /** Extended Reinhard, per channel: the core desaturates to white, the rest
175
+ stays where it was. Stands in for the tone mapping the viewer does not do. */
176
+ vec3 knee(vec3 c) {
177
+ float w = max(uWhite, 1.0);
178
+ return c * (1.0 + c / (w * w)) / (1.0 + c);
179
+ }
180
+ `;
181
+ const FLAME_UNIFORMS = () => ({
182
+ uTime: { value: 0 },
183
+ uLevel: { value: 1 },
184
+ uPulse: { value: 1 },
185
+ uPhase: { value: 0 },
186
+ uLean: { value: new Vector2(0, 0) },
187
+ uSwayDir: { value: new Vector2(1, 0) },
188
+ uSway: { value: 0.02 },
189
+ uSeed: { value: 0 },
190
+ uScroll: { value: 1.4 },
191
+ uShed: { value: 0.18 },
192
+ uShedPhase: { value: 0 },
193
+ uDetach: { value: 0 },
194
+ uGap: { value: 0.05 },
195
+ uBlueHeight: { value: 0.16 },
196
+ uStretch: { value: 1 },
197
+ uTurb: { value: 0.3 },
198
+ uRadius: { value: 0.16 },
199
+ uTop: { value: 0.78 },
200
+ uBlue: { value: 0.7 },
201
+ uBlueBias: { value: 1 },
202
+ uSoot: { value: 0.25 },
203
+ uFringe: { value: new Color(1, 0.26, 0.04) },
204
+ uBody: { value: new Color(1, 0.62, 0.18) },
205
+ uCore: { value: new Color(1, 0.95, 0.82) },
206
+ uBlueColor: { value: new Color(0.14, 0.42, 1) },
207
+ uGain: { value: 5 },
208
+ uWhite: { value: 3.5 },
209
+ uSteps: { value: 24 },
210
+ /** Camera in the box's object space; only the raymarched mode reads it. */
211
+ uCamera: { value: new Vector3() },
212
+ });
213
+ function flameMaterial() {
214
+ return new ShaderMaterial({
215
+ uniforms: FLAME_UNIFORMS(),
216
+ vertexShader: /* glsl */ `
217
+ varying vec3 vLocal;
218
+ void main() {
219
+ vLocal = position;
220
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
221
+ }`,
222
+ fragmentShader: /* glsl */ `
223
+ uniform vec3 uCamera; // camera in the box's object space
224
+ uniform float uSteps;
225
+ varying vec3 vLocal;
226
+ ${NOISE}
227
+ ${FLAME_FIELD}
228
+
229
+ void main() {
230
+ // Back faces are rendered, so this fragment is where the ray
231
+ // leaves the box; the entry comes from the slab intersection and
232
+ // is clamped to 0 so the camera can sit inside.
233
+ vec3 ro = uCamera;
234
+ vec3 rd = vLocal - ro;
235
+ float far = length(rd);
236
+ rd /= max(far, 1e-6);
237
+
238
+ // March the flame's own bounds, not the whole box. The box has to
239
+ // be big enough for the lean and the undulation, but a ray that
240
+ // spends half its steps on empty air bands badly where the flame
241
+ // is thin — which is exactly where the blue collar lives.
242
+ float topB = min(uTop * uLevel * uStretch, 1.0);
243
+ float halfW = min(uRadius * uLevel + max(abs(uLean.x), abs(uLean.y)) + uSway + 0.03, 0.5);
244
+ vec3 bmin = vec3(-halfW, -0.5, -halfW);
245
+ vec3 bmax = vec3( halfW, -0.5 + topB, halfW);
246
+
247
+ vec3 inv = 1.0 / max(abs(rd), vec3(1e-6)) * sign(rd + 1e-9);
248
+ vec3 t0 = (bmin - ro) * inv;
249
+ vec3 t1 = (bmax - ro) * inv;
250
+ vec3 tlo = min(t0, t1), thi = max(t0, t1);
251
+ float near = max(max(tlo.x, tlo.y), max(tlo.z, 0.0));
252
+ float exit = min(min(thi.x, thi.y), thi.z);
253
+ far = min(far, exit);
254
+ if (near >= far) discard;
255
+
256
+ const int MAX_STEPS = 40;
257
+ float steps = clamp(uSteps, 8.0, float(MAX_STEPS));
258
+ float stepLen = (far - near) / steps;
259
+ // Dither the entry point: 20 steps through a small volume bands
260
+ // badly otherwise, and a flame shows it more than most things.
261
+ // A third of a step of interleaved gradient noise, on the pixel
262
+ // only. Full-width jitter trades banding for a diagonal moiré
263
+ // that is worse than what it fixes; a third is enough to break
264
+ // the rings now that the march is confined to the flame.
265
+ float ign = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));
266
+ float t = near + stepLen * (0.5 + (ign - 0.5) * 0.34);
267
+
268
+ vec3 acc = vec3(0.0);
269
+ float trans = 1.0;
270
+ for (int i = 0; i < MAX_STEPS; i++) {
271
+ if (float(i) >= steps || trans < 0.01) break;
272
+ vec3 p = ro + rd * t;
273
+ float y, r;
274
+ float d = flameDensity(p, y, r);
275
+ if (d > 0.002) {
276
+ acc += flameEmission(d, y, r) * stepLen * trans * uGain;
277
+ // Soot absorbs a little, which is what gives a flame a body
278
+ // rather than making it a pure additive ghost.
279
+ trans *= exp(-d * uSoot * stepLen * 14.0);
280
+ }
281
+ t += stepLen;
282
+ }
283
+ if (acc.r + acc.g + acc.b < 1e-4) discard;
284
+ gl_FragColor = vec4(knee(acc), 1.0);
285
+ ${OUTPUT}
286
+ }`,
287
+ transparent: true,
288
+ depthWrite: false,
289
+ side: BackSide,
290
+ blending: AdditiveBlending,
291
+ });
292
+ }
293
+ /** The same flame on one camera-facing quad: no depth, a fraction of the cost. */
294
+ function flameBillboardMaterial() {
295
+ return new ShaderMaterial({
296
+ uniforms: FLAME_UNIFORMS(),
297
+ vertexShader: /* glsl */ `
298
+ varying vec2 vUv;
299
+ void main() {
300
+ vUv = position.xy;
301
+ vec4 mv = modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
302
+ float s = length(modelMatrix[0].xyz);
303
+ mv.xy += position.xy * s * 0.5;
304
+ gl_Position = projectionMatrix * mv;
305
+ }`,
306
+ fragmentShader: /* glsl */ `
307
+ varying vec2 vUv;
308
+ ${NOISE}
309
+ ${FLAME_FIELD}
310
+ void main() {
311
+ // Three slices instead of a march: enough to keep the dark zone
312
+ // and the blue base, without the depth.
313
+ vec3 acc = vec3(0.0);
314
+ for (int i = 0; i < 3; i++) {
315
+ float z = (float(i) - 1.0) * 0.11;
316
+ float y, r;
317
+ vec3 p = vec3(vUv.x * 0.5, vUv.y * 0.5, z);
318
+ float d = flameDensity(p, y, r);
319
+ if (d > 0.002) acc += flameEmission(d, y, r) * uGain * 0.17;
320
+ }
321
+ if (acc.r + acc.g + acc.b < 1e-4) discard;
322
+ gl_FragColor = vec4(knee(acc), 1.0);
323
+ ${OUTPUT}
324
+ }`,
325
+ transparent: true,
326
+ depthWrite: false,
327
+ blending: AdditiveBlending,
328
+ });
329
+ }
330
+ /** The wisp left behind for a few seconds after it is snuffed. */
331
+ function smokeMaterial() {
332
+ return new ShaderMaterial({
333
+ uniforms: {
334
+ uTime: { value: 0 },
335
+ uAge: { value: 0 },
336
+ uColor: { value: new Color(0.6, 0.6, 0.62) },
337
+ },
338
+ // A plain quad, turned to face the camera about Y by the component. A
339
+ // view-space billboard would ignore the mesh's own height and park the
340
+ // wisp wherever the X scale put it.
341
+ vertexShader: /* glsl */ `
342
+ varying vec2 vUv;
343
+ void main() {
344
+ vUv = position.xy;
345
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
346
+ }`,
347
+ fragmentShader: /* glsl */ `
348
+ uniform float uTime;
349
+ uniform float uAge;
350
+ uniform vec3 uColor;
351
+ varying vec2 vUv;
352
+ ${NOISE}
353
+ void main() {
354
+ // The quad's bottom sits on the wick, so the wisp has to start at
355
+ // y = 0 or it detaches and floats in mid-air.
356
+ float y = vUv.y * 0.5 + 0.5;
357
+ float rise = clamp(uAge * 0.55, 0.0, 1.0);
358
+ float band = smoothstep(0.0, 0.03, y) * (1.0 - smoothstep(rise, rise + 0.4, y));
359
+ float wobble = (fbm(vec3(vUv * 3.0, uTime * 0.5)) - 0.5) * (0.1 + y * 0.55);
360
+ // Thin. Snuffing a candle gives a thread of smoke, not a plume.
361
+ float w = 0.02 + y * 0.15;
362
+ float a = band * exp(-pow((vUv.x - wobble) / w, 2.0));
363
+ a *= (1.0 - smoothstep(1.6, 3.4, uAge)) * 0.33;
364
+ if (a < 0.002) discard;
365
+ gl_FragColor = vec4(uColor, a);
366
+ ${OUTPUT}
367
+ }`,
368
+ transparent: true,
369
+ depthWrite: false,
370
+ });
371
+ }
372
+ function haloMaterial() {
373
+ return new ShaderMaterial({
374
+ uniforms: {
375
+ uColor: { value: new Color(1, 0.7, 0.35) },
376
+ uStrength: { value: 0 },
377
+ uRadius: { value: 0.3 },
378
+ },
379
+ vertexShader: /* glsl */ `
380
+ uniform float uRadius;
381
+ varying vec2 vUv;
382
+ void main() {
383
+ vec4 mv = modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
384
+ mv.xy += position.xy * length(modelMatrix[0].xyz) * uRadius;
385
+ vUv = position.xy;
386
+ gl_Position = projectionMatrix * mv;
387
+ }`,
388
+ fragmentShader: /* glsl */ `
389
+ uniform vec3 uColor;
390
+ uniform float uStrength;
391
+ varying vec2 vUv;
392
+ void main() {
393
+ float d = length(vUv);
394
+ // Tight. A wide soft halo round a candle reads as fog, not flame.
395
+ float a = exp(-d * d * 7.0);
396
+ gl_FragColor = vec4(uColor * uStrength * a, 1.0);
397
+ ${OUTPUT}
398
+ }`,
399
+ transparent: true,
400
+ depthWrite: false,
401
+ blending: AdditiveBlending,
402
+ });
403
+ }
404
+ export function createCandleMaterials() {
405
+ return {
406
+ flame: flameMaterial(),
407
+ flameBillboard: flameBillboardMaterial(),
408
+ smoke: smokeMaterial(),
409
+ wax: new MeshPhysicalMaterial({
410
+ color: 0xf3e7d2, roughness: 0.42, transmission: 0.6, thickness: 0.05,
411
+ ior: 1.45, attenuationColor: new Color(0.95, 0.82, 0.62), attenuationDistance: 0.06,
412
+ sheen: 0.7, sheenColor: new Color(1, 0.94, 0.85), sheenRoughness: 0.6,
413
+ clearcoat: 0.15, clearcoatRoughness: 0.5, side: DoubleSide,
414
+ }),
415
+ melt: new MeshPhysicalMaterial({
416
+ color: 0xf6ead6, roughness: 0.1, transmission: 0.75, thickness: 0.02,
417
+ ior: 1.45, clearcoat: 1, clearcoatRoughness: 0.05, side: DoubleSide,
418
+ }),
419
+ wick: new MeshBasicMaterial({ color: 0x1a1512, toneMapped: false }),
420
+ ember: new MeshBasicMaterial({ color: 0xff6a1e, toneMapped: false }),
421
+ halo: haloMaterial(),
422
+ };
423
+ }
424
+ export function disposeCandleMaterials(m) {
425
+ m.flame.dispose();
426
+ m.flameBillboard.dispose();
427
+ m.smoke.dispose();
428
+ m.wax.dispose();
429
+ m.melt.dispose();
430
+ m.wick.dispose();
431
+ m.ember.dispose();
432
+ m.halo.dispose();
433
+ }
@@ -0,0 +1,64 @@
1
+ export interface FlameMotionConfig {
2
+ /** Depth of the brightness dip, 0-1. Light output only — not shape. */
3
+ flicker: number;
4
+ /** How much the flame moves at all, 0-1. 0 freezes the shape. */
5
+ movement: number;
6
+ /** Carrier rate in Hz. Real candles sit around 10-12 whatever their size. */
7
+ flickerHz: number;
8
+ /** Air movement, 0-1: lean, stretch, turbulence and eventually guttering. */
9
+ draught: number;
10
+ /** Seconds for the wick to catch and the flame to establish. */
11
+ ignite: number;
12
+ /** Whether the candle is lit. */
13
+ on: boolean;
14
+ }
15
+ export interface FlameMotionState {
16
+ /** 0 = out, 1 = established. Drives size and output together. */
17
+ level: number;
18
+ /** Height multiplier from the buoyancy pulse, around 1. */
19
+ pulse: number;
20
+ /** Lean of the flame's tip, in flame-height units. */
21
+ leanX: number;
22
+ leanZ: number;
23
+ /** Vertical stretch; draught pulls a flame long and thin. */
24
+ stretch: number;
25
+ /** How turbulent the shape noise is, 0-1. */
26
+ turbulence: number;
27
+ /** How fast the eddies travel up the flame. 0 freezes them. */
28
+ scroll: number;
29
+ /** Luminous output, lagging the shape a little, as the real thing does. */
30
+ output: number;
31
+ /** How blue the flame reads: high while it is catching, low once established. */
32
+ blueBias: number;
33
+ /** Seconds since it was snuffed, for the smoke wisp. -1 when lit or long out. */
34
+ smokeAge: number;
35
+ /** Carrier phase, so the shader can run the wave up the flame. */
36
+ phaseOut: number;
37
+ /** Amplitude of that undulation, in flame-height units. */
38
+ sway: number;
39
+ /** The plane it is swaying in, as a unit vector. Wanders. */
40
+ swayX: number;
41
+ swayZ: number;
42
+ /** How hard the neck pinches before the tip sheds. */
43
+ shed: number;
44
+ /** Where that pinch has travelled to, 0-1. */
45
+ shedPhase: number;
46
+ /** Fixed per candle, so two of them never move identically. */
47
+ noiseSeed: number;
48
+ /** How far wind has lifted the flame's root off the wick, 0-1. */
49
+ detach: number;
50
+ phase: number;
51
+ pinks: number[][];
52
+ windAngle: number;
53
+ swayAngle: number;
54
+ shedT: number;
55
+ shedPeriod: number;
56
+ shedStrength: number;
57
+ wasOn: boolean;
58
+ since: number;
59
+ gutter: number;
60
+ seed: number;
61
+ }
62
+ export declare function createFlameMotion(on: boolean): FlameMotionState;
63
+ export declare function stepFlameMotion(s: FlameMotionState, cfg: FlameMotionConfig, deltaSeconds: number): FlameMotionState;
64
+ //# sourceMappingURL=flameMotion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flameMotion.d.ts","sourceRoot":"","sources":["../../../src/components/candle/flameMotion.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,iBAAiB;IAC9B,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,EAAE,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC7B,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IAEjB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IAGf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CAChB;AAMD,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,OAAO,GAAG,gBAAgB,CAgB/D;AAmBD,wBAAgB,eAAe,CAAC,CAAC,EAAE,gBAAgB,EAAE,GAAG,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,GAAG,gBAAgB,CAsGnH"}
@@ -0,0 +1,138 @@
1
+ const TWO_PI = Math.PI * 2;
2
+ const clamp01 = (v) => Math.min(Math.max(v, 0), 1);
3
+ const rand = () => Math.random() * 2 - 1;
4
+ export function createFlameMotion(on) {
5
+ const swayAngle = Math.random() * TWO_PI;
6
+ return {
7
+ level: 0, pulse: 1, leanX: 0, leanZ: 0, stretch: 1, turbulence: 0.25, scroll: 1.4,
8
+ output: 0, blueBias: 1, smokeAge: -1,
9
+ phaseOut: 0, sway: 0.02, swayX: Math.cos(swayAngle), swayZ: Math.sin(swayAngle),
10
+ shed: 0.18, shedPhase: 0, noiseSeed: Math.random() * 100, detach: 0,
11
+ phase: Math.random() * TWO_PI,
12
+ // One channel each for lean, frequency, amplitude and sway direction.
13
+ pinks: [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
14
+ windAngle: Math.random() * TWO_PI,
15
+ swayAngle,
16
+ shedT: 0.2, shedPeriod: 0.35, shedStrength: 1,
17
+ wasOn: on, since: 0, gutter: 0,
18
+ seed: Math.random() * 1000,
19
+ };
20
+ }
21
+ /**
22
+ * Pink-ish noise: three leaky integrators at spaced cutoffs, summed. Cheap, and
23
+ * it gives the slow drift-and-return of air in a room rather than the twitch of
24
+ * white noise. Cutoffs roughly 0.35 / 1.4 / 5 Hz.
25
+ */
26
+ function pink(s, dt, channel) {
27
+ const p = s.pinks[channel];
28
+ const k = [0.35, 1.4, 5.0];
29
+ let sum = 0;
30
+ for (let i = 0; i < 3; i++) {
31
+ const a = 1 - Math.exp(-dt * k[i] * TWO_PI);
32
+ p[i] += (rand() - p[i]) * a;
33
+ sum += p[i] / (i + 1);
34
+ }
35
+ return sum * 0.85;
36
+ }
37
+ export function stepFlameMotion(s, cfg, deltaSeconds) {
38
+ const dt = Math.min(Math.max(deltaSeconds, 0), 0.1);
39
+ const draught = clamp01(cfg.draught);
40
+ const flicker = clamp01(cfg.flicker);
41
+ const move = clamp01(cfg.movement);
42
+ if (cfg.on !== s.wasOn) {
43
+ s.wasOn = cfg.on;
44
+ s.since = 0;
45
+ s.seed = Math.random() * 1000;
46
+ if (!cfg.on)
47
+ s.smokeAge = 0;
48
+ }
49
+ s.since += dt;
50
+ if (s.smokeAge >= 0) {
51
+ s.smokeAge += dt;
52
+ if (s.smokeAge > 3.5)
53
+ s.smokeAge = -1;
54
+ }
55
+ // --- the carrier, with a wandering rate and amplitude --------------------
56
+ // The frequency drift is what breaks the loop: with a fixed rate the two
57
+ // sines below re-phase on a cycle of a few seconds and the eye finds it.
58
+ const rateDrift = 1 + pink(s, dt, 1) * 0.3 * move;
59
+ const hz = Math.max(cfg.flickerHz, 1) * (1 + draught * 0.35) * rateDrift;
60
+ s.phase += dt * hz * TWO_PI;
61
+ if (s.phase > 1e6)
62
+ s.phase -= 1e6;
63
+ const ampDrift = 0.6 + clamp01(pink(s, dt, 2) * 0.5 + 0.5) * 0.85;
64
+ // Golden ratio, not a near-rational one: the two components never line up.
65
+ const wobble = (Math.sin(s.phase) * 0.62
66
+ + Math.sin(s.phase * 1.618034 + s.seed) * 0.24
67
+ + pink(s, dt, 0) * 0.34) * ampDrift;
68
+ s.pulse = 1 + wobble * move * 0.17;
69
+ s.phaseOut = s.phase;
70
+ // --- guttering ----------------------------------------------------------
71
+ // A rare, real event: the flame is momentarily starved and nearly goes out.
72
+ // Rate climbs with the square of the draught, so a still room never gutters.
73
+ if (s.gutter > 0)
74
+ s.gutter = Math.max(s.gutter - dt / 0.45, 0);
75
+ else if (cfg.on && draught > 0.25 && Math.random() < dt * draught * draught * 1.6)
76
+ s.gutter = 1;
77
+ const gutterDip = s.gutter > 0 ? 1 - Math.sin(s.gutter * Math.PI) * 0.72 : 1;
78
+ // --- ignition and snuffing ---------------------------------------------
79
+ if (!cfg.on) {
80
+ s.level += (0 - s.level) * (1 - Math.exp(-dt / 0.1));
81
+ if (s.level < 1e-3)
82
+ s.level = 0;
83
+ s.blueBias = 1;
84
+ }
85
+ else {
86
+ const ignite = Math.max(cfg.ignite, 0);
87
+ if (ignite <= 0)
88
+ s.level = 1;
89
+ else {
90
+ // A wick catches small and blue, flares slightly past full, settles.
91
+ const t = s.since / ignite;
92
+ const target = t >= 1 ? 1 : 1 - Math.exp(-t * 3.4);
93
+ const flare = t < 1 ? Math.sin(Math.min(t, 1) * Math.PI) * 0.12 : 0;
94
+ s.level += (Math.min(target + flare, 1.12) - s.level) * (1 - Math.exp(-dt * 9));
95
+ }
96
+ // Blue dominates while the flame is small: less soot, more oxygen.
97
+ s.blueBias = 1 + Math.max(0, 1 - s.level) * 2.2;
98
+ }
99
+ // --- lean, stretch, turbulence -----------------------------------------
100
+ // No wind direction knob: each candle picks its own and it drifts, which is
101
+ // what a room does anyway and one less thing to set.
102
+ s.windAngle += pink(s, dt, 3) * dt * 0.55;
103
+ const wander = pink(s, dt, 0) * (0.06 + draught * 0.34) * move;
104
+ const push = draught * 0.55;
105
+ s.leanX += (Math.cos(s.windAngle) * push + wander - s.leanX) * (1 - Math.exp(-dt * 6));
106
+ s.leanZ += (Math.sin(s.windAngle) * push + wander * 0.7 - s.leanZ) * (1 - Math.exp(-dt * 5));
107
+ s.stretch = 1 + draught * 0.9 + wobble * move * 0.1;
108
+ // Frozen noise still has shape, so a still flame is irregular rather than a
109
+ // perfect cone — it just stops moving.
110
+ s.turbulence = (0.16 + draught * 0.95) * (0.35 + 0.65 * move);
111
+ s.scroll = (1.4 + s.turbulence * 2.4) * move;
112
+ // --- the undulation running up the flame --------------------------------
113
+ // The plane it sways in drifts, so the flame does not rock back and forth
114
+ // along one fixed axis.
115
+ s.swayAngle += pink(s, dt, 3) * dt * 2.4 * move;
116
+ s.swayX = Math.cos(s.swayAngle);
117
+ s.swayZ = Math.sin(s.swayAngle);
118
+ s.sway = (0.022 + draught * 0.17) * move * ampDrift;
119
+ // --- the tip sheds, on its own schedule ---------------------------------
120
+ // Event-driven rather than a fixed phase march: each shed gets its own
121
+ // interval and strength, so the tip never lets go on a beat.
122
+ s.shedT -= dt * move;
123
+ if (s.shedT <= 0) {
124
+ s.shedPeriod = 0.16 + Math.random() * 0.55;
125
+ s.shedT = s.shedPeriod;
126
+ s.shedStrength = 0.45 + Math.random() * 1.0;
127
+ }
128
+ s.shedPhase = clamp01(1 - s.shedT / Math.max(s.shedPeriod, 1e-3));
129
+ s.shed = (0.12 + draught * 0.4) * move * s.shedStrength;
130
+ // Hard wind lifts the flame clear of the wick before it puts it out.
131
+ s.detach = Math.max(0, (draught - 0.5) / 0.5) * (0.5 + 0.5 * wobble);
132
+ // --- luminous output ----------------------------------------------------
133
+ // The light comes from the whole glowing volume, so it lags the shape and
134
+ // swings less than the height does. ~35 ms is about right.
135
+ const target = s.level * gutterDip * (1 + wobble * flicker * 0.12);
136
+ s.output += (target - s.output) * (1 - Math.exp(-dt / 0.035));
137
+ return s;
138
+ }
@@ -0,0 +1,9 @@
1
+ export { default as Candle } from './Candle';
2
+ export type { CandleProps } from './Candle';
3
+ export { createCandleMaterials, disposeCandleMaterials, FLAME_MODES } from './candleMaterials';
4
+ export type { CandleMaterials, FlameMode } from './candleMaterials';
5
+ export { buildCandle, CANDLE_BODIES } from './candleGeometry';
6
+ export type { CandleBody, CandleGeometry } from './candleGeometry';
7
+ export { createFlameMotion, stepFlameMotion } from './flameMotion';
8
+ export type { FlameMotionConfig, FlameMotionState } from './flameMotion';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/candle/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAC7C,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACnE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,6 @@
1
+ // A candle: a raymarched flame with a real point light, and the wax under it
2
+ // (config.type 'candle').
3
+ export { default as Candle } from './Candle.js';
4
+ export { createCandleMaterials, disposeCandleMaterials, FLAME_MODES } from './candleMaterials.js';
5
+ export { buildCandle, CANDLE_BODIES } from './candleGeometry.js';
6
+ export { createFlameMotion, stepFlameMotion } from './flameMotion.js';
@@ -162,6 +162,7 @@ export * from './lightBulb';
162
162
  export * from './spotBeam';
163
163
  export * from './ledArray';
164
164
  export * from './fluorescentTube';
165
+ export * from './candle';
165
166
  export type { LandscapeBuilderProps } from './LandscapeBuilder';
166
167
  export { default as LandscapeBuilder } from './LandscapeBuilder';
167
168
  export type { LandscapeObjectProps } from './LandscapeObject';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"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"}
@@ -106,6 +106,7 @@ export * from './lightBulb/index.js';
106
106
  export * from './spotBeam/index.js';
107
107
  export * from './ledArray/index.js';
108
108
  export * from './fluorescentTube/index.js';
109
+ export * from './candle/index.js';
109
110
  export { default as LandscapeBuilder } from './LandscapeBuilder.js';
110
111
  export { default as LandscapeObject } from './LandscapeObject.js';
111
112
  export * from './landscape/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"useLightLive.d.ts","sourceRoot":"","sources":["../../src/hooks/useLightLive.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvD,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAU7C,CAAC;AAKF,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAyB1H;AAED;;;GAGG;AACH,wBAAgB,YAAY,CACxB,OAAO,EAAE,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC,EACnC,QAAQ,EAAE,SAAS,CAAC,KAAK,GAAG,IAAI,CAAC,EACjC,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAMnD;AAED,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"useLightLive.d.ts","sourceRoot":"","sources":["../../src/hooks/useLightLive.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvD,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAa7C,CAAC;AAKF,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAyB1H;AAED;;;GAGG;AACH,wBAAgB,YAAY,CACxB,OAAO,EAAE,SAAS,CAAC,QAAQ,GAAG,IAAI,CAAC,EACnC,QAAQ,EAAE,SAAS,CAAC,KAAK,GAAG,IAAI,CAAC,EACjC,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC5B,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAMnD;AAED,eAAe,YAAY,CAAC"}
@@ -7,6 +7,9 @@ export const LIGHT_STRUCTURAL = [
7
7
  // ledArray: the die layout is rebuilt. fluorescentTube: the tube geometry is.
8
8
  'layout', 'count', 'rows', 'spacing', 'dieSize',
9
9
  'tubes', 'tubeSpacing', 'length', 'diameter', 'startup',
10
+ // candle: the wax and wick are rebuilt, and the flame mode swaps meshes.
11
+ 'body', 'waxColor', 'flameMode', 'smoke',
12
+ 'height', 'radius', 'melt', 'flameHeight', 'flameWidth',
10
13
  'shadow.enabled', 'shadow.mapSize', 'shadow.cameraNear', 'shadow.cameraFar',
11
14
  'shadow.cameraLeft', 'shadow.cameraRight', 'shadow.cameraTop', 'shadow.cameraBottom',
12
15
  'shadow.cameraFov', 'shadow.focus',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@3driseai/3drise-viewer",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,6 +39,6 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@3driseai/3drise-common": "^1.0.1",
42
- "@3driseai/3drise-core": "^1.0.16"
42
+ "@3driseai/3drise-core": "^1.0.17"
43
43
  }
44
44
  }