@pascal-app/viewer 1.0.6 → 1.0.7
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/viewer/daylight-atmosphere.d.ts +9 -0
- package/dist/components/viewer/daylight-atmosphere.d.ts.map +1 -0
- package/dist/components/viewer/daylight-atmosphere.js +315 -0
- package/dist/components/viewer/drone-controller.d.ts +15 -0
- package/dist/components/viewer/drone-controller.d.ts.map +1 -0
- package/dist/components/viewer/drone-controller.js +194 -0
- package/dist/components/viewer/glb-interactive.d.ts +4 -18
- package/dist/components/viewer/glb-interactive.d.ts.map +1 -1
- package/dist/components/viewer/glb-interactive.js +32 -268
- package/dist/components/viewer/glb-scene-motion.d.ts +8 -0
- package/dist/components/viewer/glb-scene-motion.d.ts.map +1 -0
- package/dist/components/viewer/glb-scene-motion.js +57 -0
- package/dist/components/viewer/glb-scene.d.ts +3 -1
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +289 -49
- package/dist/components/viewer/glb-selection-callout.d.ts +17 -0
- package/dist/components/viewer/glb-selection-callout.d.ts.map +1 -0
- package/dist/components/viewer/glb-selection-callout.js +54 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.js +6 -3
- package/dist/components/viewer/index.d.ts +5 -0
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +30 -6
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +192 -5
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +150 -94
- package/dist/components/viewer/scene-environment.js +4 -5
- package/dist/components/viewer/selection-callout.d.ts +51 -0
- package/dist/components/viewer/selection-callout.d.ts.map +1 -0
- package/dist/components/viewer/selection-callout.js +197 -0
- package/dist/components/viewer/use-daylight-rooms.d.ts +13 -0
- package/dist/components/viewer/use-daylight-rooms.d.ts.map +1 -0
- package/dist/components/viewer/use-daylight-rooms.js +236 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +12 -1
- package/dist/index.d.ts +21 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -7
- package/dist/lib/daylight/ground.d.ts +21 -0
- package/dist/lib/daylight/ground.d.ts.map +1 -0
- package/dist/lib/daylight/ground.js +53 -0
- package/dist/lib/daylight/location.d.ts +32 -0
- package/dist/lib/daylight/location.d.ts.map +1 -0
- package/dist/lib/daylight/location.js +74 -0
- package/dist/lib/daylight/look.d.ts +33 -0
- package/dist/lib/daylight/look.d.ts.map +1 -0
- package/dist/lib/daylight/look.js +13 -0
- package/dist/lib/daylight/material-hooks.d.ts +2 -0
- package/dist/lib/daylight/material-hooks.d.ts.map +1 -0
- package/dist/lib/daylight/material-hooks.js +150 -0
- package/dist/lib/daylight/room-context.d.ts +12 -0
- package/dist/lib/daylight/room-context.d.ts.map +1 -0
- package/dist/lib/daylight/room-context.js +53 -0
- package/dist/lib/daylight/room-scene.d.ts +54 -0
- package/dist/lib/daylight/room-scene.d.ts.map +1 -0
- package/dist/lib/daylight/room-scene.js +356 -0
- package/dist/lib/daylight/rooms.d.ts +262 -0
- package/dist/lib/daylight/rooms.d.ts.map +1 -0
- package/dist/lib/daylight/rooms.js +1223 -0
- package/dist/lib/daylight/runtime.d.ts +33 -0
- package/dist/lib/daylight/runtime.d.ts.map +1 -0
- package/dist/lib/daylight/runtime.js +42 -0
- package/dist/lib/daylight/sky.d.ts +26 -0
- package/dist/lib/daylight/sky.d.ts.map +1 -0
- package/dist/lib/daylight/sky.js +217 -0
- package/dist/lib/daylight/solar.d.ts +46 -0
- package/dist/lib/daylight/solar.d.ts.map +1 -0
- package/dist/lib/daylight/solar.js +156 -0
- package/dist/lib/device-tier.d.ts +29 -0
- package/dist/lib/device-tier.d.ts.map +1 -0
- package/dist/lib/device-tier.js +71 -0
- package/dist/lib/dispose-object3d.d.ts.map +1 -1
- package/dist/lib/dispose-object3d.js +4 -2
- package/dist/lib/drone-flight.d.ts +44 -0
- package/dist/lib/drone-flight.d.ts.map +1 -0
- package/dist/lib/drone-flight.js +121 -0
- package/dist/lib/editor-highlight.d.ts +16 -0
- package/dist/lib/editor-highlight.d.ts.map +1 -0
- package/dist/lib/editor-highlight.js +33 -0
- package/dist/lib/foundation-materials.d.ts.map +1 -1
- package/dist/lib/foundation-materials.js +2 -4
- package/dist/lib/glb-merge.d.ts +23 -0
- package/dist/lib/glb-merge.d.ts.map +1 -0
- package/dist/lib/glb-merge.js +184 -0
- package/dist/lib/hardware-geometry.d.ts +21 -0
- package/dist/lib/hardware-geometry.d.ts.map +1 -0
- package/dist/lib/hardware-geometry.js +283 -0
- package/dist/lib/layer-pass.d.ts +2 -0
- package/dist/lib/layer-pass.d.ts.map +1 -1
- package/dist/lib/layer-pass.js +20 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +50 -23
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +7 -1
- package/dist/lib/occlusion-culler.d.ts +34 -0
- package/dist/lib/occlusion-culler.d.ts.map +1 -0
- package/dist/lib/occlusion-culler.js +698 -0
- package/dist/lib/post-processing-resources.d.ts +4 -0
- package/dist/lib/post-processing-resources.d.ts.map +1 -1
- package/dist/lib/post-processing-resources.js +5 -0
- package/dist/lib/readback-rows.d.ts +14 -0
- package/dist/lib/readback-rows.d.ts.map +1 -0
- package/dist/lib/readback-rows.js +31 -0
- package/dist/lib/reveal-pose.d.ts.map +1 -1
- package/dist/lib/reveal-pose.js +3 -0
- package/dist/lib/scene-matrices.d.ts +12 -0
- package/dist/lib/scene-matrices.d.ts.map +1 -0
- package/dist/lib/scene-matrices.js +287 -0
- package/dist/lib/scene-pass-mrt.d.ts +5 -2
- package/dist/lib/scene-pass-mrt.d.ts.map +1 -1
- package/dist/lib/scene-pass-mrt.js +7 -2
- package/dist/lib/scene-themes.d.ts +0 -2
- package/dist/lib/scene-themes.d.ts.map +1 -1
- package/dist/lib/scene-themes.js +0 -2
- package/dist/lib/scene-visibility.d.ts +2 -0
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +12 -3
- package/dist/lib/site-surfaces.d.ts +1 -8
- package/dist/lib/site-surfaces.d.ts.map +1 -1
- package/dist/lib/site-surfaces.js +0 -19
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +24 -40
- package/dist/lib/texture-reference.d.ts.map +1 -1
- package/dist/lib/texture-reference.js +10 -8
- package/dist/lib/underground-ambiance.d.ts +0 -2
- package/dist/lib/underground-ambiance.d.ts.map +1 -1
- package/dist/lib/underground-ambiance.js +0 -2
- package/dist/store/use-item-light-pool.d.ts +3 -6
- package/dist/store/use-item-light-pool.d.ts.map +1 -1
- package/dist/store/use-item-light-pool.js +12 -0
- package/dist/store/use-viewer.d.ts +72 -15
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +103 -18
- package/dist/systems/ceiling/ceiling-system.d.ts +5 -0
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +11 -1
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +501 -145
- package/dist/systems/door/leaf-profile.d.ts +11 -0
- package/dist/systems/door/leaf-profile.d.ts.map +1 -0
- package/dist/systems/door/leaf-profile.js +140 -0
- package/dist/systems/interactive/callout-actions.d.ts +72 -0
- package/dist/systems/interactive/callout-actions.d.ts.map +1 -0
- package/dist/systems/interactive/callout-actions.js +212 -0
- package/dist/systems/interactive/interactive-system.d.ts +5 -1
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +44 -121
- package/dist/systems/interactive/opening-settled.d.ts +16 -0
- package/dist/systems/interactive/opening-settled.d.ts.map +1 -0
- package/dist/systems/interactive/opening-settled.js +53 -0
- package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
- package/dist/systems/item-light/item-light-system.js +22 -214
- package/dist/systems/item-light/pooled-lights.d.ts +70 -0
- package/dist/systems/item-light/pooled-lights.d.ts.map +1 -0
- package/dist/systems/item-light/pooled-lights.js +449 -0
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +19 -1
- package/dist/systems/roof/roof-material-slots.d.ts +14 -0
- package/dist/systems/roof/roof-material-slots.d.ts.map +1 -0
- package/dist/systems/roof/roof-material-slots.js +13 -0
- package/dist/systems/roof/roof-materials.d.ts +25 -2
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +85 -26
- package/dist/systems/roof/roof-system.d.ts +34 -1
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +477 -105
- package/dist/systems/wall/opening-casing-geometry.d.ts +14 -0
- package/dist/systems/wall/opening-casing-geometry.d.ts.map +1 -0
- package/dist/systems/wall/opening-casing-geometry.js +102 -0
- package/dist/systems/wall/opening-cutout-geometry.d.ts +9 -0
- package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -1
- package/dist/systems/wall/opening-cutout-geometry.js +7 -3
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +28 -35
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +34 -19
- package/dist/systems/wall/wall-t-junctions.d.ts +15 -0
- package/dist/systems/wall/wall-t-junctions.d.ts.map +1 -0
- package/dist/systems/wall/wall-t-junctions.js +149 -0
- package/dist/systems/window/window-animation-system.d.ts +3 -12
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +37 -122
- package/dist/systems/window/window-pose.d.ts +31 -0
- package/dist/systems/window/window-pose.d.ts.map +1 -0
- package/dist/systems/window/window-pose.js +152 -0
- package/dist/systems/window/window-roller-shutter.d.ts +8 -0
- package/dist/systems/window/window-roller-shutter.d.ts.map +1 -0
- package/dist/systems/window/window-roller-shutter.js +64 -0
- package/dist/systems/window/window-system.d.ts +2 -11
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +470 -303
- package/package.json +6 -6
- package/dist/systems/interactive/control-widget.d.ts +0 -10
- package/dist/systems/interactive/control-widget.d.ts.map +0 -1
- package/dist/systems/interactive/control-widget.js +0 -35
- package/dist/systems/interactive/procedural-controls.d.ts +0 -13
- package/dist/systems/interactive/procedural-controls.d.ts.map +0 -1
- package/dist/systems/interactive/procedural-controls.js +0 -17
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opening-settled.d.ts","sourceRoot":"","sources":["../../../src/systems/interactive/opening-settled.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAA2B,MAAM,kBAAkB,CAAA;AAK1E;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAClC,IAAI,GAAE,MAAM,GAAG,UAAmB,GACjC,OAAO,CAAC,IAAI,CAAC,CAmBf;AAKD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAclE"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { emitter, useInteractive } from '@pascal-app/core';
|
|
2
|
+
/** Longer than any built-in leaf, sash or shutter run, so a missed event never strands a sound. */
|
|
3
|
+
const FALLBACK_MS = 2000;
|
|
4
|
+
/**
|
|
5
|
+
* Resolves when a built-in door's leaf or a window's sash (or, with `shutters`,
|
|
6
|
+
* its shutters) finishes the run just started, so the latch can sound on the
|
|
7
|
+
* frame the leaf lands.
|
|
8
|
+
*/
|
|
9
|
+
export function openingSettled(node, part = 'leaf') {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
const done = () => {
|
|
12
|
+
clearTimeout(timer);
|
|
13
|
+
emitter.off('door:animation-completed', onDoor);
|
|
14
|
+
emitter.off('window:animation-completed', onWindow);
|
|
15
|
+
resolve();
|
|
16
|
+
};
|
|
17
|
+
const onDoor = (event) => {
|
|
18
|
+
if (node.type === 'door' && event.doorId === node.id)
|
|
19
|
+
done();
|
|
20
|
+
};
|
|
21
|
+
const onWindow = (event) => {
|
|
22
|
+
if (node.type !== 'window' || event.windowId !== node.id)
|
|
23
|
+
return;
|
|
24
|
+
if ((event.field === 'operationState') === (part === 'leaf'))
|
|
25
|
+
done();
|
|
26
|
+
};
|
|
27
|
+
const timer = setTimeout(done, FALLBACK_MS);
|
|
28
|
+
emitter.on('door:animation-completed', onDoor);
|
|
29
|
+
emitter.on('window:animation-completed', onWindow);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Recipe timelines run longer than a door leaf; past this, a lost event still frees the sound. */
|
|
33
|
+
const MOTION_FALLBACK_MS = 6000;
|
|
34
|
+
/**
|
|
35
|
+
* Resolves when a procedural item's latest commanded run (a cabinet door, a
|
|
36
|
+
* drawer, a lid) has its moving parts at rest. Call it right after the toggle.
|
|
37
|
+
*/
|
|
38
|
+
export function proceduralSettled(nodeId) {
|
|
39
|
+
const sequence = useInteractive.getState().procedural[nodeId]?.motionCommand?.sequence ?? 0;
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
const done = () => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
emitter.off('procedural:motion-settled', onSettled);
|
|
44
|
+
resolve();
|
|
45
|
+
};
|
|
46
|
+
const onSettled = (event) => {
|
|
47
|
+
if (event.nodeId === nodeId && event.sequence >= sequence)
|
|
48
|
+
done();
|
|
49
|
+
};
|
|
50
|
+
const timer = setTimeout(done, MOTION_FALLBACK_MS);
|
|
51
|
+
emitter.on('procedural:motion-settled', onSettled);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"item-light-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item-light/item-light-system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"item-light-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item-light/item-light-system.tsx"],"names":[],"mappings":"AA+CA,wBAAgB,eAAe,uCAoB9B"}
|
|
@@ -1,25 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { findLevelAncestorId, sceneRegistry, useScene } from '@pascal-app/core';
|
|
3
|
-
import {
|
|
4
|
-
import { useRef } from 'react';
|
|
5
|
-
import { MathUtils, Vector3 } from 'three';
|
|
3
|
+
import { useThree } from '@react-three/fiber';
|
|
6
4
|
import { SCENE_LAYER } from '../../lib/layers';
|
|
7
5
|
import { useItemLightPool } from '../../store/use-item-light-pool';
|
|
8
6
|
import useViewer from '../../store/use-viewer';
|
|
9
|
-
|
|
10
|
-
// How often (in seconds) to re-evaluate which items have lights assigned (fallback timer)
|
|
11
|
-
const REASSIGN_INTERVAL = 0.2;
|
|
12
|
-
// Hysteresis: a currently-assigned slot keeps its key unless an unassigned
|
|
13
|
-
// candidate beats it by at least this much (prevents flickering at the boundary)
|
|
14
|
-
const HYSTERESIS = 0.15;
|
|
15
|
-
// Camera movement thresholds that trigger an early re-evaluation
|
|
16
|
-
const CAM_MOVE_DIST = 0.5; // units
|
|
17
|
-
const CAM_ROT_DOT = 0.995; // cos(~5.7°)
|
|
18
|
-
// Module-level temp vectors reused every frame (avoids GC pressure)
|
|
19
|
-
const _dir = new Vector3();
|
|
20
|
-
const _camPos = new Vector3();
|
|
21
|
-
const _camFwd = new Vector3();
|
|
22
|
-
const _itemPos = new Vector3();
|
|
7
|
+
import { PooledLights } from './pooled-lights';
|
|
23
8
|
function isRendered(nodeId) {
|
|
24
9
|
let object = sceneRegistry.nodes.get(nodeId);
|
|
25
10
|
if (!object?.layers.isEnabled(SCENE_LAYER))
|
|
@@ -31,213 +16,36 @@ function isRendered(nodeId) {
|
|
|
31
16
|
}
|
|
32
17
|
return true;
|
|
33
18
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
19
|
+
// A light on another storey than the one being edited counts for this much of what it adds.
|
|
20
|
+
const OTHER_LEVEL_WEIGHT = 0.25;
|
|
21
|
+
/** 0 while the light cannot be seen lit (off, hidden, on a hidden level), else its level factor. */
|
|
22
|
+
function registrationWeight(reg, nodes, selectedLevelId, levelMode, hideAbove) {
|
|
23
|
+
if (!reg.isEligible() || !isRendered(reg.nodeId))
|
|
24
|
+
return 0;
|
|
37
25
|
const { nodeId } = reg;
|
|
38
26
|
let current = nodes[nodeId];
|
|
39
27
|
while (current) {
|
|
40
28
|
if (current.type !== 'site' && current.visible === false)
|
|
41
|
-
return
|
|
29
|
+
return 0;
|
|
42
30
|
current = current.parentId ? nodes[current.parentId] : undefined;
|
|
43
31
|
}
|
|
44
32
|
const itemLevelId = findLevelAncestorId(nodeId, nodes);
|
|
45
|
-
if (selectedLevelId
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// ── Level factor ──────────────────────────────────────────────────────────
|
|
54
|
-
let levelPenalty = 0;
|
|
55
|
-
if (selectedLevelId) {
|
|
56
|
-
if (itemLevelId !== selectedLevelId) {
|
|
57
|
-
// Items on hidden levels (every other one in solo, those above the
|
|
58
|
-
// selected one while editing) are invisible — deprioritize strongly
|
|
59
|
-
const itemIndex = nodes[itemLevelId]?.level ?? 0;
|
|
60
|
-
const selectedIndex = nodes[selectedLevelId]?.level ?? 0;
|
|
61
|
-
const hidden = levelMode === 'solo' || (hideAbove && itemIndex > selectedIndex);
|
|
62
|
-
levelPenalty = hidden ? 100 : 0.8;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
else if (itemLevelId) {
|
|
66
|
-
// No level selected — lightly prefer items on level index 0
|
|
67
|
-
const levelNode = nodes[itemLevelId];
|
|
68
|
-
const levelIndex = levelNode?.level ?? 0;
|
|
69
|
-
if (levelIndex !== 0)
|
|
70
|
-
levelPenalty = 0.3;
|
|
71
|
-
}
|
|
72
|
-
return angular * 0.7 + dist * 0.3 + levelPenalty;
|
|
33
|
+
if (!selectedLevelId || itemLevelId === selectedLevelId)
|
|
34
|
+
return 1;
|
|
35
|
+
if (levelMode === 'solo')
|
|
36
|
+
return 0;
|
|
37
|
+
// Above the selected level while editing, a storey is hidden.
|
|
38
|
+
const itemIndex = nodes[itemLevelId]?.level ?? 0;
|
|
39
|
+
const selectedIndex = nodes[selectedLevelId]?.level ?? 0;
|
|
40
|
+
return hideAbove && itemIndex > selectedIndex ? 0 : OTHER_LEVEL_WEIGHT;
|
|
73
41
|
}
|
|
74
42
|
export function ItemLightSystem() {
|
|
75
43
|
const scene = useThree((state) => state.scene);
|
|
76
44
|
const bakedOwner = useItemLightPool((state) => state.bakedCanvases.has(scene));
|
|
77
|
-
const lightRefs = useRef(Array.from({ length: POOL_SIZE }, () => null));
|
|
78
|
-
const slots = useRef(Array.from({ length: POOL_SIZE }, () => ({ key: null, pendingKey: null, isFadingOut: false })));
|
|
79
|
-
const reassignTimer = useRef(0);
|
|
80
|
-
// Track camera state at last reassignment to detect meaningful movement
|
|
81
|
-
const prevReassignCamPos = useRef(new Vector3());
|
|
82
|
-
const prevReassignCamFwd = useRef(new Vector3(0, 0, -1));
|
|
83
|
-
useFrame(({ camera }, delta) => {
|
|
84
|
-
if (bakedOwner)
|
|
85
|
-
return;
|
|
86
|
-
const dt = Math.min(delta, 0.1);
|
|
87
|
-
const { registrations } = useItemLightPool.getState();
|
|
88
|
-
// ── 1. Throttled priority reassignment ──────────────────────────────────
|
|
89
|
-
camera.getWorldPosition(_camPos);
|
|
90
|
-
camera.getWorldDirection(_camFwd);
|
|
91
|
-
const camMoved = _camPos.distanceTo(prevReassignCamPos.current) > CAM_MOVE_DIST ||
|
|
92
|
-
_camFwd.dot(prevReassignCamFwd.current) < CAM_ROT_DOT;
|
|
93
|
-
reassignTimer.current -= delta;
|
|
94
|
-
const shouldReassign = reassignTimer.current <= 0 || camMoved;
|
|
95
|
-
if (shouldReassign) {
|
|
96
|
-
reassignTimer.current = REASSIGN_INTERVAL;
|
|
97
|
-
prevReassignCamPos.current.copy(_camPos);
|
|
98
|
-
prevReassignCamFwd.current.copy(_camFwd);
|
|
99
|
-
// Read level/scene state once for the whole tick
|
|
100
|
-
const nodes = useScene.getState().nodes;
|
|
101
|
-
const viewerState = useViewer.getState();
|
|
102
|
-
const selectedLevelId = viewerState.selection.levelId;
|
|
103
|
-
const levelMode = viewerState.levelMode;
|
|
104
|
-
const hideAbove = viewerState.hideLevelsAboveSelection;
|
|
105
|
-
// Score every registration
|
|
106
|
-
const scored = [];
|
|
107
|
-
for (const [key, reg] of registrations) {
|
|
108
|
-
scored.push({
|
|
109
|
-
key,
|
|
110
|
-
score: scoreRegistration(reg, nodes, selectedLevelId, levelMode, hideAbove),
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
scored.sort((a, b) => a.score - b.score);
|
|
114
|
-
// Build the desired assignment (top POOL_SIZE keys)
|
|
115
|
-
const desired = scored
|
|
116
|
-
.filter((s) => Number.isFinite(s.score))
|
|
117
|
-
.slice(0, POOL_SIZE)
|
|
118
|
-
.map((s) => s.key);
|
|
119
|
-
// Build a map of currently-assigned keys → slot index for hysteresis
|
|
120
|
-
const currentlyAssigned = new Map();
|
|
121
|
-
for (let i = 0; i < POOL_SIZE; i++) {
|
|
122
|
-
const s = slots.current[i];
|
|
123
|
-
if (!s)
|
|
124
|
-
continue;
|
|
125
|
-
const k = s.key ?? s.pendingKey;
|
|
126
|
-
if (k)
|
|
127
|
-
currentlyAssigned.set(k, i);
|
|
128
|
-
}
|
|
129
|
-
// Assign desired keys to slots — prefer keeping existing assignments
|
|
130
|
-
const usedSlots = new Set();
|
|
131
|
-
const assignedKeys = new Set();
|
|
132
|
-
// Pass 1: keep existing slots where the key is still in desired
|
|
133
|
-
for (const key of desired) {
|
|
134
|
-
const existingSlot = currentlyAssigned.get(key);
|
|
135
|
-
if (existingSlot !== undefined && !usedSlots.has(existingSlot)) {
|
|
136
|
-
usedSlots.add(existingSlot);
|
|
137
|
-
assignedKeys.add(key);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// Pass 2: assign remaining desired keys to free slots
|
|
141
|
-
let freeSlot = 0;
|
|
142
|
-
for (const key of desired) {
|
|
143
|
-
if (assignedKeys.has(key))
|
|
144
|
-
continue;
|
|
145
|
-
while (freeSlot < POOL_SIZE && usedSlots.has(freeSlot))
|
|
146
|
-
freeSlot++;
|
|
147
|
-
if (freeSlot >= POOL_SIZE)
|
|
148
|
-
break;
|
|
149
|
-
// Hysteresis: only evict the current occupant if the new key scores
|
|
150
|
-
// meaningfully better than it
|
|
151
|
-
const freeSlotData = slots.current[freeSlot];
|
|
152
|
-
const currentKey = freeSlotData ? (freeSlotData.key ?? freeSlotData.pendingKey) : null;
|
|
153
|
-
if (currentKey && !desired.includes(currentKey)) {
|
|
154
|
-
const currentScore = scored.find((s) => s.key === currentKey)?.score ?? Number.POSITIVE_INFINITY;
|
|
155
|
-
const newScore = scored.find((s) => s.key === key)?.score ?? 0;
|
|
156
|
-
if (currentScore - newScore < HYSTERESIS) {
|
|
157
|
-
freeSlot++;
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
usedSlots.add(freeSlot);
|
|
162
|
-
assignedKeys.add(key);
|
|
163
|
-
const slot = slots.current[freeSlot];
|
|
164
|
-
if (slot && slot.key !== key) {
|
|
165
|
-
slot.pendingKey = key;
|
|
166
|
-
slot.isFadingOut = slot.key !== null;
|
|
167
|
-
if (!slot.isFadingOut) {
|
|
168
|
-
// Slot was idle — skip fade-out, assign immediately
|
|
169
|
-
slot.key = key;
|
|
170
|
-
slot.pendingKey = null;
|
|
171
|
-
const light = lightRefs.current[freeSlot];
|
|
172
|
-
const reg = registrations.get(key);
|
|
173
|
-
if (light && reg) {
|
|
174
|
-
light.color.set(reg.color);
|
|
175
|
-
light.distance = reg.distance;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
freeSlot++;
|
|
180
|
-
}
|
|
181
|
-
// Clear slots whose key is no longer in desired and not pending
|
|
182
|
-
for (let i = 0; i < POOL_SIZE; i++) {
|
|
183
|
-
if (!usedSlots.has(i)) {
|
|
184
|
-
const slot = slots.current[i];
|
|
185
|
-
if (slot?.key && !desired.includes(slot.key)) {
|
|
186
|
-
slot.pendingKey = null;
|
|
187
|
-
slot.isFadingOut = true;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
// ── 2. Per-frame light updates ───────────────────────────────────────────
|
|
193
|
-
for (let i = 0; i < POOL_SIZE; i++) {
|
|
194
|
-
const light = lightRefs.current[i];
|
|
195
|
-
if (!light)
|
|
196
|
-
continue;
|
|
197
|
-
const slot = slots.current[i];
|
|
198
|
-
if (!slot)
|
|
199
|
-
continue;
|
|
200
|
-
// Fade-out phase: lerp intensity → 0, then complete the transition
|
|
201
|
-
if (slot.isFadingOut) {
|
|
202
|
-
light.intensity = MathUtils.lerp(light.intensity, 0, dt * 12);
|
|
203
|
-
if (light.intensity < 0.01) {
|
|
204
|
-
light.intensity = 0;
|
|
205
|
-
slot.isFadingOut = false;
|
|
206
|
-
slot.key = slot.pendingKey;
|
|
207
|
-
slot.pendingKey = null;
|
|
208
|
-
if (slot.key) {
|
|
209
|
-
const reg = registrations.get(slot.key);
|
|
210
|
-
if (reg) {
|
|
211
|
-
light.color.set(reg.color);
|
|
212
|
-
light.distance = reg.distance;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
if (!slot.key) {
|
|
219
|
-
// Idle slot — keep dark
|
|
220
|
-
light.intensity = 0;
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
const reg = registrations.get(slot.key);
|
|
224
|
-
if (!reg) {
|
|
225
|
-
slot.key = null;
|
|
226
|
-
light.intensity = 0;
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
if (reg.getWorldPosition(_itemPos))
|
|
230
|
-
light.position.copy(_itemPos);
|
|
231
|
-
const targetIntensity = reg.isEligible() && isRendered(reg.nodeId) ? reg.getIntensity() : 0;
|
|
232
|
-
light.intensity = MathUtils.lerp(light.intensity, targetIntensity, dt * 12);
|
|
233
|
-
if (targetIntensity <= 0 && light.intensity < 0.01) {
|
|
234
|
-
light.intensity = 0;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}, 6);
|
|
238
45
|
if (bakedOwner)
|
|
239
46
|
return null;
|
|
240
|
-
return (_jsx(
|
|
241
|
-
|
|
242
|
-
|
|
47
|
+
return (_jsx(PooledLights, { getLights: () => useItemLightPool.getState().registrations.values(), intensity: (reg) => (reg.isEligible() && isRendered(reg.nodeId) ? reg.getIntensity() : 0), weight: (reg) => {
|
|
48
|
+
const viewer = useViewer.getState();
|
|
49
|
+
return registrationWeight(reg, useScene.getState().nodes, viewer.selection.levelId, viewer.levelMode, viewer.hideLevelsAboveSelection);
|
|
50
|
+
} }));
|
|
243
51
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { LightSpot } from '@pascal-app/core';
|
|
2
|
+
import { type Camera, Frustum, type Object3D, Vector3 } from 'three/webgpu';
|
|
3
|
+
/** A light an item holds, as the pools drive it. */
|
|
4
|
+
export type PooledLight = {
|
|
5
|
+
key: string;
|
|
6
|
+
color: string;
|
|
7
|
+
distance: number;
|
|
8
|
+
spot?: LightSpot;
|
|
9
|
+
shadows?: boolean;
|
|
10
|
+
glow?: {
|
|
11
|
+
size: number;
|
|
12
|
+
strength?: number;
|
|
13
|
+
};
|
|
14
|
+
/** Its fully-on intensity, so its glow dims with it. */
|
|
15
|
+
peak?: number;
|
|
16
|
+
/** Where it shines from; false while its object is not mounted. */
|
|
17
|
+
getWorldPosition: (out: Vector3) => boolean;
|
|
18
|
+
/** Its spot's aim in world space, turned with its item. */
|
|
19
|
+
getWorldDirection?: (out: Vector3) => void;
|
|
20
|
+
};
|
|
21
|
+
type Props<T extends PooledLight> = {
|
|
22
|
+
getLights: () => Iterable<T>;
|
|
23
|
+
/**
|
|
24
|
+
* 0 while it cannot be seen lit (off, hidden, on a hidden level); otherwise a factor on what it
|
|
25
|
+
* adds to the view (a light on another storey than the one being edited counts for less).
|
|
26
|
+
*/
|
|
27
|
+
weight: (light: T) => number;
|
|
28
|
+
/** What it shines at now: 0 while off or not drawn. */
|
|
29
|
+
intensity: (light: T) => number;
|
|
30
|
+
};
|
|
31
|
+
type Slot = {
|
|
32
|
+
key: string | null;
|
|
33
|
+
pendingKey: string | null;
|
|
34
|
+
isFadingOut: boolean;
|
|
35
|
+
};
|
|
36
|
+
type Candidate = {
|
|
37
|
+
key: string;
|
|
38
|
+
priority: number;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Roughly what a light adds to a view: its intensity over its squared distance from the eye, or 0
|
|
42
|
+
* when its reach (its distance, else where it falls under REACH_CUTOFF) misses the view.
|
|
43
|
+
*/
|
|
44
|
+
export declare function lightContribution(position: Vector3, intensity: number, distance: number, eye: Vector3, frustum: Frustum): number;
|
|
45
|
+
/**
|
|
46
|
+
* Gives the wanted keys (most first) the pool's slots: a key that holds a slot keeps it, and a
|
|
47
|
+
* newcomer evicts a key no longer wanted only on a clear win. A slot changing hands fades out
|
|
48
|
+
* first; an idle slot takes its key at once.
|
|
49
|
+
*/
|
|
50
|
+
export declare function assignSlots(slots: Slot[], wanted: Candidate[], priorityByKey: Map<string, number>): void;
|
|
51
|
+
/** How much a glow shows: 1 at night, nearly nothing in daylight. */
|
|
52
|
+
export declare function glowDarkness(sunDirection: Vector3 | null, appearance: 'light' | 'dark'): number;
|
|
53
|
+
/**
|
|
54
|
+
* Sets the item lights of `scene` for a picture taken through `camera`: the pools re-ranked for
|
|
55
|
+
* what that camera sees and every light straight at its strength, no fade. A capture camera is not
|
|
56
|
+
* the live view's, and a theme switched for the picture leaves no time for lamps to fade in.
|
|
57
|
+
* Returns what puts the live view's lights back.
|
|
58
|
+
*/
|
|
59
|
+
export declare function itemLightsForCapture(scene: Object3D, camera: Camera): () => void;
|
|
60
|
+
/**
|
|
61
|
+
* The lights items hold, through fixed pools of point lights, spots and two shadow-casting slots,
|
|
62
|
+
* handed to the lights that add most to the view (intensity over squared distance, among those
|
|
63
|
+
* whose reach meets the view frustum), with hysteresis, and faded across reassignment. Every pool
|
|
64
|
+
* light stays mounted and visible and only its intensity moves: changing the light count
|
|
65
|
+
* recompiles every material. The spot and shadow pools mount the first time a light needs them,
|
|
66
|
+
* and stay. Glows are one instanced sprite for every glowing lamp, pooled or not.
|
|
67
|
+
*/
|
|
68
|
+
export declare function PooledLights<T extends PooledLight>(props: Props<T>): import("react").JSX.Element;
|
|
69
|
+
export {};
|
|
70
|
+
//# sourceMappingURL=pooled-lights.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pooled-lights.d.ts","sourceRoot":"","sources":["../../../src/systems/item-light/pooled-lights.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAYjD,OAAO,EAEL,KAAK,MAAM,EAGX,OAAO,EAIP,KAAK,QAAQ,EAMb,OAAO,EACR,MAAM,cAAc,CAAA;AAMrB,oDAAoD;AACpD,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,mEAAmE;IACnE,gBAAgB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAA;IAC3C,2DAA2D;IAC3D,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CAC3C,CAAA;AAED,KAAK,KAAK,CAAC,CAAC,SAAS,WAAW,IAAI;IAClC,SAAS,EAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAA;IAC5B;;;OAGG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAA;IAC5B,uDAAuD;IACvD,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAA;CAChC,CAAA;AAmCD,KAAK,IAAI,GAAG;IAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAA;AACnF,KAAK,SAAS,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AAoBlD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,OAAO,GACf,MAAM,CAKR;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,IAAI,EAAE,EACb,MAAM,EAAE,SAAS,EAAE,EACnB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,QAiDnC;AAWD,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,YAAY,EAAE,OAAO,GAAG,IAAI,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAI/F;AAuGD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,CAKhF;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,+BAKlE"}
|