@3driseai/3drise-viewer 0.1.22 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 -13
- package/dist/components/ModelPrimitive.d.ts.map +1 -1
- package/dist/components/ModelPrimitive.js +4 -6
- package/dist/components/ObjectNode.js +1 -1
- package/dist/components/ParticlesGenerator.d.ts +3 -1
- package/dist/components/ParticlesGenerator.d.ts.map +1 -1
- package/dist/components/ParticlesGenerator.js +2 -2
- package/dist/components/ProjectLoader.js +1 -1
- package/dist/components/SceneBuilder.d.ts.map +1 -1
- package/dist/components/SceneBuilder.js +5 -1
- package/dist/components/candle/Candle.d.ts +14 -0
- package/dist/components/candle/Candle.d.ts.map +1 -0
- package/dist/components/candle/Candle.js +249 -0
- package/dist/components/candle/candleGeometry.d.ts +23 -0
- package/dist/components/candle/candleGeometry.d.ts.map +1 -0
- package/dist/components/candle/candleGeometry.js +73 -0
- package/dist/components/candle/candleMaterials.d.ts +24 -0
- package/dist/components/candle/candleMaterials.d.ts.map +1 -0
- package/dist/components/candle/candleMaterials.js +433 -0
- package/dist/components/candle/flameMotion.d.ts +64 -0
- package/dist/components/candle/flameMotion.d.ts.map +1 -0
- package/dist/components/candle/flameMotion.js +138 -0
- package/dist/components/candle/index.d.ts +9 -0
- package/dist/components/candle/index.d.ts.map +1 -0
- package/dist/components/candle/index.js +6 -0
- 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.map +1 -1
- package/dist/components/lightBulb/LightBulb.js +10 -13
- package/dist/components/particles/BioluminescentWakeParticles.d.ts +3 -1
- package/dist/components/particles/BioluminescentWakeParticles.d.ts.map +1 -1
- package/dist/components/particles/BioluminescentWakeParticles.js +7 -3
- package/dist/components/particles/BrownianDustParticles.d.ts +3 -1
- package/dist/components/particles/BrownianDustParticles.d.ts.map +1 -1
- package/dist/components/particles/BrownianDustParticles.js +7 -3
- package/dist/components/particles/ChladniSandParticles.d.ts +3 -1
- package/dist/components/particles/ChladniSandParticles.d.ts.map +1 -1
- package/dist/components/particles/ChladniSandParticles.js +7 -3
- package/dist/components/particles/ElectronOrbitalsParticles.d.ts +3 -1
- package/dist/components/particles/ElectronOrbitalsParticles.d.ts.map +1 -1
- package/dist/components/particles/ElectronOrbitalsParticles.js +7 -3
- package/dist/components/particles/FaradayPowderParticles.d.ts +3 -1
- package/dist/components/particles/FaradayPowderParticles.d.ts.map +1 -1
- package/dist/components/particles/FaradayPowderParticles.js +7 -3
- package/dist/components/particles/GrinderSparksParticles.d.ts +3 -1
- package/dist/components/particles/GrinderSparksParticles.d.ts.map +1 -1
- package/dist/components/particles/GrinderSparksParticles.js +7 -3
- package/dist/components/particles/NeonRainParticles.d.ts +3 -1
- package/dist/components/particles/NeonRainParticles.d.ts.map +1 -1
- package/dist/components/particles/NeonRainParticles.js +7 -3
- package/dist/components/particles/TidalStreamParticles.d.ts +3 -1
- package/dist/components/particles/TidalStreamParticles.d.ts.map +1 -1
- package/dist/components/particles/TidalStreamParticles.js +7 -3
- package/dist/components/spotBeam/SpotBeam.d.ts +14 -0
- package/dist/components/spotBeam/SpotBeam.d.ts.map +1 -0
- package/dist/components/spotBeam/SpotBeam.js +244 -0
- package/dist/components/spotBeam/beamPatterns.d.ts +11 -0
- package/dist/components/spotBeam/beamPatterns.d.ts.map +1 -0
- package/dist/components/spotBeam/beamPatterns.js +106 -0
- package/dist/components/spotBeam/index.d.ts +7 -0
- package/dist/components/spotBeam/index.d.ts.map +1 -0
- package/dist/components/spotBeam/index.js +4 -0
- package/dist/components/spotBeam/spotBeamMaterials.d.ts +16 -0
- package/dist/components/spotBeam/spotBeamMaterials.d.ts.map +1 -0
- package/dist/components/spotBeam/spotBeamMaterials.js +307 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useLightConfig.d.ts +25 -0
- package/dist/hooks/useLightConfig.d.ts.map +1 -0
- package/dist/hooks/useLightConfig.js +125 -0
- package/dist/hooks/useLightLive.d.ts.map +1 -1
- package/dist/hooks/useLightLive.js +12 -2
- package/dist/hooks/useShadowApplication.d.ts +4 -4
- package/dist/hooks/useShadowApplication.d.ts.map +1 -1
- package/dist/hooks/useShadowApplication.js +25 -10
- package/package.json +2 -2
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// beamPatterns — the shape the light throws
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// A spot light's beam is shaped by what sits in front of it, so this generates
|
|
6
|
+
// that mask once, as a texture: three projects it through the light's own
|
|
7
|
+
// frustum (SpotLight.map), and the beam shader samples the same texture, so the
|
|
8
|
+
// haze in the air and the pool on the floor always agree.
|
|
9
|
+
//
|
|
10
|
+
// Everything is masked by the cone's own circle, so the beam edge stays round
|
|
11
|
+
// whatever the pattern does inside it.
|
|
12
|
+
//
|
|
13
|
+
import { CanvasTexture, LinearFilter, SRGBColorSpace } from 'three';
|
|
14
|
+
export const BEAM_PATTERNS = ['round', 'slot', 'bars', 'blinds', 'window', 'leaves'];
|
|
15
|
+
const SIZE = 512;
|
|
16
|
+
/** Soft-edged mask of the cone itself; everything else multiplies into it. */
|
|
17
|
+
function coneMask(g, softness) {
|
|
18
|
+
const r = SIZE / 2;
|
|
19
|
+
const inner = Math.max(0.02, 1 - softness) * r;
|
|
20
|
+
const grad = g.createRadialGradient(r, r, inner, r, r, r);
|
|
21
|
+
grad.addColorStop(0, 'rgba(255,255,255,1)');
|
|
22
|
+
grad.addColorStop(1, 'rgba(255,255,255,0)');
|
|
23
|
+
g.globalCompositeOperation = 'destination-in';
|
|
24
|
+
g.fillStyle = grad;
|
|
25
|
+
g.fillRect(0, 0, SIZE, SIZE);
|
|
26
|
+
g.globalCompositeOperation = 'source-over';
|
|
27
|
+
}
|
|
28
|
+
function drawPattern(g, pattern, softness) {
|
|
29
|
+
const s = SIZE;
|
|
30
|
+
g.fillStyle = '#ffffff';
|
|
31
|
+
if (pattern === 'round') {
|
|
32
|
+
g.fillRect(0, 0, s, s);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (pattern === 'slot') {
|
|
36
|
+
const h = s * 0.3;
|
|
37
|
+
g.fillRect(0, (s - h) / 2, s, h);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (pattern === 'bars') {
|
|
41
|
+
const n = 7, w = s / n;
|
|
42
|
+
for (let i = 0; i < n; i++)
|
|
43
|
+
if (i % 2 === 0)
|
|
44
|
+
g.fillRect(i * w, 0, w * 0.62, s);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (pattern === 'blinds') {
|
|
48
|
+
const n = 9, h = s / n;
|
|
49
|
+
for (let i = 0; i < n; i++)
|
|
50
|
+
g.fillRect(0, i * h, s, h * 0.5);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (pattern === 'window') {
|
|
54
|
+
const bar = s * 0.045;
|
|
55
|
+
g.fillRect(0, 0, s, s);
|
|
56
|
+
g.fillStyle = '#000000';
|
|
57
|
+
g.fillRect(s / 2 - bar / 2, 0, bar, s);
|
|
58
|
+
g.fillRect(0, s / 2 - bar / 2, s, bar);
|
|
59
|
+
g.fillRect(0, 0, s, bar * 0.8);
|
|
60
|
+
g.fillRect(0, s - bar * 0.8, s, bar * 0.8);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// leaves: overlapping blobs, deterministic so the same config is the same dapple
|
|
64
|
+
let seed = 9;
|
|
65
|
+
const rnd = () => (seed = (seed * 1664525 + 1013904223) % 4294967296) / 4294967296;
|
|
66
|
+
g.fillRect(0, 0, s, s);
|
|
67
|
+
g.fillStyle = '#000000';
|
|
68
|
+
for (let i = 0; i < 34; i++) {
|
|
69
|
+
const x = rnd() * s, y = rnd() * s, r = s * (0.05 + rnd() * 0.11);
|
|
70
|
+
g.beginPath();
|
|
71
|
+
g.ellipse(x, y, r, r * (0.55 + rnd() * 0.7), rnd() * Math.PI, 0, Math.PI * 2);
|
|
72
|
+
g.fill();
|
|
73
|
+
}
|
|
74
|
+
void softness;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Build the mask. `rotation` is baked in rather than applied as a uv transform:
|
|
78
|
+
* three samples a spot light's map through the light frustum and ignores the
|
|
79
|
+
* texture matrix, so rotating it any other way would turn the haze without
|
|
80
|
+
* turning the pool.
|
|
81
|
+
*/
|
|
82
|
+
export function createBeamPattern(pattern, softness, rotationDeg) {
|
|
83
|
+
const canvas = document.createElement('canvas');
|
|
84
|
+
canvas.width = canvas.height = SIZE;
|
|
85
|
+
const g = canvas.getContext('2d');
|
|
86
|
+
g.clearRect(0, 0, SIZE, SIZE);
|
|
87
|
+
g.save();
|
|
88
|
+
g.translate(SIZE / 2, SIZE / 2);
|
|
89
|
+
g.rotate((rotationDeg * Math.PI) / 180);
|
|
90
|
+
g.translate(-SIZE / 2, -SIZE / 2);
|
|
91
|
+
drawPattern(g, pattern, softness);
|
|
92
|
+
g.restore();
|
|
93
|
+
// Soften the pattern edges as well as the cone rim.
|
|
94
|
+
if (softness > 0.02 && pattern !== 'round') {
|
|
95
|
+
g.filter = `blur(${Math.round(softness * 26)}px)`;
|
|
96
|
+
g.drawImage(canvas, 0, 0);
|
|
97
|
+
g.filter = 'none';
|
|
98
|
+
}
|
|
99
|
+
coneMask(g, Math.max(softness, 0.06));
|
|
100
|
+
const texture = new CanvasTexture(canvas);
|
|
101
|
+
texture.colorSpace = SRGBColorSpace;
|
|
102
|
+
texture.minFilter = LinearFilter;
|
|
103
|
+
texture.magFilter = LinearFilter;
|
|
104
|
+
texture.needsUpdate = true;
|
|
105
|
+
return texture;
|
|
106
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as SpotBeam } from './SpotBeam';
|
|
2
|
+
export type { SpotBeamProps } from './SpotBeam';
|
|
3
|
+
export { BEAM_PATTERNS, createBeamPattern } from './beamPatterns';
|
|
4
|
+
export type { BeamPattern } from './beamPatterns';
|
|
5
|
+
export { createSpotBeamMaterials, disposeSpotBeamMaterials } from './spotBeamMaterials';
|
|
6
|
+
export type { SpotBeamMaterials } from './spotBeamMaterials';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/spotBeam/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AACxF,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// A spot light with a visible, pattern-shaped beam (config.type 'spotBeam').
|
|
2
|
+
export { default as SpotBeam } from './SpotBeam.js';
|
|
3
|
+
export { BEAM_PATTERNS, createBeamPattern } from './beamPatterns.js';
|
|
4
|
+
export { createSpotBeamMaterials, disposeSpotBeamMaterials } from './spotBeamMaterials.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MeshBasicMaterial, ShaderMaterial } from 'three';
|
|
2
|
+
export type BeamMode = 'none' | 'soft' | 'layered' | 'volumetric';
|
|
3
|
+
export declare const BEAM_MODES: BeamMode[];
|
|
4
|
+
export interface SpotBeamMaterials {
|
|
5
|
+
lens: MeshBasicMaterial;
|
|
6
|
+
halo: ShaderMaterial;
|
|
7
|
+
/** One cone shell, lit by how obliquely you look through it. One draw. */
|
|
8
|
+
soft: ShaderMaterial;
|
|
9
|
+
/** Nested shells: cheap depth, approximates the pattern. */
|
|
10
|
+
layered: ShaderMaterial;
|
|
11
|
+
/** Raymarched cone interior: the only mode where the pattern exists in the air. */
|
|
12
|
+
volumetric: ShaderMaterial;
|
|
13
|
+
}
|
|
14
|
+
export declare function createSpotBeamMaterials(): SpotBeamMaterials;
|
|
15
|
+
export declare function disposeSpotBeamMaterials(m: SpotBeamMaterials): void;
|
|
16
|
+
//# sourceMappingURL=spotBeamMaterials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotBeamMaterials.d.ts","sourceRoot":"","sources":["../../../src/components/spotBeam/spotBeamMaterials.ts"],"names":[],"mappings":"AAcA,OAAO,EAAiD,iBAAiB,EAAE,cAAc,EAAW,MAAM,OAAO,CAAC;AAGlH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,YAAY,CAAC;AAElE,eAAO,MAAM,UAAU,EAAE,QAAQ,EAA8C,CAAC;AAEhF,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,0EAA0E;IAC1E,IAAI,EAAE,cAAc,CAAC;IACrB,4DAA4D;IAC5D,OAAO,EAAE,cAAc,CAAC;IACxB,mFAAmF;IACnF,UAAU,EAAE,cAAc,CAAC;CAC9B;AA+RD,wBAAgB,uBAAuB,IAAI,iBAAiB,CAQ3D;AAED,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAEnE"}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// spotBeamMaterials — the emitter, its halo, and the haze in the beam
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// The viewer has no tone mapping and no bloom, so everything bright here is
|
|
6
|
+
// drawn by hand: an emissive lens face, a camera-facing halo, and the beam.
|
|
7
|
+
//
|
|
8
|
+
// The beam is a stack of nested cone shells rendered as one InstancedMesh. A
|
|
9
|
+
// single shell can only ever sample the rim of the projected pattern; nesting
|
|
10
|
+
// them (aFrac = 0 at the axis, 1 at the cone edge) samples the pattern across
|
|
11
|
+
// its whole width, which is what makes a slot read as a slot in the air as well
|
|
12
|
+
// as on the floor. Density is weighted towards the axis, the way a real cone
|
|
13
|
+
// has more haze to look through down the middle.
|
|
14
|
+
//
|
|
15
|
+
import { AdditiveBlending, BackSide, Color, DoubleSide, MeshBasicMaterial, ShaderMaterial, Vector3 } from 'three';
|
|
16
|
+
export const BEAM_MODES = ['none', 'soft', 'layered', 'volumetric'];
|
|
17
|
+
const OUTPUT = /* glsl */ `
|
|
18
|
+
#include <tonemapping_fragment>
|
|
19
|
+
#include <colorspace_fragment>
|
|
20
|
+
`;
|
|
21
|
+
function haloMaterial() {
|
|
22
|
+
return new ShaderMaterial({
|
|
23
|
+
uniforms: {
|
|
24
|
+
uColor: { value: new Color(1, 0.9, 0.75) },
|
|
25
|
+
uStrength: { value: 0 },
|
|
26
|
+
uRadius: { value: 0.55 },
|
|
27
|
+
},
|
|
28
|
+
vertexShader: /* glsl */ `
|
|
29
|
+
uniform float uRadius;
|
|
30
|
+
varying vec2 vUv;
|
|
31
|
+
void main() {
|
|
32
|
+
vec4 mv = modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
|
|
33
|
+
float s = length(modelMatrix[0].xyz) * uRadius;
|
|
34
|
+
mv.xy += position.xy * s;
|
|
35
|
+
vUv = position.xy;
|
|
36
|
+
gl_Position = projectionMatrix * mv;
|
|
37
|
+
}`,
|
|
38
|
+
fragmentShader: /* glsl */ `
|
|
39
|
+
uniform vec3 uColor;
|
|
40
|
+
uniform float uStrength;
|
|
41
|
+
varying vec2 vUv;
|
|
42
|
+
void main() {
|
|
43
|
+
float d = length(vUv);
|
|
44
|
+
float a = (exp(-d * 9.0) + exp(-d * d * 5.0) * 0.3) * (1.0 - smoothstep(0.65, 1.0, d));
|
|
45
|
+
gl_FragColor = vec4(uColor * uStrength * a, 1.0);
|
|
46
|
+
${OUTPUT}
|
|
47
|
+
}`,
|
|
48
|
+
transparent: true,
|
|
49
|
+
depthWrite: false,
|
|
50
|
+
blending: AdditiveBlending,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function beamMaterial() {
|
|
54
|
+
return new ShaderMaterial({
|
|
55
|
+
uniforms: {
|
|
56
|
+
uColor: { value: new Color(1, 0.9, 0.75) },
|
|
57
|
+
uStrength: { value: 0 },
|
|
58
|
+
uPenumbra: { value: 0.3 },
|
|
59
|
+
uPattern: { value: null },
|
|
60
|
+
uNoise: { value: 0.35 },
|
|
61
|
+
uTime: { value: 0 },
|
|
62
|
+
uLayers: { value: 6 },
|
|
63
|
+
},
|
|
64
|
+
vertexShader: /* glsl */ `
|
|
65
|
+
attribute float aFrac; // 0 at the axis, 1 at the cone edge
|
|
66
|
+
varying float vFrac;
|
|
67
|
+
varying float vT; // 0 at the lens, 1 at the far end
|
|
68
|
+
varying vec2 vPattern; // where this point sits in the mask
|
|
69
|
+
varying vec3 vLocal;
|
|
70
|
+
void main() {
|
|
71
|
+
vec3 p = position;
|
|
72
|
+
vFrac = aFrac;
|
|
73
|
+
vT = clamp(p.y, 0.0, 1.0);
|
|
74
|
+
// Unit cone: at height y the rim sits at radius y, so dividing by
|
|
75
|
+
// y gives the projector's normalised coordinates — the same ones
|
|
76
|
+
// three uses to project the light's map.
|
|
77
|
+
vec2 proj = p.y > 1e-4 ? vec2(p.x, p.z) / p.y : vec2(0.0);
|
|
78
|
+
vPattern = proj * aFrac * 0.5 + 0.5;
|
|
79
|
+
vec3 scaled = vec3(p.x * aFrac, p.y, p.z * aFrac);
|
|
80
|
+
vLocal = scaled;
|
|
81
|
+
vec4 mv = modelViewMatrix * instanceMatrix * vec4(scaled, 1.0);
|
|
82
|
+
gl_Position = projectionMatrix * mv;
|
|
83
|
+
}`,
|
|
84
|
+
fragmentShader: /* glsl */ `
|
|
85
|
+
uniform vec3 uColor;
|
|
86
|
+
uniform float uStrength;
|
|
87
|
+
uniform float uPenumbra;
|
|
88
|
+
uniform sampler2D uPattern;
|
|
89
|
+
uniform float uNoise;
|
|
90
|
+
uniform float uTime;
|
|
91
|
+
uniform float uLayers;
|
|
92
|
+
varying float vFrac;
|
|
93
|
+
varying float vT;
|
|
94
|
+
varying vec2 vPattern;
|
|
95
|
+
varying vec3 vLocal;
|
|
96
|
+
|
|
97
|
+
// cheap value noise, for haze that drifts instead of sitting still
|
|
98
|
+
float hash(vec3 p) { return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453); }
|
|
99
|
+
float vnoise(vec3 p) {
|
|
100
|
+
vec3 i = floor(p), f = fract(p);
|
|
101
|
+
f = f * f * (3.0 - 2.0 * f);
|
|
102
|
+
float n000 = hash(i), n100 = hash(i + vec3(1,0,0));
|
|
103
|
+
float n010 = hash(i + vec3(0,1,0)), n110 = hash(i + vec3(1,1,0));
|
|
104
|
+
float n001 = hash(i + vec3(0,0,1)), n101 = hash(i + vec3(1,0,1));
|
|
105
|
+
float n011 = hash(i + vec3(0,1,1)), n111 = hash(i + vec3(1,1,1));
|
|
106
|
+
return mix(mix(mix(n000,n100,f.x), mix(n010,n110,f.x), f.y),
|
|
107
|
+
mix(mix(n001,n101,f.x), mix(n011,n111,f.x), f.y), f.z);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
void main() {
|
|
111
|
+
float mask = texture2D(uPattern, vPattern).r;
|
|
112
|
+
if (mask < 0.004) discard;
|
|
113
|
+
|
|
114
|
+
// Density: thickest down the axis, thinning towards the rim, and
|
|
115
|
+
// fading with distance from the lens.
|
|
116
|
+
float core = pow(1.0 - vFrac * vFrac, 0.75);
|
|
117
|
+
float edge = mix(1.0, smoothstep(1.0, 0.55, vFrac), uPenumbra);
|
|
118
|
+
float fade = pow(1.0 - vT, 1.8);
|
|
119
|
+
float throat = smoothstep(0.0, 0.05, vT);
|
|
120
|
+
|
|
121
|
+
float haze = 1.0;
|
|
122
|
+
if (uNoise > 0.001) {
|
|
123
|
+
float n = vnoise(vLocal * 3.5 + vec3(0.0, -uTime * 0.35, uTime * 0.12));
|
|
124
|
+
haze = mix(1.0, 0.45 + n, uNoise);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
float a = mask * core * edge * fade * throat * haze / uLayers;
|
|
128
|
+
gl_FragColor = vec4(uColor * uStrength * a, 1.0);
|
|
129
|
+
${OUTPUT}
|
|
130
|
+
}`,
|
|
131
|
+
transparent: true,
|
|
132
|
+
depthWrite: false,
|
|
133
|
+
blending: AdditiveBlending,
|
|
134
|
+
side: DoubleSide,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* One shell, brightest where the view grazes it. No pattern in the air, but it
|
|
139
|
+
* is a single draw and reads perfectly well in a hazy room.
|
|
140
|
+
*/
|
|
141
|
+
function softMaterial() {
|
|
142
|
+
return new ShaderMaterial({
|
|
143
|
+
uniforms: {
|
|
144
|
+
uColor: { value: new Color(1, 0.9, 0.75) },
|
|
145
|
+
uStrength: { value: 0 },
|
|
146
|
+
uPenumbra: { value: 0.3 },
|
|
147
|
+
},
|
|
148
|
+
vertexShader: /* glsl */ `
|
|
149
|
+
varying vec3 vNormal;
|
|
150
|
+
varying vec3 vView;
|
|
151
|
+
varying float vT;
|
|
152
|
+
void main() {
|
|
153
|
+
vec4 mv = modelViewMatrix * vec4(position, 1.0);
|
|
154
|
+
vNormal = normalize(normalMatrix * normal);
|
|
155
|
+
vView = normalize(-mv.xyz);
|
|
156
|
+
vT = clamp(position.y, 0.0, 1.0);
|
|
157
|
+
gl_Position = projectionMatrix * mv;
|
|
158
|
+
}`,
|
|
159
|
+
fragmentShader: /* glsl */ `
|
|
160
|
+
uniform vec3 uColor;
|
|
161
|
+
uniform float uStrength;
|
|
162
|
+
uniform float uPenumbra;
|
|
163
|
+
varying vec3 vNormal;
|
|
164
|
+
varying vec3 vView;
|
|
165
|
+
varying float vT;
|
|
166
|
+
void main() {
|
|
167
|
+
float graze = 1.0 - abs(dot(normalize(vNormal), normalize(vView)));
|
|
168
|
+
float body = pow(graze, 3.0 - uPenumbra);
|
|
169
|
+
float fade = pow(1.0 - vT, 2.0);
|
|
170
|
+
float throat = smoothstep(0.0, 0.05, vT);
|
|
171
|
+
gl_FragColor = vec4(uColor * uStrength * body * fade * throat, 1.0);
|
|
172
|
+
${OUTPUT}
|
|
173
|
+
}`,
|
|
174
|
+
transparent: true,
|
|
175
|
+
depthWrite: false,
|
|
176
|
+
blending: AdditiveBlending,
|
|
177
|
+
side: DoubleSide,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The real thing: march the cone's interior from where the view ray enters it to
|
|
182
|
+
* where it leaves, sampling the projected mask at every step. Because each step
|
|
183
|
+
* knows its own position inside the cone, a slot is a slot in the air, blinds
|
|
184
|
+
* are blinds, and the haze thins with distance the way it should.
|
|
185
|
+
*
|
|
186
|
+
* Rendered on the cone's back faces so the volume is covered whether the camera
|
|
187
|
+
* is outside the beam or standing in it. Geometry is the unit cone (radius == y),
|
|
188
|
+
* so the ray maths works in that space and the mesh scale does the rest.
|
|
189
|
+
*/
|
|
190
|
+
function volumetricMaterial() {
|
|
191
|
+
return new ShaderMaterial({
|
|
192
|
+
uniforms: {
|
|
193
|
+
uColor: { value: new Color(1, 0.9, 0.75) },
|
|
194
|
+
uStrength: { value: 0 },
|
|
195
|
+
uPenumbra: { value: 0.3 },
|
|
196
|
+
uPattern: { value: null },
|
|
197
|
+
uNoise: { value: 0.35 },
|
|
198
|
+
uTime: { value: 0 },
|
|
199
|
+
uCamera: { value: new Vector3() },
|
|
200
|
+
uSteps: { value: 28 },
|
|
201
|
+
},
|
|
202
|
+
vertexShader: /* glsl */ `
|
|
203
|
+
varying vec3 vLocal;
|
|
204
|
+
void main() {
|
|
205
|
+
vLocal = position;
|
|
206
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
207
|
+
}`,
|
|
208
|
+
fragmentShader: /* glsl */ `
|
|
209
|
+
uniform vec3 uColor;
|
|
210
|
+
uniform float uStrength;
|
|
211
|
+
uniform float uPenumbra;
|
|
212
|
+
uniform sampler2D uPattern;
|
|
213
|
+
uniform float uNoise;
|
|
214
|
+
uniform float uTime;
|
|
215
|
+
uniform vec3 uCamera; // camera position in this cone's local space
|
|
216
|
+
uniform float uSteps;
|
|
217
|
+
varying vec3 vLocal;
|
|
218
|
+
|
|
219
|
+
float hash(vec3 p) { return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453); }
|
|
220
|
+
float vnoise(vec3 p) {
|
|
221
|
+
vec3 i = floor(p), f = fract(p);
|
|
222
|
+
f = f * f * (3.0 - 2.0 * f);
|
|
223
|
+
float n000 = hash(i), n100 = hash(i + vec3(1,0,0));
|
|
224
|
+
float n010 = hash(i + vec3(0,1,0)), n110 = hash(i + vec3(1,1,0));
|
|
225
|
+
float n001 = hash(i + vec3(0,0,1)), n101 = hash(i + vec3(1,0,1));
|
|
226
|
+
float n011 = hash(i + vec3(0,1,1)), n111 = hash(i + vec3(1,1,1));
|
|
227
|
+
return mix(mix(mix(n000,n100,f.x), mix(n010,n110,f.x), f.y),
|
|
228
|
+
mix(mix(n001,n101,f.x), mix(n011,n111,f.x), f.y), f.z);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
void main() {
|
|
232
|
+
vec3 ro = uCamera;
|
|
233
|
+
vec3 rd = normalize(vLocal - ro);
|
|
234
|
+
|
|
235
|
+
// Infinite cone x^2 + z^2 = y^2, opening along +y.
|
|
236
|
+
float a = rd.x * rd.x + rd.z * rd.z - rd.y * rd.y;
|
|
237
|
+
float b = 2.0 * (ro.x * rd.x + ro.z * rd.z - ro.y * rd.y);
|
|
238
|
+
float c = ro.x * ro.x + ro.z * ro.z - ro.y * ro.y;
|
|
239
|
+
|
|
240
|
+
float t0, t1;
|
|
241
|
+
if (abs(a) < 1e-5) {
|
|
242
|
+
if (abs(b) < 1e-6) discard;
|
|
243
|
+
t0 = -c / b; t1 = t0; // ray parallel to the surface
|
|
244
|
+
} else {
|
|
245
|
+
float disc = b * b - 4.0 * a * c;
|
|
246
|
+
if (disc < 0.0) discard;
|
|
247
|
+
float sq = sqrt(disc);
|
|
248
|
+
t0 = (-b - sq) / (2.0 * a);
|
|
249
|
+
t1 = (-b + sq) / (2.0 * a);
|
|
250
|
+
if (t0 > t1) { float tmp = t0; t0 = t1; t1 = tmp; }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Keep the half with y in [0, 1] — the cone, not its mirror.
|
|
254
|
+
const int MAX_STEPS = 48;
|
|
255
|
+
float tEnter = max(t0, 0.0);
|
|
256
|
+
float tExit = max(t1, 0.0);
|
|
257
|
+
if (tExit <= tEnter) { tEnter = 0.0; tExit = max(t1, 0.0); }
|
|
258
|
+
if (tExit <= 0.0) discard;
|
|
259
|
+
|
|
260
|
+
float acc = 0.0;
|
|
261
|
+
float steps = clamp(uSteps, 8.0, float(MAX_STEPS));
|
|
262
|
+
float dt = (tExit - tEnter) / steps;
|
|
263
|
+
// Dither the start so slices do not band.
|
|
264
|
+
float jitter = hash(vec3(gl_FragCoord.xy, uTime)) * dt;
|
|
265
|
+
|
|
266
|
+
for (int i = 0; i < MAX_STEPS; i++) {
|
|
267
|
+
if (float(i) >= steps) break;
|
|
268
|
+
vec3 p = ro + rd * (tEnter + jitter + float(i) * dt);
|
|
269
|
+
if (p.y <= 0.04 || p.y > 1.0) continue; // skip the singular near field
|
|
270
|
+
float r = length(p.xz) / p.y;
|
|
271
|
+
if (r > 1.0) continue;
|
|
272
|
+
|
|
273
|
+
vec2 uv = (p.xz / p.y) * 0.5 + 0.5;
|
|
274
|
+
float mask = texture2D(uPattern, uv).r;
|
|
275
|
+
if (mask < 0.004) continue;
|
|
276
|
+
|
|
277
|
+
float edge = mix(1.0, smoothstep(1.0, 0.45, r), uPenumbra);
|
|
278
|
+
float fall = 1.0 / (0.35 + p.y * p.y * 2.2); // falls off from the lens, softened so the near field does not swamp the pattern
|
|
279
|
+
float haze = 1.0;
|
|
280
|
+
if (uNoise > 0.001) {
|
|
281
|
+
float n = vnoise(p * 4.0 + vec3(0.0, -uTime * 0.3, uTime * 0.1));
|
|
282
|
+
haze = mix(1.0, 0.45 + n, uNoise);
|
|
283
|
+
}
|
|
284
|
+
acc += mask * edge * fall * haze * dt;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
gl_FragColor = vec4(uColor * uStrength * acc, 1.0);
|
|
288
|
+
${OUTPUT}
|
|
289
|
+
}`,
|
|
290
|
+
transparent: true,
|
|
291
|
+
depthWrite: false,
|
|
292
|
+
blending: AdditiveBlending,
|
|
293
|
+
side: BackSide,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
export function createSpotBeamMaterials() {
|
|
297
|
+
return {
|
|
298
|
+
lens: new MeshBasicMaterial({ color: '#0a0a0a', toneMapped: false }),
|
|
299
|
+
halo: haloMaterial(),
|
|
300
|
+
soft: softMaterial(),
|
|
301
|
+
layered: beamMaterial(),
|
|
302
|
+
volumetric: volumetricMaterial(),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
export function disposeSpotBeamMaterials(m) {
|
|
306
|
+
Object.values(m).forEach((mat) => mat.dispose());
|
|
307
|
+
}
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ export type { UseLightMouseMoveOptions } from './useLightMouseMove';
|
|
|
45
45
|
export { useLightMouseMove } from './useLightMouseMove';
|
|
46
46
|
export { useLightShadow } from './useLightShadow';
|
|
47
47
|
export { LIGHT_STRUCTURAL, applyLightLive, useLightLive } from './useLightLive';
|
|
48
|
+
export type { UseLightConfigResult } from './useLightConfig';
|
|
49
|
+
export { applyLightConfig, useLightConfig } from './useLightConfig';
|
|
48
50
|
export { useMaterialApplication } from './useMaterialApplication';
|
|
49
51
|
export type { UseMaterialOptionsReturn } from './useMaterialOptions';
|
|
50
52
|
export { useMaterialOptions } from './useMaterialOptions';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks/index.js
CHANGED
|
@@ -31,6 +31,7 @@ export { useLightHandlers } from './useLightHandlers.js';
|
|
|
31
31
|
export { useLightMouseMove } from './useLightMouseMove.js';
|
|
32
32
|
export { useLightShadow } from './useLightShadow.js';
|
|
33
33
|
export { LIGHT_STRUCTURAL, applyLightLive, useLightLive } from './useLightLive.js';
|
|
34
|
+
export { applyLightConfig, useLightConfig } from './useLightConfig.js';
|
|
34
35
|
export { useMaterialApplication } from './useMaterialApplication.js';
|
|
35
36
|
export { useMaterialOptions } from './useMaterialOptions.js';
|
|
36
37
|
export { useMaterialUniforms } from './useMaterialUniforms.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MutableRefObject, RefObject } from 'react';
|
|
2
|
+
import type { Light, Object3D } from 'three';
|
|
3
|
+
import type { AnimationOptions, MouseMoveDomain } from '@3driseai/3drise-core';
|
|
4
|
+
/**
|
|
5
|
+
* Push a light config onto a THREE light. Only properties the light actually
|
|
6
|
+
* has are written, so one function covers every light type.
|
|
7
|
+
*/
|
|
8
|
+
export declare function applyLightConfig(light: Light, cfg: Record<string, any>): void;
|
|
9
|
+
export interface UseLightConfigResult {
|
|
10
|
+
/** Committed config + live drags + animation overrides, refreshed every frame. */
|
|
11
|
+
merged: MutableRefObject<Record<string, any>>;
|
|
12
|
+
/** What the slider drags have written since the last commit. */
|
|
13
|
+
live: MutableRefObject<Record<string, string | number | boolean>>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Registers the live-edit handle, evaluates the light's keyframe animations and
|
|
17
|
+
* its pointer bindings, and applies the merged config to the light.
|
|
18
|
+
*
|
|
19
|
+
* Call it before the component's own useFrame: R3F runs frame callbacks in
|
|
20
|
+
* subscription order, so a light with visuals of its own reads a `merged` that is
|
|
21
|
+
* already up to date for this frame.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useLightConfig(rootRef: RefObject<Object3D | null>, lightRef: RefObject<Light | null>, id: string | undefined, config: Record<string, any>, animations: AnimationOptions[] | undefined, mouseMove?: MouseMoveDomain): UseLightConfigResult;
|
|
24
|
+
export default useLightConfig;
|
|
25
|
+
//# sourceMappingURL=useLightConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLightConfig.d.ts","sourceRoot":"","sources":["../../src/hooks/useLightConfig.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEzD,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAS/E;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CA6B7E;AAED,MAAM,WAAW,oBAAoB;IACjC,kFAAkF;IAClF,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9C,gEAAgE;IAChE,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;CACrE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC1B,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,EAC3B,UAAU,EAAE,gBAAgB,EAAE,GAAG,SAAS,EAC1C,SAAS,CAAC,EAAE,eAAe,GAC5B,oBAAoB,CA2DtB;AAED,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// useLightConfig — one config → the THREE light, every frame
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// The light family's version of the generative config pattern (see
|
|
6
|
+
// useConfigLive / configAnimations): one committed config in Redux, two
|
|
7
|
+
// per-frame override layers, one merge point.
|
|
8
|
+
//
|
|
9
|
+
// committed config → live drag overrides → keyframes → pointer (mouse move)
|
|
10
|
+
//
|
|
11
|
+
// `applyLightConfig` is the single door into the light: a knob it does not read
|
|
12
|
+
// cannot be animated, which is exactly what core's LIGHTS_ANIMATABLE lists. The
|
|
13
|
+
// merged config is also handed back through `merged`, so a light with visuals
|
|
14
|
+
// of its own (the bulb's filament, glow and halo) reads the same values the
|
|
15
|
+
// light got, instead of the committed config alone.
|
|
16
|
+
//
|
|
17
|
+
// This replaces useLightAnimation / useLightMouseMove (handleLightAnimations,
|
|
18
|
+
// handleLightMouseMove) for createdObject lights: those only knew the six
|
|
19
|
+
// properties of a THREE light, so a bulb's temperature, flicker or halo could
|
|
20
|
+
// never be driven. LightsManager's ambient + sun still use that path — they are
|
|
21
|
+
// scene lights, not config objects.
|
|
22
|
+
//
|
|
23
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
24
|
+
import { Color } from 'three';
|
|
25
|
+
import { DEFAULT_LIGHTS, getGenerativeAnimatable } from '@3driseai/3drise-core';
|
|
26
|
+
import { useFrame } from '@react-three/fiber';
|
|
27
|
+
import { applyConfigKeyframeAnimations, applyConfigMouseMove } from '../utils/configAnimations.js';
|
|
28
|
+
import { useLightLive } from './useLightLive.js';
|
|
29
|
+
import { kelvinToColor } from '../components/lightBulb/kelvin.js';
|
|
30
|
+
const tmpColor = new Color();
|
|
31
|
+
/**
|
|
32
|
+
* Push a light config onto a THREE light. Only properties the light actually
|
|
33
|
+
* has are written, so one function covers every light type.
|
|
34
|
+
*/
|
|
35
|
+
export function applyLightConfig(light, cfg) {
|
|
36
|
+
if (typeof cfg.intensity === 'number')
|
|
37
|
+
light.intensity = cfg.intensity;
|
|
38
|
+
// A type that has a colour-mode knob takes its colour from Kelvin in
|
|
39
|
+
// 'temperature' mode; everything else takes the colour knob. Read the knob
|
|
40
|
+
// rather than listing type names: `base` lays DEFAULT_LIGHTS under the config,
|
|
41
|
+
// so a type that has the knob always carries it and one that does not never
|
|
42
|
+
// will — and a new light type needs no edit here.
|
|
43
|
+
const byTemperature = typeof cfg.colorMode === 'string' && cfg.colorMode === 'temperature';
|
|
44
|
+
if (byTemperature) {
|
|
45
|
+
if (typeof cfg.temperature === 'number')
|
|
46
|
+
light.color.copy(kelvinToColor(cfg.temperature, tmpColor));
|
|
47
|
+
}
|
|
48
|
+
else if (typeof cfg.color === 'string' || cfg.color?.isColor) {
|
|
49
|
+
// A pointer binding on a colour interpolates to a THREE.Color, a keyframe
|
|
50
|
+
// or the committed config to a hex string; Color.set() takes either.
|
|
51
|
+
light.color.set(cfg.color);
|
|
52
|
+
}
|
|
53
|
+
const target = light;
|
|
54
|
+
if (typeof cfg.distance === 'number' && 'distance' in light)
|
|
55
|
+
target.distance = cfg.distance;
|
|
56
|
+
if (typeof cfg.decay === 'number' && 'decay' in light)
|
|
57
|
+
target.decay = cfg.decay;
|
|
58
|
+
if (typeof cfg.angle === 'number' && 'angle' in light)
|
|
59
|
+
target.angle = cfg.angle;
|
|
60
|
+
if (typeof cfg.penumbra === 'number' && 'penumbra' in light)
|
|
61
|
+
target.penumbra = cfg.penumbra;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Registers the live-edit handle, evaluates the light's keyframe animations and
|
|
65
|
+
* its pointer bindings, and applies the merged config to the light.
|
|
66
|
+
*
|
|
67
|
+
* Call it before the component's own useFrame: R3F runs frame callbacks in
|
|
68
|
+
* subscription order, so a light with visuals of its own reads a `merged` that is
|
|
69
|
+
* already up to date for this frame.
|
|
70
|
+
*/
|
|
71
|
+
export function useLightConfig(rootRef, lightRef, id, config, animations, mouseMove) {
|
|
72
|
+
// Fill the type's defaults under the committed config: a knob the saved config
|
|
73
|
+
// never stored (an older light has no `temperature`) has no value to drive
|
|
74
|
+
// from, and a keyframe or pointer binding on it would silently do nothing.
|
|
75
|
+
const base = useMemo(() => {
|
|
76
|
+
const merged = { ...(DEFAULT_LIGHTS[config?.type] ?? {}) };
|
|
77
|
+
// Skip undefined: a config that carries the key explicitly unset (a
|
|
78
|
+
// controller writing a cleared field) would otherwise erase the default
|
|
79
|
+
// and leave the knob with nothing to drive from.
|
|
80
|
+
for (const key of Object.keys(config ?? {})) {
|
|
81
|
+
if (config[key] !== undefined)
|
|
82
|
+
merged[key] = config[key];
|
|
83
|
+
}
|
|
84
|
+
return merged;
|
|
85
|
+
}, [config]);
|
|
86
|
+
const live = useLightLive(rootRef, lightRef, id, base);
|
|
87
|
+
const merged = useRef(base);
|
|
88
|
+
const animOverride = useRef({});
|
|
89
|
+
const animFrom = useRef({});
|
|
90
|
+
const animStart = useRef(performance.now());
|
|
91
|
+
// Editing the keyframes restarts them, and re-samples every `from`-less start value.
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
animStart.current = performance.now();
|
|
94
|
+
animFrom.current = {};
|
|
95
|
+
for (const k of Object.keys(animOverride.current))
|
|
96
|
+
delete animOverride.current[k];
|
|
97
|
+
}, [animations]);
|
|
98
|
+
// A committed config supersedes the previous frame's merge until the next one.
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
merged.current = base;
|
|
101
|
+
}, [base]);
|
|
102
|
+
const allowed = useMemo(() => new Set(getGenerativeAnimatable('lights', base).map((p) => p.value)), [base]);
|
|
103
|
+
useFrame((state) => {
|
|
104
|
+
const hasAnimations = !!animations?.length;
|
|
105
|
+
const hasPointer = !!mouseMove?.enabled && !!mouseMove.properties?.length;
|
|
106
|
+
const hasLive = Object.keys(live.current).length > 0;
|
|
107
|
+
if (!hasAnimations && !hasPointer && !hasLive) {
|
|
108
|
+
// Nothing overriding: the committed config is already on the light
|
|
109
|
+
// (R3F props), and useLightMouseMove owns the frame from here.
|
|
110
|
+
merged.current = base;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Keyframes first, pointer on top: a property bound to both follows the cursor.
|
|
114
|
+
applyConfigKeyframeAnimations(animations, animFrom.current, base, animOverride.current, performance.now() - animStart.current, allowed);
|
|
115
|
+
if (hasPointer)
|
|
116
|
+
applyConfigMouseMove(mouseMove.properties, base, animOverride.current, state.pointer, allowed);
|
|
117
|
+
const next = { ...base, ...live.current, ...animOverride.current };
|
|
118
|
+
merged.current = next;
|
|
119
|
+
const light = lightRef.current;
|
|
120
|
+
if (light)
|
|
121
|
+
applyLightConfig(light, next);
|
|
122
|
+
});
|
|
123
|
+
return { merged, live };
|
|
124
|
+
}
|
|
125
|
+
export default useLightConfig;
|
|
@@ -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,
|
|
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"}
|