@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,243 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// LedArray — a point light drawn as the dies that make it
|
|
4
|
+
// =============================================================================
|
|
5
|
+
//
|
|
6
|
+
// Rendered by LightObject for `config.type === 'ledArray'`. Like spotBeam, this
|
|
7
|
+
// is a light rather than a lamp: the geometry is the emitters themselves (and
|
|
8
|
+
// can be switched off), because the object is meant to be dropped into whatever
|
|
9
|
+
// housing the scene already has.
|
|
10
|
+
//
|
|
11
|
+
// What makes it read as LED rather than a small bulb:
|
|
12
|
+
// - it comes on instantly — no thermal lag, the opposite of a filament;
|
|
13
|
+
// - `pwm` puts back the high-frequency chop a dimmed LED runs on, which a
|
|
14
|
+
// camera (and a 60 Hz render) sees as a slow strobe rather than a flat dim;
|
|
15
|
+
// - `diffuser` both fades the dies into one sheet and widens the light's own
|
|
16
|
+
// shadow radius, because those are the same fact: a bigger apparent source
|
|
17
|
+
// casts a softer shadow;
|
|
18
|
+
// - 'rgb' colour mode runs a hue across the array and can chase along it,
|
|
19
|
+
// which is what an addressable strip actually is. The light itself takes the
|
|
20
|
+
// average of the dies, the way the room would.
|
|
21
|
+
//
|
|
22
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
23
|
+
import { useFrame } from '@react-three/fiber';
|
|
24
|
+
import { BoxGeometry, Color, InstancedBufferAttribute, Matrix4, PlaneGeometry } from 'three';
|
|
25
|
+
import { SELF_OWNED_MATERIAL_KEY } from '@3driseai/3drise-core';
|
|
26
|
+
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
27
|
+
import { useLightShadow } from '../../hooks/useLightShadow.js';
|
|
28
|
+
import { useLightConfig } from '../../hooks/useLightConfig.js';
|
|
29
|
+
import { kelvinToColor } from '../lightBulb/kelvin.js';
|
|
30
|
+
import { createLedArrayMaterials, disposeLedArrayMaterials } from './ledMaterials.js';
|
|
31
|
+
import { placeDies } from './ledLayout.js';
|
|
32
|
+
const num = (v, fallback) => (typeof v === 'number' && Number.isFinite(v) ? v : fallback);
|
|
33
|
+
const clamp01 = (v) => Math.min(Math.max(v, 0), 1);
|
|
34
|
+
const hash = (n) => {
|
|
35
|
+
const s = Math.sin(n * 127.1) * 43758.5453;
|
|
36
|
+
return s - Math.floor(s);
|
|
37
|
+
};
|
|
38
|
+
const noise1 = (x) => {
|
|
39
|
+
const i = Math.floor(x), f = x - i, u = f * f * (3 - 2 * f);
|
|
40
|
+
return hash(i) * (1 - u) + hash(i + 1) * u;
|
|
41
|
+
};
|
|
42
|
+
const scratchMatrix = new Matrix4();
|
|
43
|
+
const LedArray = ({ id, config, animations, mouseMove, settings, onObjectReady, onObjectDispose }) => {
|
|
44
|
+
const groupRef = useRef(null);
|
|
45
|
+
const lightRef = useRef(null);
|
|
46
|
+
const diesRef = useRef(null);
|
|
47
|
+
const glowRef = useRef(null);
|
|
48
|
+
const diffuserRef = useRef(null);
|
|
49
|
+
useObjectReady(groupRef, settings, onObjectReady, onObjectDispose);
|
|
50
|
+
useLightShadow(lightRef, config.shadow);
|
|
51
|
+
const { merged } = useLightConfig(groupRef, lightRef, id, config, animations, mouseMove);
|
|
52
|
+
const layout = (config.layout ?? 'panel');
|
|
53
|
+
const spacing = Math.max(num(config.spacing, 0.06), 0.005);
|
|
54
|
+
const dieSize = Math.max(num(config.dieSize, 0.022), 0.002);
|
|
55
|
+
const emitter = (config.emitter ?? 'dies');
|
|
56
|
+
// --- where the dies sit; rebuilt only when the array's shape changes ---
|
|
57
|
+
const place = useMemo(() => placeDies(layout, config.count, config.rows, spacing), [layout, config.count, config.rows, spacing]);
|
|
58
|
+
const geometry = useMemo(() => ({
|
|
59
|
+
// a flat chip, so a die reads as a surface rather than a cube
|
|
60
|
+
die: new BoxGeometry(1, 0.35, 1),
|
|
61
|
+
glow: new PlaneGeometry(2, 2),
|
|
62
|
+
sheet: new PlaneGeometry(1, 1),
|
|
63
|
+
}), []);
|
|
64
|
+
useEffect(() => () => {
|
|
65
|
+
geometry.die.dispose();
|
|
66
|
+
geometry.glow.dispose();
|
|
67
|
+
geometry.sheet.dispose();
|
|
68
|
+
}, [geometry]);
|
|
69
|
+
const mats = useMemo(() => createLedArrayMaterials(), []);
|
|
70
|
+
useEffect(() => () => disposeLedArrayMaterials(mats), [mats]);
|
|
71
|
+
/** One colour per die, reused every frame instead of reallocated. */
|
|
72
|
+
const dieColors = useMemo(() => {
|
|
73
|
+
const list = [];
|
|
74
|
+
for (let i = 0; i < place.count; i++)
|
|
75
|
+
list.push(new Color());
|
|
76
|
+
return list;
|
|
77
|
+
}, [place]);
|
|
78
|
+
// Instance transforms + the per-die colour attribute the glow shader reads.
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
const dies = diesRef.current;
|
|
81
|
+
const glow = glowRef.current;
|
|
82
|
+
const { positions, count } = place;
|
|
83
|
+
const colors = new Float32Array(count * 3).fill(1);
|
|
84
|
+
for (let i = 0; i < count; i++) {
|
|
85
|
+
const x = positions[i * 3], z = positions[i * 3 + 2];
|
|
86
|
+
scratchMatrix.makeScale(dieSize, dieSize, dieSize);
|
|
87
|
+
scratchMatrix.setPosition(x, 0, z);
|
|
88
|
+
dies?.setMatrixAt(i, scratchMatrix);
|
|
89
|
+
glow?.setMatrixAt(i, scratchMatrix);
|
|
90
|
+
}
|
|
91
|
+
if (dies) {
|
|
92
|
+
dies.count = count;
|
|
93
|
+
dies.instanceMatrix.needsUpdate = true;
|
|
94
|
+
}
|
|
95
|
+
if (glow) {
|
|
96
|
+
glow.count = count;
|
|
97
|
+
glow.instanceMatrix.needsUpdate = true;
|
|
98
|
+
glow.geometry.setAttribute('aColor', new InstancedBufferAttribute(colors, 3));
|
|
99
|
+
}
|
|
100
|
+
mats.diffuser.uniforms.uPitch.value = Math.max(place.pitchX, 1);
|
|
101
|
+
}, [place, dieSize, mats]);
|
|
102
|
+
// The light owns its materials and its shadow flags.
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const group = groupRef.current;
|
|
105
|
+
if (!group)
|
|
106
|
+
return;
|
|
107
|
+
group.userData[SELF_OWNED_MATERIAL_KEY] = true;
|
|
108
|
+
return () => { delete group.userData[SELF_OWNED_MATERIAL_KEY]; };
|
|
109
|
+
}, []);
|
|
110
|
+
// The dies sit millimetres from the light; the point shadow camera's default
|
|
111
|
+
// near of 0.5 would clip the whole array out of its own shadow map.
|
|
112
|
+
const shadowNear = config.shadow?.cameraNear;
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
const light = lightRef.current;
|
|
115
|
+
if (!light || shadowNear !== undefined)
|
|
116
|
+
return;
|
|
117
|
+
light.shadow.camera.near = 0.02;
|
|
118
|
+
light.shadow.camera.updateProjectionMatrix();
|
|
119
|
+
}, [shadowNear]);
|
|
120
|
+
const colorMode = (config.colorMode ?? 'temperature');
|
|
121
|
+
const lightColor = useMemo(() => (colorMode === 'temperature'
|
|
122
|
+
? kelvinToColor(config.temperature ?? 5000)
|
|
123
|
+
: new Color(config.color || '#ffffff')), [colorMode, config.color, config.temperature]);
|
|
124
|
+
const power = useRef({
|
|
125
|
+
level: (config.on ?? true) ? 1 : 0,
|
|
126
|
+
base: config.intensity ?? 1,
|
|
127
|
+
written: Number.NaN,
|
|
128
|
+
seed: Math.random() * 1000,
|
|
129
|
+
});
|
|
130
|
+
const scratch = useMemo(() => ({ mean: new Color(), glow: new Color(), tmp: new Color() }), []);
|
|
131
|
+
useFrame(({ clock }, delta) => {
|
|
132
|
+
const light = lightRef.current;
|
|
133
|
+
if (!light)
|
|
134
|
+
return;
|
|
135
|
+
const cfg = merged.current;
|
|
136
|
+
const p = power.current;
|
|
137
|
+
// Adopt any intensity we did not write ourselves as the new base, so the
|
|
138
|
+
// multiplier below never compounds and animations stay in charge.
|
|
139
|
+
if (light.intensity !== p.written)
|
|
140
|
+
p.base = light.intensity;
|
|
141
|
+
const on = cfg.on ?? true;
|
|
142
|
+
// An LED has no thermal lag: the default is a hard switch, and `warmup`
|
|
143
|
+
// is there only for someone who wants a fade.
|
|
144
|
+
const warmup = Math.max(num(cfg.warmup, 0), 0);
|
|
145
|
+
const target = on ? 1 : 0;
|
|
146
|
+
p.level = warmup <= 0
|
|
147
|
+
? target
|
|
148
|
+
: p.level + (target - p.level) * (1 - Math.exp(-Math.min(delta, 0.1) * 3 / warmup));
|
|
149
|
+
if (Math.abs(target - p.level) < 1e-4)
|
|
150
|
+
p.level = target;
|
|
151
|
+
const t = clock.elapsedTime;
|
|
152
|
+
// PWM: a real dimmed LED is fully on for part of each cycle and fully off
|
|
153
|
+
// for the rest. At ~118 Hz against a 60 Hz frame that beats slowly, which
|
|
154
|
+
// is exactly the strobe a camera picks up.
|
|
155
|
+
const pwm = clamp01(num(cfg.pwm, 0));
|
|
156
|
+
const chop = pwm > 0 ? 1 - pwm * 0.5 * (0.5 + 0.5 * Math.sin(t * Math.PI * 2 * 118)) : 1;
|
|
157
|
+
const flicker = clamp01(num(cfg.flicker, 0));
|
|
158
|
+
let flick = 1;
|
|
159
|
+
if (flicker > 0) {
|
|
160
|
+
const ft = t + p.seed;
|
|
161
|
+
flick = 1 - flicker * (0.1 * noise1(ft * 13) + 0.6 * Math.pow(noise1(ft * 3.1), 4));
|
|
162
|
+
}
|
|
163
|
+
const output = p.level * chop * flick;
|
|
164
|
+
light.intensity = p.base * output;
|
|
165
|
+
p.written = light.intensity;
|
|
166
|
+
const baseIntensity = Math.max(num(config.intensity, 1), 1e-4);
|
|
167
|
+
const glowK = Math.min(output * (p.base / baseIntensity), 3);
|
|
168
|
+
// --- colour: one per die, and the light takes their mean ---
|
|
169
|
+
const rgbSpread = num(cfg.rgbSpread, 1);
|
|
170
|
+
const rgbSpeed = num(cfg.rgbSpeed, 0.2);
|
|
171
|
+
const mean = scratch.mean.setRGB(0, 0, 0);
|
|
172
|
+
const n = dieColors.length || 1;
|
|
173
|
+
for (let i = 0; i < dieColors.length; i++) {
|
|
174
|
+
const c = dieColors[i];
|
|
175
|
+
if (colorMode === 'rgb')
|
|
176
|
+
c.setHSL(((i / n) * rgbSpread + t * rgbSpeed) % 1, 1, 0.5);
|
|
177
|
+
else
|
|
178
|
+
c.copy(light.color);
|
|
179
|
+
mean.r += c.r / n;
|
|
180
|
+
mean.g += c.g / n;
|
|
181
|
+
mean.b += c.b / n;
|
|
182
|
+
}
|
|
183
|
+
// In RGB mode the light is the mix of the dies, not one of them. This runs
|
|
184
|
+
// after useLightConfig's own write, which is the point: the array owns its
|
|
185
|
+
// colour the way the bulb's filament owns its heat.
|
|
186
|
+
if (colorMode === 'rgb' && dieColors.length)
|
|
187
|
+
light.color.copy(mean);
|
|
188
|
+
const glowColor = scratch.glow.copy(colorMode === 'rgb' ? mean : light.color);
|
|
189
|
+
// --- diffuser: fades the dies out and softens the shadow with them ---
|
|
190
|
+
const diffuser = clamp01(num(cfg.diffuser, 0));
|
|
191
|
+
const dieVisible = emitter === 'dies' && glowK > 0.005;
|
|
192
|
+
const dieK = glowK * (1 - diffuser * 0.85);
|
|
193
|
+
const dies = diesRef.current;
|
|
194
|
+
const glow = glowRef.current;
|
|
195
|
+
if (dies) {
|
|
196
|
+
dies.visible = dieVisible;
|
|
197
|
+
if (dieVisible) {
|
|
198
|
+
for (let i = 0; i < dieColors.length; i++)
|
|
199
|
+
dies.setColorAt(i, scratch.tmp.copy(dieColors[i]).multiplyScalar(Math.min(0.06 + dieK * 1.8, 4)));
|
|
200
|
+
if (dies.instanceColor)
|
|
201
|
+
dies.instanceColor.needsUpdate = true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (glow) {
|
|
205
|
+
glow.visible = dieVisible;
|
|
206
|
+
const attr = glow.geometry.getAttribute('aColor');
|
|
207
|
+
if (dieVisible && attr) {
|
|
208
|
+
const arr = attr.array;
|
|
209
|
+
for (let i = 0; i < dieColors.length; i++) {
|
|
210
|
+
const c = dieColors[i];
|
|
211
|
+
arr[i * 3] = c.r;
|
|
212
|
+
arr[i * 3 + 1] = c.g;
|
|
213
|
+
arr[i * 3 + 2] = c.b;
|
|
214
|
+
}
|
|
215
|
+
attr.needsUpdate = true;
|
|
216
|
+
}
|
|
217
|
+
// Every die adds to the same pixels, so the per-die glow has to stay
|
|
218
|
+
// small and faint or a panel fuses into one white slab.
|
|
219
|
+
mats.dieGlow.uniforms.uStrength.value = dieK * 0.34;
|
|
220
|
+
mats.dieGlow.uniforms.uSize.value = Math.min(dieSize * 3.6, spacing * 0.85);
|
|
221
|
+
}
|
|
222
|
+
const sheet = diffuserRef.current;
|
|
223
|
+
if (sheet) {
|
|
224
|
+
sheet.visible = diffuser > 0.01 && glowK > 0.005;
|
|
225
|
+
mats.diffuser.uniforms.uColor.value.copy(glowColor);
|
|
226
|
+
mats.diffuser.uniforms.uStrength.value = diffuser * glowK * 0.85;
|
|
227
|
+
mats.diffuser.uniforms.uBlend.value = diffuser;
|
|
228
|
+
}
|
|
229
|
+
// A diffuser is a physically bigger source, so it blurs the shadow. Only
|
|
230
|
+
// when the config has not set a radius of its own.
|
|
231
|
+
if (config.shadow?.radius === undefined)
|
|
232
|
+
light.shadow.radius = 1 + diffuser * 11;
|
|
233
|
+
const halo = Math.max(num(cfg.halo, 1), 0);
|
|
234
|
+
mats.halo.uniforms.uColor.value.copy(glowColor);
|
|
235
|
+
mats.halo.uniforms.uStrength.value = halo * glowK * (0.12 + diffuser * 0.2);
|
|
236
|
+
// Sized to the array, not the world: a strip and a panel glow differently.
|
|
237
|
+
mats.halo.uniforms.uRadius.value = Math.max(place.width, place.depth) * 1.5;
|
|
238
|
+
});
|
|
239
|
+
// The diffuser sits just in front of the dies, facing down with them.
|
|
240
|
+
const sheetSize = [place.width * 1.12, place.depth * 1.12];
|
|
241
|
+
return (_jsxs("group", { ref: groupRef, children: [_jsx("pointLight", { ref: lightRef, color: lightColor, intensity: config.intensity ?? 1, decay: config.decay ?? 1, distance: config.distance ?? 0 }), _jsx("instancedMesh", { ref: diesRef, args: [geometry.die, mats.die, place.count], renderOrder: 2, frustumCulled: false }), _jsx("instancedMesh", { ref: glowRef, args: [geometry.glow, mats.dieGlow, place.count], renderOrder: 3, frustumCulled: false }), _jsx("mesh", { ref: diffuserRef, material: mats.diffuser, position: [0, -Math.max(dieSize * 1.4, 0.012), 0], rotation: [-Math.PI / 2, 0, 0], scale: [sheetSize[0], sheetSize[1], 1], geometry: geometry.sheet, renderOrder: 4, frustumCulled: false }), _jsx("mesh", { material: mats.halo, geometry: geometry.glow, renderOrder: 5, frustumCulled: false })] }));
|
|
242
|
+
};
|
|
243
|
+
export default LedArray;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as LedArray } from './LedArray';
|
|
2
|
+
export type { LedArrayProps } from './LedArray';
|
|
3
|
+
export { createLedArrayMaterials, disposeLedArrayMaterials } from './ledMaterials';
|
|
4
|
+
export type { LedArrayMaterials } from './ledMaterials';
|
|
5
|
+
export { LED_LAYOUTS, MAX_DIES, placeDies } from './ledLayout';
|
|
6
|
+
export type { LedLayout, LedPlacement } from './ledLayout';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/ledArray/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,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AACnF,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC/D,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// An array of LED dies with a real point light (config.type 'ledArray').
|
|
2
|
+
export { default as LedArray } from './LedArray.js';
|
|
3
|
+
export { createLedArrayMaterials, disposeLedArrayMaterials } from './ledMaterials.js';
|
|
4
|
+
export { LED_LAYOUTS, MAX_DIES, placeDies } from './ledLayout.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type LedLayout = 'single' | 'strip' | 'panel' | 'ring';
|
|
2
|
+
export declare const LED_LAYOUTS: LedLayout[];
|
|
3
|
+
/** Instances are cheap but not free, and a 64x12 panel is already 768 dies. */
|
|
4
|
+
export declare const MAX_DIES = 512;
|
|
5
|
+
export interface LedPlacement {
|
|
6
|
+
/** x, y, z per die, flat. */
|
|
7
|
+
positions: Float32Array;
|
|
8
|
+
/** How many dies were actually placed (after the cap). */
|
|
9
|
+
count: number;
|
|
10
|
+
/** Footprint in object space, for the diffuser and the halo. */
|
|
11
|
+
width: number;
|
|
12
|
+
depth: number;
|
|
13
|
+
/** Hot spots across the diffuser's width, for its ripple. */
|
|
14
|
+
pitchX: number;
|
|
15
|
+
pitchZ: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Lay the dies out for a layout. `count` is the number along the main axis;
|
|
19
|
+
* `rows` only means anything for a panel.
|
|
20
|
+
*/
|
|
21
|
+
export declare function placeDies(layout: LedLayout, countRaw: unknown, rowsRaw: unknown, spacing: number): LedPlacement;
|
|
22
|
+
//# sourceMappingURL=ledLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledLayout.d.ts","sourceRoot":"","sources":["../../../src/components/ledArray/ledLayout.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAE9D,eAAO,MAAM,WAAW,EAAE,SAAS,EAAyC,CAAC;AAE7E,+EAA+E;AAC/E,eAAO,MAAM,QAAQ,MAAM,CAAC;AAE5B,MAAM,WAAW,YAAY;IACzB,6BAA6B;IAC7B,SAAS,EAAE,YAAY,CAAC;IACxB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAOD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CA8C/G"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export const LED_LAYOUTS = ['single', 'strip', 'panel', 'ring'];
|
|
2
|
+
/** Instances are cheap but not free, and a 64x12 panel is already 768 dies. */
|
|
3
|
+
export const MAX_DIES = 512;
|
|
4
|
+
const clampInt = (v, lo, hi, fallback) => {
|
|
5
|
+
const n = typeof v === 'number' && Number.isFinite(v) ? Math.round(v) : fallback;
|
|
6
|
+
return Math.min(Math.max(n, lo), hi);
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Lay the dies out for a layout. `count` is the number along the main axis;
|
|
10
|
+
* `rows` only means anything for a panel.
|
|
11
|
+
*/
|
|
12
|
+
export function placeDies(layout, countRaw, rowsRaw, spacing) {
|
|
13
|
+
const pitch = Math.max(spacing, 0.005);
|
|
14
|
+
let count = clampInt(countRaw, 1, 64, 8);
|
|
15
|
+
let rows = clampInt(rowsRaw, 1, 12, 4);
|
|
16
|
+
if (layout === 'single') {
|
|
17
|
+
count = 1;
|
|
18
|
+
rows = 1;
|
|
19
|
+
}
|
|
20
|
+
else if (layout !== 'panel')
|
|
21
|
+
rows = 1;
|
|
22
|
+
// Keep the instance count sane whatever the knobs say.
|
|
23
|
+
while (count * rows > MAX_DIES) {
|
|
24
|
+
if (rows > 1)
|
|
25
|
+
rows -= 1;
|
|
26
|
+
else
|
|
27
|
+
count -= 1;
|
|
28
|
+
}
|
|
29
|
+
const total = count * rows;
|
|
30
|
+
const positions = new Float32Array(total * 3);
|
|
31
|
+
if (layout === 'ring') {
|
|
32
|
+
// A ring of `count`, sized so neighbours sit one pitch apart.
|
|
33
|
+
const radius = count > 1 ? (pitch * count) / (2 * Math.PI) : 0;
|
|
34
|
+
for (let i = 0; i < count; i++) {
|
|
35
|
+
const a = (i / count) * Math.PI * 2;
|
|
36
|
+
positions[i * 3] = Math.cos(a) * radius;
|
|
37
|
+
positions[i * 3 + 2] = Math.sin(a) * radius;
|
|
38
|
+
}
|
|
39
|
+
const span = radius * 2 + pitch;
|
|
40
|
+
return { positions, count, width: span, depth: span, pitchX: Math.max(count / 4, 1), pitchZ: Math.max(count / 4, 1) };
|
|
41
|
+
}
|
|
42
|
+
const spanX = (count - 1) * pitch;
|
|
43
|
+
const spanZ = (rows - 1) * pitch;
|
|
44
|
+
let i = 0;
|
|
45
|
+
for (let r = 0; r < rows; r++) {
|
|
46
|
+
for (let c = 0; c < count; c++, i++) {
|
|
47
|
+
positions[i * 3] = c * pitch - spanX / 2;
|
|
48
|
+
positions[i * 3 + 2] = r * pitch - spanZ / 2;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
positions,
|
|
53
|
+
count: total,
|
|
54
|
+
width: spanX + pitch,
|
|
55
|
+
depth: spanZ + pitch,
|
|
56
|
+
pitchX: Math.max(count / 2, 1),
|
|
57
|
+
pitchZ: Math.max(rows / 2, 1),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { MeshBasicMaterial, ShaderMaterial } from 'three';
|
|
2
|
+
export interface LedArrayMaterials {
|
|
3
|
+
/** The dies themselves: emissive squares, per-instance colour. */
|
|
4
|
+
die: MeshBasicMaterial;
|
|
5
|
+
/** One additive billboard per die. */
|
|
6
|
+
dieGlow: ShaderMaterial;
|
|
7
|
+
/** The sheet in front of the dies, lit from behind. */
|
|
8
|
+
diffuser: ShaderMaterial;
|
|
9
|
+
/** One big camera-facing halo for the array as a whole. */
|
|
10
|
+
halo: ShaderMaterial;
|
|
11
|
+
}
|
|
12
|
+
export declare function createLedArrayMaterials(): LedArrayMaterials;
|
|
13
|
+
export declare function disposeLedArrayMaterials(m: LedArrayMaterials): void;
|
|
14
|
+
//# sourceMappingURL=ledMaterials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledMaterials.d.ts","sourceRoot":"","sources":["../../../src/components/ledArray/ledMaterials.ts"],"names":[],"mappings":"AAeA,OAAO,EAAuC,iBAAiB,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE/F,MAAM,WAAW,iBAAiB;IAC9B,kEAAkE;IAClE,GAAG,EAAE,iBAAiB,CAAC;IACvB,sCAAsC;IACtC,OAAO,EAAE,cAAc,CAAC;IACxB,uDAAuD;IACvD,QAAQ,EAAE,cAAc,CAAC;IACzB,2DAA2D;IAC3D,IAAI,EAAE,cAAc,CAAC;CACxB;AA+HD,wBAAgB,uBAAuB,IAAI,iBAAiB,CAO3D;AAED,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAKnE"}
|
|
@@ -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
|
+
}
|
|
@@ -1 +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;
|
|
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"}
|
|
@@ -7,8 +7,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
// is the filament centre, which is where the point light sits; the bulb stands
|
|
8
8
|
// upright (base down, -Y) — rotate the object 180° for a pendant.
|
|
9
9
|
//
|
|
10
|
-
// Intensity ownership:
|
|
11
|
-
// `intensity` prop
|
|
10
|
+
// Intensity ownership: useLightConfig (keyframes + pointer bindings) and R3F's
|
|
11
|
+
// `intensity` prop write `light.intensity` as usual. This component then
|
|
12
12
|
// multiplies in the power level (on/off, warm-up, flicker) each frame. Any
|
|
13
13
|
// intensity it did not write itself is adopted as the new base, so the
|
|
14
14
|
// multiplier never compounds and external writers stay in charge of the base.
|
|
@@ -26,9 +26,7 @@ import { Color } from 'three';
|
|
|
26
26
|
import { SELF_OWNED_MATERIAL_KEY } from '@3driseai/3drise-core';
|
|
27
27
|
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
28
28
|
import { useLightShadow } from '../../hooks/useLightShadow.js';
|
|
29
|
-
import {
|
|
30
|
-
import { useLightMouseMove } from '../../hooks/useLightMouseMove.js';
|
|
31
|
-
import { useLightLive } from '../../hooks/useLightLive.js';
|
|
29
|
+
import { useLightConfig } from '../../hooks/useLightConfig.js';
|
|
32
30
|
import { buildBulbShape, disposeBulbShape } from './bulbShapes.js';
|
|
33
31
|
import { applyGlass, createBulbMaterials, disposeBulbMaterials, GLASS_RESPONSE } from './bulbMaterials.js';
|
|
34
32
|
import { kelvinToColor } from './kelvin.js';
|
|
@@ -53,10 +51,9 @@ const LightBulb = ({ id, config, animations, mouseMove, settings, onObjectReady,
|
|
|
53
51
|
const lightRef = useRef(null);
|
|
54
52
|
useObjectReady(groupRef, settings, onObjectReady, onObjectDispose);
|
|
55
53
|
useLightShadow(lightRef, config.shadow);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const live = useLightLive(groupRef, lightRef, id, config);
|
|
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);
|
|
60
57
|
const glass = config.glass ?? 'clear';
|
|
61
58
|
const glassTint = config.glassTint ?? '#ffb070';
|
|
62
59
|
const on = config.on ?? true;
|
|
@@ -99,10 +96,10 @@ const LightBulb = ({ id, config, animations, mouseMove, settings, onObjectReady,
|
|
|
99
96
|
if (!light)
|
|
100
97
|
return;
|
|
101
98
|
const p = power.current;
|
|
102
|
-
const
|
|
103
|
-
const warmup = Math.max(num(
|
|
104
|
-
const flicker = Math.min(Math.max(num(
|
|
105
|
-
const haloAmount = Math.min(Math.max(num(
|
|
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);
|
|
106
103
|
if (light.intensity !== p.written)
|
|
107
104
|
p.base = light.intensity;
|
|
108
105
|
const target = on ? 1 : 0;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ObjectAnimations, BioluminescentWakeParticlesConfig } from '@3driseai/3drise-core';
|
|
2
|
+
import type { ObjectAnimations, BioluminescentWakeParticlesConfig, MouseMoveDomain } from '@3driseai/3drise-core';
|
|
3
3
|
import type { ObjectReadyProps } from '../../types/objectReady';
|
|
4
4
|
export interface BioluminescentWakeParticlesProps extends ObjectReadyProps {
|
|
5
|
+
/** Pointer bindings for this object's config knobs (mouseMove.particles). */
|
|
6
|
+
mouseMove?: MouseMoveDomain;
|
|
5
7
|
/** Scene object id: the controller finds the live-edit handle by it. */
|
|
6
8
|
id?: string;
|
|
7
9
|
/** World size of the object (units); the object's transform scales it further. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BioluminescentWakeParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/BioluminescentWakeParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"BioluminescentWakeParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/BioluminescentWakeParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,iCAAiC,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAMlH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,gCAAiC,SAAQ,gBAAgB;IACtE,6EAA6E;IAC7E,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iCAAiC,CAAC;IAC1C,sFAAsF;IACtF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AA09BD,QAAA,MAAM,2BAA2B,EAAE,KAAK,CAAC,EAAE,CAAC,gCAAgC,CAqD3E,CAAC;AAEF,eAAe,2BAA2B,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { useFrame } from '@react-three/fiber';
|
|
5
5
|
import { GENERATIVE_PARTICLES_ANIMATABLE } from '@3driseai/3drise-core';
|
|
6
|
-
import { applyConfigKeyframeAnimations } from '../../utils/configAnimations.js';
|
|
6
|
+
import { applyConfigKeyframeAnimations, applyConfigMouseMove } from '../../utils/configAnimations.js';
|
|
7
7
|
import { createPointerUniforms, stepPointerField, usePointerField } from '../../utils/pointerField.js';
|
|
8
8
|
import { createTrailUniforms, applyTrailConfig, createCpuTrail } from '../../utils/particleTrails.js';
|
|
9
9
|
import { useObjectReady } from '../../hooks/useObjectReady.js';
|
|
@@ -1103,7 +1103,7 @@ const DEFAULTS = {
|
|
|
1103
1103
|
const STRUCTURAL = ['particleCount', 'seed', 'quality'];
|
|
1104
1104
|
/** Knobs an animation (or mouse move) may drive per frame — generated from applyConfig. */
|
|
1105
1105
|
const ANIMATABLE = new Set(GENERATIVE_PARTICLES_ANIMATABLE.bioluminescentWake.map((p) => p.value));
|
|
1106
|
-
const BioluminescentWakeParticles = ({ id, size, config, animations, onObjectReady, onObjectDispose }) => {
|
|
1106
|
+
const BioluminescentWakeParticles = ({ id, size, config, animations, mouseMove, onObjectReady, onObjectDispose }) => {
|
|
1107
1107
|
const rootRef = useRef(null);
|
|
1108
1108
|
useObjectReady(rootRef, config, onObjectReady, onObjectDispose);
|
|
1109
1109
|
const s = size > 0 ? size : 1;
|
|
@@ -1135,8 +1135,12 @@ const BioluminescentWakeParticles = ({ id, size, config, animations, onObjectRea
|
|
|
1135
1135
|
const pointer = usePointerField();
|
|
1136
1136
|
useFrame((state, delta) => {
|
|
1137
1137
|
instance.update(Math.min(delta, 0.1), state.camera, pointer);
|
|
1138
|
-
|
|
1138
|
+
// Keyframes first, pointer on top: a knob bound to both follows the cursor.
|
|
1139
|
+
const pointerBound = !!mouseMove?.enabled && !!mouseMove.properties?.length;
|
|
1140
|
+
if (keyframes?.length || pointerBound) {
|
|
1139
1141
|
applyConfigKeyframeAnimations(keyframes, animFrom.current, cfg, animOverride.current, performance.now() - animStart.current, ANIMATABLE);
|
|
1142
|
+
if (pointerBound)
|
|
1143
|
+
applyConfigMouseMove(mouseMove.properties, cfg, animOverride.current, state.pointer, ANIMATABLE);
|
|
1140
1144
|
push(live.current);
|
|
1141
1145
|
}
|
|
1142
1146
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ObjectAnimations, BrownianDustParticlesConfig } from '@3driseai/3drise-core';
|
|
2
|
+
import type { ObjectAnimations, BrownianDustParticlesConfig, MouseMoveDomain } from '@3driseai/3drise-core';
|
|
3
3
|
import type { ObjectReadyProps } from '../../types/objectReady';
|
|
4
4
|
export interface BrownianDustParticlesProps extends ObjectReadyProps {
|
|
5
|
+
/** Pointer bindings for this object's config knobs (mouseMove.particles). */
|
|
6
|
+
mouseMove?: MouseMoveDomain;
|
|
5
7
|
/** Scene object id: the controller finds the live-edit handle by it. */
|
|
6
8
|
id?: string;
|
|
7
9
|
/** World size of the object (units); the object's transform scales it further. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BrownianDustParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/BrownianDustParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"BrownianDustParticles.d.ts","sourceRoot":"","sources":["../../../src/components/particles/BrownianDustParticles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAIxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAM5G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,0BAA2B,SAAQ,gBAAgB;IAChE,6EAA6E;IAC7E,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,wEAAwE;IACxE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,2BAA2B,CAAC;IACpC,sFAAsF;IACtF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AA4qBD,QAAA,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAqD/D,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
|