@3driseai/3drise-viewer 0.1.22 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/components/CreatedObject.d.ts.map +1 -1
  2. package/dist/components/CreatedObject.js +6 -1
  3. package/dist/components/LightObject.d.ts.map +1 -1
  4. package/dist/components/LightObject.js +24 -13
  5. package/dist/components/ModelPrimitive.d.ts.map +1 -1
  6. package/dist/components/ModelPrimitive.js +4 -6
  7. package/dist/components/ObjectNode.js +1 -1
  8. package/dist/components/ParticlesGenerator.d.ts +3 -1
  9. package/dist/components/ParticlesGenerator.d.ts.map +1 -1
  10. package/dist/components/ParticlesGenerator.js +2 -2
  11. package/dist/components/ProjectLoader.js +1 -1
  12. package/dist/components/SceneBuilder.d.ts.map +1 -1
  13. package/dist/components/SceneBuilder.js +5 -1
  14. package/dist/components/candle/Candle.d.ts +14 -0
  15. package/dist/components/candle/Candle.d.ts.map +1 -0
  16. package/dist/components/candle/Candle.js +249 -0
  17. package/dist/components/candle/candleGeometry.d.ts +23 -0
  18. package/dist/components/candle/candleGeometry.d.ts.map +1 -0
  19. package/dist/components/candle/candleGeometry.js +73 -0
  20. package/dist/components/candle/candleMaterials.d.ts +24 -0
  21. package/dist/components/candle/candleMaterials.d.ts.map +1 -0
  22. package/dist/components/candle/candleMaterials.js +433 -0
  23. package/dist/components/candle/flameMotion.d.ts +64 -0
  24. package/dist/components/candle/flameMotion.d.ts.map +1 -0
  25. package/dist/components/candle/flameMotion.js +138 -0
  26. package/dist/components/candle/index.d.ts +9 -0
  27. package/dist/components/candle/index.d.ts.map +1 -0
  28. package/dist/components/candle/index.js +6 -0
  29. package/dist/components/fluorescentTube/FluorescentTube.d.ts +14 -0
  30. package/dist/components/fluorescentTube/FluorescentTube.d.ts.map +1 -0
  31. package/dist/components/fluorescentTube/FluorescentTube.js +262 -0
  32. package/dist/components/fluorescentTube/index.d.ts +5 -0
  33. package/dist/components/fluorescentTube/index.d.ts.map +1 -0
  34. package/dist/components/fluorescentTube/index.js +3 -0
  35. package/dist/components/fluorescentTube/tubeMaterials.d.ts +34 -0
  36. package/dist/components/fluorescentTube/tubeMaterials.d.ts.map +1 -0
  37. package/dist/components/fluorescentTube/tubeMaterials.js +185 -0
  38. package/dist/components/index.d.ts +4 -0
  39. package/dist/components/index.d.ts.map +1 -1
  40. package/dist/components/index.js +4 -0
  41. package/dist/components/ledArray/LedArray.d.ts +14 -0
  42. package/dist/components/ledArray/LedArray.d.ts.map +1 -0
  43. package/dist/components/ledArray/LedArray.js +243 -0
  44. package/dist/components/ledArray/index.d.ts +7 -0
  45. package/dist/components/ledArray/index.d.ts.map +1 -0
  46. package/dist/components/ledArray/index.js +4 -0
  47. package/dist/components/ledArray/ledLayout.d.ts +22 -0
  48. package/dist/components/ledArray/ledLayout.d.ts.map +1 -0
  49. package/dist/components/ledArray/ledLayout.js +59 -0
  50. package/dist/components/ledArray/ledMaterials.d.ts +14 -0
  51. package/dist/components/ledArray/ledMaterials.d.ts.map +1 -0
  52. package/dist/components/ledArray/ledMaterials.js +151 -0
  53. package/dist/components/lightBulb/LightBulb.d.ts.map +1 -1
  54. package/dist/components/lightBulb/LightBulb.js +10 -13
  55. package/dist/components/particles/BioluminescentWakeParticles.d.ts +3 -1
  56. package/dist/components/particles/BioluminescentWakeParticles.d.ts.map +1 -1
  57. package/dist/components/particles/BioluminescentWakeParticles.js +7 -3
  58. package/dist/components/particles/BrownianDustParticles.d.ts +3 -1
  59. package/dist/components/particles/BrownianDustParticles.d.ts.map +1 -1
  60. package/dist/components/particles/BrownianDustParticles.js +7 -3
  61. package/dist/components/particles/ChladniSandParticles.d.ts +3 -1
  62. package/dist/components/particles/ChladniSandParticles.d.ts.map +1 -1
  63. package/dist/components/particles/ChladniSandParticles.js +7 -3
  64. package/dist/components/particles/ElectronOrbitalsParticles.d.ts +3 -1
  65. package/dist/components/particles/ElectronOrbitalsParticles.d.ts.map +1 -1
  66. package/dist/components/particles/ElectronOrbitalsParticles.js +7 -3
  67. package/dist/components/particles/FaradayPowderParticles.d.ts +3 -1
  68. package/dist/components/particles/FaradayPowderParticles.d.ts.map +1 -1
  69. package/dist/components/particles/FaradayPowderParticles.js +7 -3
  70. package/dist/components/particles/GrinderSparksParticles.d.ts +3 -1
  71. package/dist/components/particles/GrinderSparksParticles.d.ts.map +1 -1
  72. package/dist/components/particles/GrinderSparksParticles.js +7 -3
  73. package/dist/components/particles/NeonRainParticles.d.ts +3 -1
  74. package/dist/components/particles/NeonRainParticles.d.ts.map +1 -1
  75. package/dist/components/particles/NeonRainParticles.js +7 -3
  76. package/dist/components/particles/TidalStreamParticles.d.ts +3 -1
  77. package/dist/components/particles/TidalStreamParticles.d.ts.map +1 -1
  78. package/dist/components/particles/TidalStreamParticles.js +7 -3
  79. package/dist/components/spotBeam/SpotBeam.d.ts +14 -0
  80. package/dist/components/spotBeam/SpotBeam.d.ts.map +1 -0
  81. package/dist/components/spotBeam/SpotBeam.js +244 -0
  82. package/dist/components/spotBeam/beamPatterns.d.ts +11 -0
  83. package/dist/components/spotBeam/beamPatterns.d.ts.map +1 -0
  84. package/dist/components/spotBeam/beamPatterns.js +106 -0
  85. package/dist/components/spotBeam/index.d.ts +7 -0
  86. package/dist/components/spotBeam/index.d.ts.map +1 -0
  87. package/dist/components/spotBeam/index.js +4 -0
  88. package/dist/components/spotBeam/spotBeamMaterials.d.ts +16 -0
  89. package/dist/components/spotBeam/spotBeamMaterials.d.ts.map +1 -0
  90. package/dist/components/spotBeam/spotBeamMaterials.js +307 -0
  91. package/dist/hooks/index.d.ts +2 -0
  92. package/dist/hooks/index.d.ts.map +1 -1
  93. package/dist/hooks/index.js +1 -0
  94. package/dist/hooks/useLightConfig.d.ts +25 -0
  95. package/dist/hooks/useLightConfig.d.ts.map +1 -0
  96. package/dist/hooks/useLightConfig.js +125 -0
  97. package/dist/hooks/useLightLive.d.ts.map +1 -1
  98. package/dist/hooks/useLightLive.js +12 -2
  99. package/dist/hooks/useShadowApplication.d.ts +4 -4
  100. package/dist/hooks/useShadowApplication.d.ts.map +1 -1
  101. package/dist/hooks/useShadowApplication.js +25 -10
  102. package/package.json +2 -2
@@ -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
+ }
@@ -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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"}
@@ -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,EAK7C,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,CAuB1H;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"}