@3driseai/3drise-viewer 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CreatedObject.d.ts.map +1 -1
- package/dist/components/CreatedObject.js +6 -1
- package/dist/components/LightObject.d.ts.map +1 -1
- package/dist/components/LightObject.js +24 -9
- package/dist/components/ModelPrimitive.d.ts.map +1 -1
- package/dist/components/ModelPrimitive.js +10 -1
- package/dist/components/ObjectNode.js +1 -1
- package/dist/components/ParticlesGenerator.d.ts +3 -1
- package/dist/components/ParticlesGenerator.d.ts.map +1 -1
- package/dist/components/ParticlesGenerator.js +2 -2
- package/dist/components/ProjectLoader.js +1 -1
- package/dist/components/SceneBuilder.d.ts.map +1 -1
- package/dist/components/SceneBuilder.js +5 -1
- package/dist/components/fluorescentTube/FluorescentTube.d.ts +14 -0
- package/dist/components/fluorescentTube/FluorescentTube.d.ts.map +1 -0
- package/dist/components/fluorescentTube/FluorescentTube.js +262 -0
- package/dist/components/fluorescentTube/index.d.ts +5 -0
- package/dist/components/fluorescentTube/index.d.ts.map +1 -0
- package/dist/components/fluorescentTube/index.js +3 -0
- package/dist/components/fluorescentTube/tubeMaterials.d.ts +34 -0
- package/dist/components/fluorescentTube/tubeMaterials.d.ts.map +1 -0
- package/dist/components/fluorescentTube/tubeMaterials.js +185 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -0
- package/dist/components/ledArray/LedArray.d.ts +14 -0
- package/dist/components/ledArray/LedArray.d.ts.map +1 -0
- package/dist/components/ledArray/LedArray.js +243 -0
- package/dist/components/ledArray/index.d.ts +7 -0
- package/dist/components/ledArray/index.d.ts.map +1 -0
- package/dist/components/ledArray/index.js +4 -0
- package/dist/components/ledArray/ledLayout.d.ts +22 -0
- package/dist/components/ledArray/ledLayout.d.ts.map +1 -0
- package/dist/components/ledArray/ledLayout.js +59 -0
- package/dist/components/ledArray/ledMaterials.d.ts +14 -0
- package/dist/components/ledArray/ledMaterials.d.ts.map +1 -0
- package/dist/components/ledArray/ledMaterials.js +151 -0
- package/dist/components/lightBulb/LightBulb.d.ts +16 -0
- package/dist/components/lightBulb/LightBulb.d.ts.map +1 -0
- package/dist/components/lightBulb/LightBulb.js +141 -0
- package/dist/components/lightBulb/bulbMaterials.d.ts +27 -0
- package/dist/components/lightBulb/bulbMaterials.d.ts.map +1 -0
- package/dist/components/lightBulb/bulbMaterials.js +133 -0
- package/dist/components/lightBulb/bulbShapes.d.ts +15 -0
- package/dist/components/lightBulb/bulbShapes.d.ts.map +1 -0
- package/dist/components/lightBulb/bulbShapes.js +180 -0
- package/dist/components/lightBulb/index.d.ts +5 -0
- package/dist/components/lightBulb/index.d.ts.map +1 -0
- package/dist/components/lightBulb/index.js +3 -0
- package/dist/components/lightBulb/kelvin.d.ts +7 -0
- package/dist/components/lightBulb/kelvin.d.ts.map +1 -0
- package/dist/components/lightBulb/kelvin.js +21 -0
- package/dist/components/lightBulb/types.d.ts +2 -0
- package/dist/components/lightBulb/types.d.ts.map +1 -0
- package/dist/components/lightBulb/types.js +1 -0
- package/dist/components/particles/BioluminescentWakeParticles.d.ts +3 -1
- package/dist/components/particles/BioluminescentWakeParticles.d.ts.map +1 -1
- package/dist/components/particles/BioluminescentWakeParticles.js +7 -3
- package/dist/components/particles/BrownianDustParticles.d.ts +3 -1
- package/dist/components/particles/BrownianDustParticles.d.ts.map +1 -1
- package/dist/components/particles/BrownianDustParticles.js +7 -3
- package/dist/components/particles/ChladniSandParticles.d.ts +3 -1
- package/dist/components/particles/ChladniSandParticles.d.ts.map +1 -1
- package/dist/components/particles/ChladniSandParticles.js +7 -3
- package/dist/components/particles/ElectronOrbitalsParticles.d.ts +3 -1
- package/dist/components/particles/ElectronOrbitalsParticles.d.ts.map +1 -1
- package/dist/components/particles/ElectronOrbitalsParticles.js +7 -3
- package/dist/components/particles/FaradayPowderParticles.d.ts +3 -1
- package/dist/components/particles/FaradayPowderParticles.d.ts.map +1 -1
- package/dist/components/particles/FaradayPowderParticles.js +7 -3
- package/dist/components/particles/GrinderSparksParticles.d.ts +3 -1
- package/dist/components/particles/GrinderSparksParticles.d.ts.map +1 -1
- package/dist/components/particles/GrinderSparksParticles.js +7 -3
- package/dist/components/particles/NeonRainParticles.d.ts +3 -1
- package/dist/components/particles/NeonRainParticles.d.ts.map +1 -1
- package/dist/components/particles/NeonRainParticles.js +7 -3
- package/dist/components/particles/TidalStreamParticles.d.ts +3 -1
- package/dist/components/particles/TidalStreamParticles.d.ts.map +1 -1
- package/dist/components/particles/TidalStreamParticles.js +7 -3
- package/dist/components/spotBeam/SpotBeam.d.ts +14 -0
- package/dist/components/spotBeam/SpotBeam.d.ts.map +1 -0
- package/dist/components/spotBeam/SpotBeam.js +244 -0
- package/dist/components/spotBeam/beamPatterns.d.ts +11 -0
- package/dist/components/spotBeam/beamPatterns.d.ts.map +1 -0
- package/dist/components/spotBeam/beamPatterns.js +106 -0
- package/dist/components/spotBeam/index.d.ts +7 -0
- package/dist/components/spotBeam/index.d.ts.map +1 -0
- package/dist/components/spotBeam/index.js +4 -0
- package/dist/components/spotBeam/spotBeamMaterials.d.ts +16 -0
- package/dist/components/spotBeam/spotBeamMaterials.d.ts.map +1 -0
- package/dist/components/spotBeam/spotBeamMaterials.js +307 -0
- package/dist/hooks/index.d.ts +4 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +3 -1
- package/dist/hooks/useConfigLive.d.ts +2 -2
- package/dist/hooks/useConfigLive.d.ts.map +1 -1
- package/dist/hooks/useConfigLive.js +1 -1
- package/dist/hooks/useLightConfig.d.ts +25 -0
- package/dist/hooks/useLightConfig.d.ts.map +1 -0
- package/dist/hooks/useLightConfig.js +125 -0
- package/dist/hooks/useLightLive.d.ts +12 -0
- package/dist/hooks/useLightLive.d.ts.map +1 -0
- package/dist/hooks/useLightLive.js +53 -0
- package/dist/hooks/useShadowApplication.d.ts +8 -0
- package/dist/hooks/useShadowApplication.d.ts.map +1 -1
- package/dist/hooks/useShadowApplication.js +33 -9
- package/package.json +2 -2
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// ledMaterials — the dies, their per-die glow, the diffuser and the halo
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// The viewer has no tone mapping and no bloom, so every bright thing here is
|
|
6
|
+
// drawn by hand. An LED array is a set of very small, very bright sources, so
|
|
7
|
+
// the glow is per die rather than one blob: a billboard quad per instance,
|
|
8
|
+
// coloured from an instanced attribute so RGB mode can run a different hue down
|
|
9
|
+
// the array.
|
|
10
|
+
//
|
|
11
|
+
// The diffuser is the other half of the look. A bare die is a point source with
|
|
12
|
+
// a hard-edged shadow; a diffuser turns the whole array into one large soft
|
|
13
|
+
// source. The slab drawn here is that surface — and the component couples the
|
|
14
|
+
// same knob to the light's shadow radius, because the two are the same physics.
|
|
15
|
+
//
|
|
16
|
+
import { AdditiveBlending, Color, DoubleSide, MeshBasicMaterial, ShaderMaterial } from 'three';
|
|
17
|
+
const OUTPUT = /* glsl */ `
|
|
18
|
+
#include <tonemapping_fragment>
|
|
19
|
+
#include <colorspace_fragment>
|
|
20
|
+
`;
|
|
21
|
+
/**
|
|
22
|
+
* A billboard per instance. The quad is built in view space from the instance's
|
|
23
|
+
* own origin, so the dies always face the camera however the array is turned.
|
|
24
|
+
*/
|
|
25
|
+
function dieGlowMaterial() {
|
|
26
|
+
return new ShaderMaterial({
|
|
27
|
+
uniforms: {
|
|
28
|
+
uStrength: { value: 0 },
|
|
29
|
+
uSize: { value: 0.09 },
|
|
30
|
+
},
|
|
31
|
+
vertexShader: /* glsl */ `
|
|
32
|
+
attribute vec3 aColor;
|
|
33
|
+
uniform float uSize;
|
|
34
|
+
varying vec2 vUv;
|
|
35
|
+
varying vec3 vColor;
|
|
36
|
+
void main() {
|
|
37
|
+
vColor = aColor;
|
|
38
|
+
vUv = position.xy;
|
|
39
|
+
// instance origin → view space, then the quad is laid out there
|
|
40
|
+
vec4 mv = modelViewMatrix * instanceMatrix * vec4(0.0, 0.0, 0.0, 1.0);
|
|
41
|
+
float s = length(modelMatrix[0].xyz) * uSize;
|
|
42
|
+
mv.xy += position.xy * s;
|
|
43
|
+
gl_Position = projectionMatrix * mv;
|
|
44
|
+
}`,
|
|
45
|
+
fragmentShader: /* glsl */ `
|
|
46
|
+
uniform float uStrength;
|
|
47
|
+
varying vec2 vUv;
|
|
48
|
+
varying vec3 vColor;
|
|
49
|
+
void main() {
|
|
50
|
+
float d = length(vUv);
|
|
51
|
+
// a tight core with a wide, faint skirt — how a small very bright
|
|
52
|
+
// source reads through a lens
|
|
53
|
+
float a = exp(-d * 11.0) + exp(-d * d * 6.0) * 0.35;
|
|
54
|
+
a *= 1.0 - smoothstep(0.7, 1.0, d);
|
|
55
|
+
gl_FragColor = vec4(vColor * uStrength * a, 1.0);
|
|
56
|
+
${OUTPUT}
|
|
57
|
+
}`,
|
|
58
|
+
transparent: true,
|
|
59
|
+
depthWrite: false,
|
|
60
|
+
blending: AdditiveBlending,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The diffuser sheet. Brightest where the dies are (a soft periodic ripple that
|
|
65
|
+
* fades out as the sheet thickens), dimmer towards the rim where light escapes.
|
|
66
|
+
*/
|
|
67
|
+
function diffuserMaterial() {
|
|
68
|
+
return new ShaderMaterial({
|
|
69
|
+
uniforms: {
|
|
70
|
+
uColor: { value: new Color(1, 1, 1) },
|
|
71
|
+
uStrength: { value: 0 },
|
|
72
|
+
/** 0 = the dies still show through, 1 = a single even sheet. */
|
|
73
|
+
uBlend: { value: 0.5 },
|
|
74
|
+
/** How many die pitches across the sheet, for the hot-spot ripple. */
|
|
75
|
+
uPitch: { value: 4 },
|
|
76
|
+
},
|
|
77
|
+
vertexShader: /* glsl */ `
|
|
78
|
+
varying vec2 vUv;
|
|
79
|
+
void main() {
|
|
80
|
+
vUv = uv;
|
|
81
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
82
|
+
}`,
|
|
83
|
+
fragmentShader: /* glsl */ `
|
|
84
|
+
uniform vec3 uColor;
|
|
85
|
+
uniform float uStrength;
|
|
86
|
+
uniform float uBlend;
|
|
87
|
+
uniform float uPitch;
|
|
88
|
+
varying vec2 vUv;
|
|
89
|
+
void main() {
|
|
90
|
+
vec2 p = vUv * 2.0 - 1.0;
|
|
91
|
+
// light leaks away at the rim of any real diffuser
|
|
92
|
+
float edge = (1.0 - smoothstep(0.72, 1.0, abs(p.x))) * (1.0 - smoothstep(0.72, 1.0, abs(p.y)));
|
|
93
|
+
// hot spots over each die, washed out as the sheet thickens
|
|
94
|
+
vec2 c = cos(vUv * uPitch * 6.2831853);
|
|
95
|
+
float dots = (c.x * c.y) * 0.5 + 0.5;
|
|
96
|
+
float lit = mix(0.45 + dots * 0.75, 1.0, uBlend);
|
|
97
|
+
gl_FragColor = vec4(uColor * uStrength * lit * edge, 1.0);
|
|
98
|
+
${OUTPUT}
|
|
99
|
+
}`,
|
|
100
|
+
transparent: true,
|
|
101
|
+
depthWrite: false,
|
|
102
|
+
side: DoubleSide,
|
|
103
|
+
blending: AdditiveBlending,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function haloMaterial() {
|
|
107
|
+
return new ShaderMaterial({
|
|
108
|
+
uniforms: {
|
|
109
|
+
uColor: { value: new Color(1, 1, 1) },
|
|
110
|
+
uStrength: { value: 0 },
|
|
111
|
+
uRadius: { value: 0.55 },
|
|
112
|
+
},
|
|
113
|
+
vertexShader: /* glsl */ `
|
|
114
|
+
uniform float uRadius;
|
|
115
|
+
varying vec2 vUv;
|
|
116
|
+
void main() {
|
|
117
|
+
vec4 mv = modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
|
|
118
|
+
float s = length(modelMatrix[0].xyz) * uRadius;
|
|
119
|
+
mv.xy += position.xy * s;
|
|
120
|
+
vUv = position.xy;
|
|
121
|
+
gl_Position = projectionMatrix * mv;
|
|
122
|
+
}`,
|
|
123
|
+
fragmentShader: /* glsl */ `
|
|
124
|
+
uniform vec3 uColor;
|
|
125
|
+
uniform float uStrength;
|
|
126
|
+
varying vec2 vUv;
|
|
127
|
+
void main() {
|
|
128
|
+
float d = length(vUv);
|
|
129
|
+
float a = (exp(-d * 8.0) + exp(-d * d * 4.0) * 0.3) * (1.0 - smoothstep(0.65, 1.0, d));
|
|
130
|
+
gl_FragColor = vec4(uColor * uStrength * a, 1.0);
|
|
131
|
+
${OUTPUT}
|
|
132
|
+
}`,
|
|
133
|
+
transparent: true,
|
|
134
|
+
depthWrite: false,
|
|
135
|
+
blending: AdditiveBlending,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
export function createLedArrayMaterials() {
|
|
139
|
+
return {
|
|
140
|
+
die: new MeshBasicMaterial({ color: 0xffffff, toneMapped: false }),
|
|
141
|
+
dieGlow: dieGlowMaterial(),
|
|
142
|
+
diffuser: diffuserMaterial(),
|
|
143
|
+
halo: haloMaterial(),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export function disposeLedArrayMaterials(m) {
|
|
147
|
+
m.die.dispose();
|
|
148
|
+
m.dieGlow.dispose();
|
|
149
|
+
m.diffuser.dispose();
|
|
150
|
+
m.halo.dispose();
|
|
151
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { AnimationOptions, CreatedObjectSettings, MouseMoveDomain } from '@3driseai/3drise-core';
|
|
3
|
+
import type { ObjectReadyProps } from '../../types/objectReady';
|
|
4
|
+
import type { LightBulbConfig } from '@3driseai/3drise-core';
|
|
5
|
+
export type LightBulbProps = ObjectReadyProps & {
|
|
6
|
+
/** Scene object id: the light controller finds the live-edit handle by it. */
|
|
7
|
+
id?: string;
|
|
8
|
+
config: LightBulbConfig;
|
|
9
|
+
animations?: AnimationOptions[];
|
|
10
|
+
mouseMove?: MouseMoveDomain;
|
|
11
|
+
/** Reported as the first argument of onObjectReady / onObjectDispose. */
|
|
12
|
+
settings: CreatedObjectSettings | Record<string, any>;
|
|
13
|
+
};
|
|
14
|
+
declare const LightBulb: React.FC<LightBulbProps>;
|
|
15
|
+
export default LightBulb;
|
|
16
|
+
//# sourceMappingURL=LightBulb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LightBulb.d.ts","sourceRoot":"","sources":["../../../src/components/lightBulb/LightBulb.tsx"],"names":[],"mappings":"AAqBA,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;AAIhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG;IAC5C,8EAA8E;IAC9E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAChC,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACzD,CAAC;AAuBF,QAAA,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAqHvC,CAAC;AACF,eAAe,SAAS,CAAC"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// LightBulb — a point light you can see: glass, filament, base and glow
|
|
4
|
+
// =============================================================================
|
|
5
|
+
//
|
|
6
|
+
// Rendered by LightObject for `config.type === 'lightBulb'`. The object origin
|
|
7
|
+
// is the filament centre, which is where the point light sits; the bulb stands
|
|
8
|
+
// upright (base down, -Y) — rotate the object 180° for a pendant.
|
|
9
|
+
//
|
|
10
|
+
// Intensity ownership: useLightConfig (keyframes + pointer bindings) and R3F's
|
|
11
|
+
// `intensity` prop write `light.intensity` as usual. This component then
|
|
12
|
+
// multiplies in the power level (on/off, warm-up, flicker) each frame. Any
|
|
13
|
+
// intensity it did not write itself is adopted as the new base, so the
|
|
14
|
+
// multiplier never compounds and external writers stay in charge of the base.
|
|
15
|
+
// The glow reads the light's real output every frame, so an animated or
|
|
16
|
+
// pointer-driven intensity/colour dims and tints the bulb with it.
|
|
17
|
+
//
|
|
18
|
+
// Materials are self-owned: the root carries SELF_OWNED_MATERIAL_KEY so the
|
|
19
|
+
// CreatedObject wrapper's material/shadow application skips the bulb. Only the
|
|
20
|
+
// screw base casts a shadow — the glass and filament surround the light and
|
|
21
|
+
// would otherwise black it out.
|
|
22
|
+
//
|
|
23
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
24
|
+
import { useFrame } from '@react-three/fiber';
|
|
25
|
+
import { Color } from 'three';
|
|
26
|
+
import { SELF_OWNED_MATERIAL_KEY } from '@3driseai/3drise-core';
|
|
27
|
+
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
28
|
+
import { useLightShadow } from '../../hooks/useLightShadow.js';
|
|
29
|
+
import { useLightConfig } from '../../hooks/useLightConfig.js';
|
|
30
|
+
import { buildBulbShape, disposeBulbShape } from './bulbShapes.js';
|
|
31
|
+
import { applyGlass, createBulbMaterials, disposeBulbMaterials, GLASS_RESPONSE } from './bulbMaterials.js';
|
|
32
|
+
import { kelvinToColor } from './kelvin.js';
|
|
33
|
+
const num = (v, fallback) => (typeof v === 'number' ? v : fallback);
|
|
34
|
+
// Smooth 1D value noise for flicker.
|
|
35
|
+
const hash = (n) => {
|
|
36
|
+
const s = Math.sin(n * 127.1) * 43758.5453;
|
|
37
|
+
return s - Math.floor(s);
|
|
38
|
+
};
|
|
39
|
+
const noise = (x) => {
|
|
40
|
+
const i = Math.floor(x), f = x - i, u = f * f * (3 - 2 * f);
|
|
41
|
+
return hash(i) * (1 - u) + hash(i + 1) * u;
|
|
42
|
+
};
|
|
43
|
+
// Tungsten heating ramp: unlit grey → dull red → orange → the light's own colour.
|
|
44
|
+
const UNLIT = new Color(0.12, 0.12, 0.12);
|
|
45
|
+
const DULL_RED = new Color(0.55, 0.05, 0.0);
|
|
46
|
+
const ORANGE = new Color(1.0, 0.38, 0.06);
|
|
47
|
+
const WHITE = new Color(1, 1, 1);
|
|
48
|
+
const HALO_SEGMENTS = [2, 2];
|
|
49
|
+
const LightBulb = ({ id, config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
|
|
50
|
+
const groupRef = useRef(null);
|
|
51
|
+
const lightRef = useRef(null);
|
|
52
|
+
useObjectReady(groupRef, settings, onObjectReady, onObjectDispose);
|
|
53
|
+
useLightShadow(lightRef, config.shadow);
|
|
54
|
+
// Keyframes, pointer bindings and slider drags: the light's own knobs land on
|
|
55
|
+
// the light, and the merged config is what the filament, glow and halo read.
|
|
56
|
+
const { merged } = useLightConfig(groupRef, lightRef, id, config, animations, mouseMove);
|
|
57
|
+
const glass = config.glass ?? 'clear';
|
|
58
|
+
const glassTint = config.glassTint ?? '#ffb070';
|
|
59
|
+
const on = config.on ?? true;
|
|
60
|
+
const baseIntensity = config.intensity ?? 1;
|
|
61
|
+
const shape = useMemo(() => buildBulbShape(config.shape), [config.shape]);
|
|
62
|
+
useEffect(() => () => disposeBulbShape(shape), [shape]);
|
|
63
|
+
const mats = useMemo(() => createBulbMaterials(), []);
|
|
64
|
+
useEffect(() => () => disposeBulbMaterials(mats), [mats]);
|
|
65
|
+
useEffect(() => applyGlass(mats, glass, glassTint), [mats, glass, glassTint]);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
mats.halo.uniforms.uRadius.value = shape.haloRadius;
|
|
68
|
+
}, [mats, shape]);
|
|
69
|
+
const lightColor = useMemo(() => (config.colorMode ?? 'temperature') === 'color'
|
|
70
|
+
? new Color(config.color || '#ffffff')
|
|
71
|
+
: kelvinToColor(config.temperature ?? 2700), [config.colorMode, config.color, config.temperature]);
|
|
72
|
+
const tint = useMemo(() => new Color(glass === 'tinted' ? glassTint : '#ffffff'), [glass, glassTint]);
|
|
73
|
+
// The screw base sits close to the light; three's default shadow near (0.5)
|
|
74
|
+
// would clip it out of the shadow map.
|
|
75
|
+
const shadowNear = config.shadow?.cameraNear;
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
const light = lightRef.current;
|
|
78
|
+
if (!light || shadowNear !== undefined)
|
|
79
|
+
return;
|
|
80
|
+
light.shadow.camera.near = 0.05;
|
|
81
|
+
light.shadow.camera.updateProjectionMatrix();
|
|
82
|
+
}, [shadowNear]);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const group = groupRef.current;
|
|
85
|
+
if (!group)
|
|
86
|
+
return;
|
|
87
|
+
group.userData[SELF_OWNED_MATERIAL_KEY] = true;
|
|
88
|
+
return () => {
|
|
89
|
+
delete group.userData[SELF_OWNED_MATERIAL_KEY];
|
|
90
|
+
};
|
|
91
|
+
}, []);
|
|
92
|
+
const power = useRef({ level: on ? 1 : 0, base: baseIntensity, written: Number.NaN, seed: Math.random() * 1000 });
|
|
93
|
+
const scratch = useMemo(() => ({ heat: new Color(), glow: new Color() }), []);
|
|
94
|
+
useFrame(({ clock }, delta) => {
|
|
95
|
+
const light = lightRef.current;
|
|
96
|
+
if (!light)
|
|
97
|
+
return;
|
|
98
|
+
const p = power.current;
|
|
99
|
+
const cfg = merged.current;
|
|
100
|
+
const warmup = Math.max(num(cfg.warmup, 0.25), 0);
|
|
101
|
+
const flicker = Math.min(Math.max(num(cfg.flicker, 0), 0), 1);
|
|
102
|
+
const haloAmount = Math.min(Math.max(num(cfg.halo, 1), 0), 2);
|
|
103
|
+
if (light.intensity !== p.written)
|
|
104
|
+
p.base = light.intensity;
|
|
105
|
+
const target = on ? 1 : 0;
|
|
106
|
+
const tau = target > p.level ? warmup : warmup * 2.5;
|
|
107
|
+
p.level = tau <= 0 ? target : p.level + (target - p.level) * (1 - Math.exp(-Math.min(delta, 0.1) * 3 / tau));
|
|
108
|
+
if (Math.abs(target - p.level) < 1e-4)
|
|
109
|
+
p.level = target;
|
|
110
|
+
let flick = 1;
|
|
111
|
+
if (flicker > 0) {
|
|
112
|
+
const t = clock.elapsedTime + p.seed;
|
|
113
|
+
flick = 1 - flicker * (0.15 * noise(t * 13) + 0.6 * Math.pow(noise(t * 3.1), 4));
|
|
114
|
+
}
|
|
115
|
+
const output = Math.pow(p.level, 2.2) * flick;
|
|
116
|
+
light.intensity = p.base * output;
|
|
117
|
+
p.written = light.intensity;
|
|
118
|
+
// Glow follows the light's real output relative to the configured intensity.
|
|
119
|
+
const glowK = Math.min(output * (p.base / Math.max(baseIntensity, 1e-4)), 2);
|
|
120
|
+
const response = GLASS_RESPONSE[glass];
|
|
121
|
+
// Filament: grey when cold, heats through red/orange to near-white of the light colour.
|
|
122
|
+
const k = p.level * flick;
|
|
123
|
+
const heat = scratch.heat;
|
|
124
|
+
if (k < 0.35)
|
|
125
|
+
heat.copy(UNLIT).lerp(DULL_RED, k / 0.35);
|
|
126
|
+
else if (k < 0.7)
|
|
127
|
+
heat.copy(DULL_RED).lerp(ORANGE, (k - 0.35) / 0.35);
|
|
128
|
+
else
|
|
129
|
+
heat.copy(ORANGE).lerp(scratch.glow.copy(light.color).lerp(WHITE, 0.55), (k - 0.7) / 0.3);
|
|
130
|
+
mats.filament.color.copy(heat).multiplyScalar(k < 0.05 ? 1 : 1 + glowK * 2);
|
|
131
|
+
const glow = scratch.glow.copy(light.color).multiply(tint);
|
|
132
|
+
mats.glass.emissive.copy(glow).multiplyScalar(response.emissive * glowK);
|
|
133
|
+
mats.innerGlow.uniforms.uColor.value.copy(glow);
|
|
134
|
+
mats.innerGlow.uniforms.uStrength.value = response.inner * glowK;
|
|
135
|
+
mats.halo.uniforms.uColor.value.copy(glow);
|
|
136
|
+
mats.halo.uniforms.uStrength.value = response.halo * haloAmount * glowK;
|
|
137
|
+
});
|
|
138
|
+
const dy = -shape.filamentCenterY;
|
|
139
|
+
return (_jsxs("group", { ref: groupRef, children: [_jsx("pointLight", { ref: lightRef, color: lightColor, intensity: baseIntensity, decay: config.decay ?? 1, distance: config.distance ?? 0 }), _jsxs("group", { position: [0, dy, 0], children: [_jsx("mesh", { geometry: shape.filament, material: mats.filament }), shape.supports.map((g, i) => (_jsx("mesh", { geometry: g, material: i === 0 ? mats.stem : mats.wire, renderOrder: i === 0 ? 1 : 0 }, i))), _jsx("mesh", { geometry: shape.base, material: mats.metal, castShadow: true }), _jsx("mesh", { geometry: shape.glass, material: mats.glass, renderOrder: 2 }), _jsx("mesh", { geometry: shape.glass, material: mats.innerGlow, scale: 0.97, renderOrder: 3 })] }), _jsx("mesh", { material: mats.halo, renderOrder: 4, frustumCulled: false, children: _jsx("planeGeometry", { args: HALO_SEGMENTS }) })] }));
|
|
140
|
+
};
|
|
141
|
+
export default LightBulb;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { MeshBasicMaterial, MeshPhysicalMaterial, MeshStandardMaterial, ShaderMaterial } from 'three';
|
|
2
|
+
import type { LightBulbGlass } from './types';
|
|
3
|
+
export interface BulbMaterials {
|
|
4
|
+
glass: MeshPhysicalMaterial;
|
|
5
|
+
filament: MeshBasicMaterial;
|
|
6
|
+
metal: MeshStandardMaterial;
|
|
7
|
+
wire: MeshStandardMaterial;
|
|
8
|
+
stem: MeshPhysicalMaterial;
|
|
9
|
+
innerGlow: ShaderMaterial;
|
|
10
|
+
halo: ShaderMaterial;
|
|
11
|
+
}
|
|
12
|
+
/** Per-glass tuning of how much of the light the glass itself shows. */
|
|
13
|
+
export interface GlassResponse {
|
|
14
|
+
/** Emissive strength of the glass surface itself. */
|
|
15
|
+
emissive: number;
|
|
16
|
+
/** innerGlow strength + falloff power (low power = even, frosted-looking fill). */
|
|
17
|
+
inner: number;
|
|
18
|
+
innerPower: number;
|
|
19
|
+
/** Halo multiplier. */
|
|
20
|
+
halo: number;
|
|
21
|
+
}
|
|
22
|
+
export declare const GLASS_RESPONSE: Record<LightBulbGlass, GlassResponse>;
|
|
23
|
+
export declare function createBulbMaterials(): BulbMaterials;
|
|
24
|
+
/** Configure the glass surface for a finish. Emissive is driven per frame. */
|
|
25
|
+
export declare function applyGlass(m: BulbMaterials, glass: LightBulbGlass, tint: string): void;
|
|
26
|
+
export declare function disposeBulbMaterials(m: BulbMaterials): void;
|
|
27
|
+
//# sourceMappingURL=bulbMaterials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulbMaterials.d.ts","sourceRoot":"","sources":["../../../src/components/lightBulb/bulbMaterials.ts"],"names":[],"mappings":"AAaA,OAAO,EAAsC,iBAAiB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAC1I,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,oBAAoB,CAAC;IAC5B,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,KAAK,EAAE,oBAAoB,CAAC;IAC5B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,SAAS,EAAE,cAAc,CAAC;IAC1B,IAAI,EAAE,cAAc,CAAC;CACxB;AAED,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC1B,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,aAAa,CAIhE,CAAC;AA8EF,wBAAgB,mBAAmB,IAAI,aAAa,CAanD;AAED,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAsBtF;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAE3D"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// bulbMaterials — glass, filament, metal and the two self-drawn glow layers
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// The viewer renders with NoToneMapping and no postprocessing, so there is no
|
|
6
|
+
// bloom to lean on. The bulb draws its own glow instead:
|
|
7
|
+
// - innerGlow: a view-facing fresnel shell just inside the glass — light
|
|
8
|
+
// scattering in the glass, strongest where you look straight through it.
|
|
9
|
+
// - halo: a camera-facing quad with a soft radial falloff, sized in object
|
|
10
|
+
// space so it scales with the bulb.
|
|
11
|
+
// Both are additive and depthWrite-off; their strength is set per frame from
|
|
12
|
+
// the light's actual intensity, so animations / mouseMove dim the glow too.
|
|
13
|
+
//
|
|
14
|
+
import { AdditiveBlending, Color, FrontSide, MeshBasicMaterial, MeshPhysicalMaterial, MeshStandardMaterial, ShaderMaterial } from 'three';
|
|
15
|
+
export const GLASS_RESPONSE = {
|
|
16
|
+
clear: { emissive: 0.02, inner: 0.14, innerPower: 4.0, halo: 0.7 },
|
|
17
|
+
frosted: { emissive: 0.9, inner: 0.55, innerPower: 0.8, halo: 1.15 },
|
|
18
|
+
tinted: { emissive: 0.3, inner: 0.5, innerPower: 1.6, halo: 0.9 },
|
|
19
|
+
};
|
|
20
|
+
const OUTPUT = /* glsl */ `
|
|
21
|
+
#include <tonemapping_fragment>
|
|
22
|
+
#include <colorspace_fragment>
|
|
23
|
+
`;
|
|
24
|
+
function innerGlowMaterial() {
|
|
25
|
+
return new ShaderMaterial({
|
|
26
|
+
uniforms: {
|
|
27
|
+
uColor: { value: new Color(1, 0.7, 0.4) },
|
|
28
|
+
uStrength: { value: 0 },
|
|
29
|
+
uPower: { value: 3 },
|
|
30
|
+
},
|
|
31
|
+
vertexShader: /* glsl */ `
|
|
32
|
+
varying vec3 vNormal;
|
|
33
|
+
varying vec3 vView;
|
|
34
|
+
void main() {
|
|
35
|
+
vec4 mv = modelViewMatrix * vec4(position, 1.0);
|
|
36
|
+
vNormal = normalize(normalMatrix * normal);
|
|
37
|
+
vView = normalize(-mv.xyz);
|
|
38
|
+
gl_Position = projectionMatrix * mv;
|
|
39
|
+
}`,
|
|
40
|
+
fragmentShader: /* glsl */ `
|
|
41
|
+
uniform vec3 uColor;
|
|
42
|
+
uniform float uStrength;
|
|
43
|
+
uniform float uPower;
|
|
44
|
+
varying vec3 vNormal;
|
|
45
|
+
varying vec3 vView;
|
|
46
|
+
void main() {
|
|
47
|
+
float facing = clamp(dot(normalize(vNormal), normalize(vView)), 0.0, 1.0);
|
|
48
|
+
gl_FragColor = vec4(uColor * uStrength * pow(facing, uPower), 1.0);
|
|
49
|
+
${OUTPUT}
|
|
50
|
+
}`,
|
|
51
|
+
transparent: true,
|
|
52
|
+
depthWrite: false,
|
|
53
|
+
blending: AdditiveBlending,
|
|
54
|
+
side: FrontSide,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function haloMaterial() {
|
|
58
|
+
return new ShaderMaterial({
|
|
59
|
+
uniforms: {
|
|
60
|
+
uColor: { value: new Color(1, 0.7, 0.4) },
|
|
61
|
+
uStrength: { value: 0 },
|
|
62
|
+
uRadius: { value: 1 },
|
|
63
|
+
},
|
|
64
|
+
vertexShader: /* glsl */ `
|
|
65
|
+
uniform float uRadius;
|
|
66
|
+
varying vec2 vUv;
|
|
67
|
+
void main() {
|
|
68
|
+
// Billboard: expand the quad in view space around the object origin,
|
|
69
|
+
// sized by the object's world scale so the halo tracks the bulb.
|
|
70
|
+
vec4 mv = modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
|
|
71
|
+
float s = length(modelMatrix[0].xyz) * uRadius;
|
|
72
|
+
mv.xy += position.xy * s;
|
|
73
|
+
vUv = position.xy;
|
|
74
|
+
gl_Position = projectionMatrix * mv;
|
|
75
|
+
}`,
|
|
76
|
+
fragmentShader: /* glsl */ `
|
|
77
|
+
uniform vec3 uColor;
|
|
78
|
+
uniform float uStrength;
|
|
79
|
+
varying vec2 vUv;
|
|
80
|
+
void main() {
|
|
81
|
+
float d = length(vUv);
|
|
82
|
+
float core = exp(-d * 9.0);
|
|
83
|
+
float wide = exp(-d * d * 5.0) * 0.35;
|
|
84
|
+
float a = (core + wide) * (1.0 - smoothstep(0.7, 1.0, d));
|
|
85
|
+
gl_FragColor = vec4(uColor * uStrength * a, 1.0);
|
|
86
|
+
${OUTPUT}
|
|
87
|
+
}`,
|
|
88
|
+
transparent: true,
|
|
89
|
+
depthWrite: false,
|
|
90
|
+
blending: AdditiveBlending,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export function createBulbMaterials() {
|
|
94
|
+
return {
|
|
95
|
+
glass: new MeshPhysicalMaterial({
|
|
96
|
+
color: '#ffffff', roughness: 0.05, metalness: 0, transparent: true, opacity: 0.18,
|
|
97
|
+
clearcoat: 1, clearcoatRoughness: 0.05, depthWrite: false, emissive: '#000000',
|
|
98
|
+
}),
|
|
99
|
+
filament: new MeshBasicMaterial({ color: '#202020', toneMapped: false }),
|
|
100
|
+
metal: new MeshStandardMaterial({ color: '#b9ae98', metalness: 1, roughness: 0.3 }),
|
|
101
|
+
wire: new MeshStandardMaterial({ color: '#9a9a9a', metalness: 1, roughness: 0.35 }),
|
|
102
|
+
stem: new MeshPhysicalMaterial({ color: '#ffffff', roughness: 0.1, transparent: true, opacity: 0.25, depthWrite: false }),
|
|
103
|
+
innerGlow: innerGlowMaterial(),
|
|
104
|
+
halo: haloMaterial(),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** Configure the glass surface for a finish. Emissive is driven per frame. */
|
|
108
|
+
export function applyGlass(m, glass, tint) {
|
|
109
|
+
const g = m.glass;
|
|
110
|
+
if (glass === 'frosted') {
|
|
111
|
+
g.color.set('#f4f1ec');
|
|
112
|
+
g.roughness = 0.9;
|
|
113
|
+
g.opacity = 0.88;
|
|
114
|
+
g.clearcoat = 0;
|
|
115
|
+
}
|
|
116
|
+
else if (glass === 'tinted') {
|
|
117
|
+
g.color.set(tint);
|
|
118
|
+
g.roughness = 0.08;
|
|
119
|
+
g.opacity = 0.42;
|
|
120
|
+
g.clearcoat = 1;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
g.color.set('#ffffff');
|
|
124
|
+
g.roughness = 0.05;
|
|
125
|
+
g.opacity = 0.18;
|
|
126
|
+
g.clearcoat = 1;
|
|
127
|
+
}
|
|
128
|
+
m.innerGlow.uniforms.uPower.value = GLASS_RESPONSE[glass].innerPower;
|
|
129
|
+
g.needsUpdate = true;
|
|
130
|
+
}
|
|
131
|
+
export function disposeBulbMaterials(m) {
|
|
132
|
+
Object.values(m).forEach((mat) => mat.dispose());
|
|
133
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BufferGeometry } from 'three';
|
|
2
|
+
import type { LightBulbShape } from './types';
|
|
3
|
+
export interface BulbShapeGeometry {
|
|
4
|
+
glass: BufferGeometry;
|
|
5
|
+
filament: BufferGeometry;
|
|
6
|
+
supports: BufferGeometry[];
|
|
7
|
+
base: BufferGeometry;
|
|
8
|
+
/** Bulb-space height of the filament centre — becomes the object origin. */
|
|
9
|
+
filamentCenterY: number;
|
|
10
|
+
/** Radius of the glow halo, bulb space. */
|
|
11
|
+
haloRadius: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function buildBulbShape(shape: LightBulbShape | undefined): BulbShapeGeometry;
|
|
14
|
+
export declare function disposeBulbShape(g: BulbShapeGeometry): void;
|
|
15
|
+
//# sourceMappingURL=bulbShapes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulbShapes.d.ts","sourceRoot":"","sources":["../../../src/components/lightBulb/bulbShapes.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,cAAc,CAAC;IACtB,QAAQ,EAAE,cAAc,CAAC;IACzB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,IAAI,EAAE,cAAc,CAAC;IACrB,4EAA4E;IAC5E,eAAe,EAAE,MAAM,CAAC;IACxB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;CACtB;AAyKD,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,GAAG,iBAAiB,CAEnF;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAK3D"}
|