From 8f65daa853056607328f5435fdc7415e3572724b Mon Sep 17 00:00:00 2001 From: wass08 Date: Thu, 5 Mar 2026 14:57:34 +0100 Subject: [PATCH] handling lights --- .../ui/item-catalog/catalog-items.tsx | 18 +++++ .../renderers/item/item-renderer.tsx | 76 +++++++++++++++++-- .../interactive/interactive-system.tsx | 2 + 3 files changed, 88 insertions(+), 8 deletions(-) diff --git a/apps/editor/components/ui/item-catalog/catalog-items.tsx b/apps/editor/components/ui/item-catalog/catalog-items.tsx index 094a29b2..8a72a8d5 100644 --- a/apps/editor/components/ui/item-catalog/catalog-items.tsx +++ b/apps/editor/components/ui/item-catalog/catalog-items.tsx @@ -1229,6 +1229,24 @@ export const CATALOG_ITEMS: AssetInput[] = [ rotation: [0, 0, 0], dimensions: [1, 1, 1], attachTo: "ceiling", + interactive: { + controls: [ + { + kind: 'toggle', + }, + { + kind: 'slider', label: 'Intensity', min: 0, max: 100, unit: '%', displayMode: 'dial' + } + ], + effects: [ + { + kind: 'light', + intensityRange: [0, 2], + color: '#ffffff', + offset: [0, -0.5, 0], + } + ] + } }, { diff --git a/packages/viewer/src/components/renderers/item/item-renderer.tsx b/packages/viewer/src/components/renderers/item/item-renderer.tsx index a825d7aa..e05acdfd 100644 --- a/packages/viewer/src/components/renderers/item/item-renderer.tsx +++ b/packages/viewer/src/components/renderers/item/item-renderer.tsx @@ -1,7 +1,10 @@ import { type AnimationEffect, type AnyNodeId, + type Interactive, type ItemNode, + type LightEffect, + type SliderControl, useInteractive, useRegistry, useScene, @@ -10,7 +13,9 @@ import { useAnimations } from '@react-three/drei' import { Clone } from '@react-three/drei/core/Clone' import { useGLTF } from '@react-three/drei/core/Gltf' import { Suspense, useEffect, useMemo, useRef } from 'react' -import type { Group, Material, Mesh } from 'three' +import { useFrame } from '@react-three/fiber' +import type { Group, Material, Mesh, PointLight } from 'three' +import { MathUtils } from 'three' import { positionLocal, smoothstep, time } from 'three/tsl' import { DoubleSide, MeshStandardNodeMaterial } from 'three/webgpu' import { useShallow } from 'zustand/react/shallow' @@ -163,14 +168,69 @@ const ModelRenderer = ({ node }: { node: ItemNode }) => { }) }, [scene]) + const interactive = interactiveRef.current + const lightEffects = interactive?.effects.filter((e): e is LightEffect => e.kind === 'light') ?? [] + return ( - + + {lightEffects.map((effect, i) => ( + + ))} + + ) +} + +const ItemLight = ({ + nodeId, + effect, + interactive, +}: { + nodeId: AnyNodeId + effect: LightEffect + interactive: Interactive +}) => { + const lightRef = useRef(null!) + // Precompute stable indices — interactive is frozen at mount + const toggleIndex = interactive.controls.findIndex((c) => c.kind === 'toggle') + const sliderIndex = interactive.controls.findIndex((c) => c.kind === 'slider') + const sliderControl = sliderIndex >= 0 ? (interactive.controls[sliderIndex] as SliderControl) : null + + useFrame((_, delta) => { + if (!lightRef.current) return + const values = useInteractive.getState().items[nodeId]?.controlValues + + const isOn = toggleIndex >= 0 ? Boolean(values?.[toggleIndex]) : true + + // Normalize slider to 0-1 (default full intensity if no slider) + let t = 1 + if (sliderControl) { + const raw = (values?.[sliderIndex] as number) ?? sliderControl.min + t = (raw - sliderControl.min) / (sliderControl.max - sliderControl.min) + } + + const target = isOn + ? MathUtils.lerp(effect.intensityRange[0], effect.intensityRange[1], t) + : effect.intensityRange[0] + + lightRef.current.intensity = MathUtils.lerp(lightRef.current.intensity, target, Math.min(delta * 12, 1)) + }) + + return ( + ) } diff --git a/packages/viewer/src/systems/interactive/interactive-system.tsx b/packages/viewer/src/systems/interactive/interactive-system.tsx index f3cd8035..aa1415c0 100644 --- a/packages/viewer/src/systems/interactive/interactive-system.tsx +++ b/packages/viewer/src/systems/interactive/interactive-system.tsx @@ -141,6 +141,7 @@ const ControlWidget = ({ step={control.step} value={value as number} onChange={(e) => onChange(Number(e.target.value))} + onPointerDown={(e) => e.stopPropagation()} /> ) @@ -159,6 +160,7 @@ const ControlWidget = ({ step={1} value={value as number} onChange={(e) => onChange(Number(e.target.value))} + onPointerDown={(e) => e.stopPropagation()} /> )