@pascal-app/viewer 1.0.3 → 1.0.5
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/capture/capture-runtime.js +1 -1
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.d.ts +1 -1
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +12 -4
- package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -1
- package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.d.ts +1 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +22 -3
- package/dist/components/viewer/glb-interactive.d.ts +25 -1
- package/dist/components/viewer/glb-interactive.d.ts.map +1 -1
- package/dist/components/viewer/glb-interactive.js +253 -86
- package/dist/components/viewer/glb-mechanisms.d.ts +10 -0
- package/dist/components/viewer/glb-mechanisms.d.ts.map +1 -0
- package/dist/components/viewer/glb-mechanisms.js +19 -0
- package/dist/components/viewer/glb-reference-nodes.js +1 -1
- package/dist/components/viewer/glb-replace-instances.js +1 -1
- package/dist/components/viewer/glb-scene.d.ts +45 -27
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +421 -508
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.js +13 -2
- package/dist/components/viewer/index.d.ts +24 -1
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +111 -37
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +113 -22
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +6 -0
- package/dist/components/viewer/perf-panel.d.ts.map +1 -1
- package/dist/components/viewer/perf-panel.js +1 -1
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +104 -36
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +17 -3
- package/dist/components/viewer/render-error.d.ts +18 -0
- package/dist/components/viewer/render-error.d.ts.map +1 -0
- package/dist/components/viewer/render-error.js +29 -0
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -1
- package/dist/components/viewer/scene-bvh.js +1 -1
- package/dist/components/viewer/scene-environment.d.ts.map +1 -1
- package/dist/components/viewer/scene-environment.js +31 -11
- package/dist/components/viewer/scene-ground-replacement.d.ts +6 -0
- package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -1
- package/dist/components/viewer/scene-ground-replacement.js +15 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +25 -7
- package/dist/components/viewer/underground-ambiance.d.ts +9 -0
- package/dist/components/viewer/underground-ambiance.d.ts.map +1 -0
- package/dist/components/viewer/underground-ambiance.js +105 -0
- package/dist/components/viewer/viewer-camera.d.ts +4 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +12 -2
- package/dist/components/viewer/viewer-presentations.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +2 -0
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/index.d.ts +36 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -19
- package/dist/lib/asset-url.d.ts.map +1 -1
- package/dist/lib/asset-url.js +7 -1
- package/dist/lib/backdrop.d.ts +17 -0
- package/dist/lib/backdrop.d.ts.map +1 -1
- package/dist/lib/backdrop.js +18 -1
- package/dist/lib/construction-dust.d.ts +57 -0
- package/dist/lib/construction-dust.d.ts.map +1 -0
- package/dist/lib/construction-dust.js +187 -0
- package/dist/lib/csg-utils.js +1 -1
- package/dist/lib/display-state.d.ts +25 -0
- package/dist/lib/display-state.d.ts.map +1 -0
- package/dist/lib/display-state.js +1 -0
- package/dist/lib/foundation-materials.d.ts +26 -0
- package/dist/lib/foundation-materials.d.ts.map +1 -0
- package/dist/lib/foundation-materials.js +65 -0
- package/dist/lib/gltf-unlit.d.ts +10 -0
- package/dist/lib/gltf-unlit.d.ts.map +1 -0
- package/dist/lib/gltf-unlit.js +23 -0
- package/dist/lib/ground-window.d.ts +33 -0
- package/dist/lib/ground-window.d.ts.map +1 -0
- package/dist/lib/ground-window.js +114 -0
- package/dist/lib/hero-pose.d.ts +13 -3
- package/dist/lib/hero-pose.d.ts.map +1 -1
- package/dist/lib/hero-pose.js +72 -5
- package/dist/lib/ink-edges.d.ts +6 -3
- package/dist/lib/ink-edges.d.ts.map +1 -1
- package/dist/lib/ink-edges.js +17 -7
- package/dist/lib/key-light-override.d.ts +11 -0
- package/dist/lib/key-light-override.d.ts.map +1 -0
- package/dist/lib/key-light-override.js +14 -0
- package/dist/lib/ktx2-loader.d.ts +7 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -1
- package/dist/lib/ktx2-loader.js +35 -0
- package/dist/lib/layer-pass.d.ts.map +1 -1
- package/dist/lib/layers.d.ts +11 -7
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +16 -6
- package/dist/lib/live-frame-hold.d.ts +14 -0
- package/dist/lib/live-frame-hold.d.ts.map +1 -0
- package/dist/lib/live-frame-hold.js +28 -0
- package/dist/lib/materials.d.ts +19 -7
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +65 -9
- package/dist/lib/merged-outline-node.d.ts +5 -0
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +80 -2
- package/dist/lib/node-top-surface-height.d.ts +6 -0
- package/dist/lib/node-top-surface-height.d.ts.map +1 -0
- package/dist/lib/node-top-surface-height.js +74 -0
- package/dist/lib/perf-probe-state.d.ts +39 -0
- package/dist/lib/perf-probe-state.d.ts.map +1 -0
- package/dist/lib/perf-probe-state.js +17 -0
- package/dist/lib/pointer-events.d.ts +26 -2
- package/dist/lib/pointer-events.d.ts.map +1 -1
- package/dist/lib/pointer-events.js +99 -12
- package/dist/lib/post-processing-resources.d.ts.map +1 -1
- package/dist/lib/procedural-emission.d.ts +7 -0
- package/dist/lib/procedural-emission.d.ts.map +1 -0
- package/dist/lib/procedural-emission.js +54 -0
- package/dist/lib/reveal-candidates.d.ts +80 -0
- package/dist/lib/reveal-candidates.d.ts.map +1 -0
- package/dist/lib/reveal-candidates.js +524 -0
- package/dist/lib/reveal-ghost.d.ts +9 -0
- package/dist/lib/reveal-ghost.d.ts.map +1 -0
- package/dist/lib/reveal-ghost.js +62 -0
- package/dist/lib/reveal-pending.d.ts +12 -0
- package/dist/lib/reveal-pending.d.ts.map +1 -0
- package/dist/lib/reveal-pending.js +12 -0
- package/dist/lib/reveal-pose.d.ts +70 -0
- package/dist/lib/reveal-pose.d.ts.map +1 -0
- package/dist/lib/reveal-pose.js +248 -0
- package/dist/lib/reveal-schedule.d.ts +208 -0
- package/dist/lib/reveal-schedule.d.ts.map +1 -0
- package/dist/lib/reveal-schedule.js +410 -0
- package/dist/lib/reveal-texture-scale.d.ts +5 -0
- package/dist/lib/reveal-texture-scale.d.ts.map +1 -0
- package/dist/lib/reveal-texture-scale.js +113 -0
- package/dist/lib/reveal-windows.d.ts +23 -0
- package/dist/lib/reveal-windows.d.ts.map +1 -0
- package/dist/lib/reveal-windows.js +31 -0
- package/dist/lib/reveal-wipe.d.ts +71 -0
- package/dist/lib/reveal-wipe.d.ts.map +1 -0
- package/dist/lib/reveal-wipe.js +105 -0
- package/dist/lib/scene-pass-mrt.d.ts +12 -0
- package/dist/lib/scene-pass-mrt.d.ts.map +1 -0
- package/dist/lib/scene-pass-mrt.js +23 -0
- package/dist/lib/scene-support-height.d.ts +4 -0
- package/dist/lib/scene-support-height.d.ts.map +1 -0
- package/dist/lib/scene-support-height.js +34 -0
- package/dist/lib/scene-themes.d.ts +30 -8
- package/dist/lib/scene-themes.d.ts.map +1 -1
- package/dist/lib/scene-themes.js +36 -0
- package/dist/lib/scene-visibility.d.ts +17 -1
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +53 -3
- package/dist/lib/scripted-opening.d.ts +11 -0
- package/dist/lib/scripted-opening.d.ts.map +1 -0
- package/dist/lib/scripted-opening.js +20 -0
- package/dist/lib/site-surfaces.d.ts +32 -0
- package/dist/lib/site-surfaces.d.ts.map +1 -0
- package/dist/lib/site-surfaces.js +70 -0
- package/dist/lib/snapshot-pipeline.d.ts +34 -3
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +247 -117
- package/dist/lib/terrain-perimeter-fill.d.ts.map +1 -1
- package/dist/lib/terrain-perimeter-fill.js +22 -14
- package/dist/lib/tsl-compat.d.ts +2 -2
- package/dist/lib/tsl-compat.d.ts.map +1 -1
- package/dist/lib/underground-ambiance.d.ts +168 -0
- package/dist/lib/underground-ambiance.d.ts.map +1 -0
- package/dist/lib/underground-ambiance.js +334 -0
- package/dist/lib/zone-geometry.d.ts +11 -0
- package/dist/lib/zone-geometry.d.ts.map +1 -0
- package/dist/lib/zone-geometry.js +46 -0
- package/dist/store/use-item-light-pool.d.ts +14 -10
- package/dist/store/use-item-light-pool.d.ts.map +1 -1
- package/dist/store/use-item-light-pool.js +52 -26
- package/dist/store/use-viewer.d.ts +194 -26
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +164 -21
- package/dist/systems/ceiling/ceiling-system.d.ts +15 -1
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +88 -6
- package/dist/systems/construction-reveal/construction-dust.d.ts +16 -0
- package/dist/systems/construction-reveal/construction-dust.d.ts.map +1 -0
- package/dist/systems/construction-reveal/construction-dust.js +92 -0
- package/dist/systems/construction-reveal/construction-reveal.d.ts +276 -0
- package/dist/systems/construction-reveal/construction-reveal.d.ts.map +1 -0
- package/dist/systems/construction-reveal/construction-reveal.js +2383 -0
- package/dist/systems/construction-reveal/dust-material.d.ts +10 -0
- package/dist/systems/construction-reveal/dust-material.d.ts.map +1 -0
- package/dist/systems/construction-reveal/dust-material.js +17 -0
- package/dist/systems/construction-reveal/ghost-dress.d.ts +10 -0
- package/dist/systems/construction-reveal/ghost-dress.d.ts.map +1 -0
- package/dist/systems/construction-reveal/ghost-dress.js +24 -0
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +55 -17
- package/dist/systems/elevator/elevator-interaction-system.d.ts +7 -0
- package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-interaction-system.js +18 -1
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts +3 -1
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +33 -5
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +71 -11
- package/dist/systems/interactive/control-widget.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +52 -17
- package/dist/systems/interactive/procedural-controls.d.ts +13 -0
- package/dist/systems/interactive/procedural-controls.d.ts.map +1 -0
- package/dist/systems/interactive/procedural-controls.js +17 -0
- package/dist/systems/interactive/scripted-clips.d.ts +19 -0
- package/dist/systems/interactive/scripted-clips.d.ts.map +1 -0
- package/dist/systems/interactive/scripted-clips.js +134 -0
- package/dist/systems/item/item-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.js +9 -3
- package/dist/systems/item/object-cut-invalidation.d.ts +3 -0
- package/dist/systems/item/object-cut-invalidation.d.ts.map +1 -0
- package/dist/systems/item/object-cut-invalidation.js +68 -0
- package/dist/systems/item-light/item-light-system.d.ts +1 -1
- package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
- package/dist/systems/item-light/item-light-system.js +48 -53
- package/dist/systems/level/level-hold.d.ts +4 -0
- package/dist/systems/level/level-hold.d.ts.map +1 -0
- package/dist/systems/level/level-hold.js +14 -0
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +19 -6
- package/dist/systems/level/level-utils.d.ts +19 -6
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +68 -9
- package/dist/systems/level/shadow-only-levels.d.ts +9 -0
- package/dist/systems/level/shadow-only-levels.d.ts.map +1 -0
- package/dist/systems/level/shadow-only-levels.js +7 -0
- package/dist/systems/roof/__fixtures__/roof-plane-cases.d.ts +103 -0
- package/dist/systems/roof/__fixtures__/roof-plane-cases.d.ts.map +1 -0
- package/dist/systems/roof/__fixtures__/roof-plane-cases.js +317 -0
- package/dist/systems/roof/roof-materials.d.ts +16 -2
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +64 -31
- package/dist/systems/roof/roof-system.d.ts +21 -4
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +374 -49
- package/dist/systems/slab/slab-system.d.ts +1 -1
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +49 -19
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +133 -354
- package/dist/systems/wall/wall-cutout-cache.d.ts +39 -3
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout-cache.js +141 -19
- package/dist/systems/wall/wall-cutout.d.ts +6 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +42 -28
- package/dist/systems/wall/wall-finish-data.d.ts +22 -0
- package/dist/systems/wall/wall-finish-data.d.ts.map +1 -0
- package/dist/systems/wall/wall-finish-data.js +17 -0
- package/dist/systems/wall/wall-material-variant.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.d.ts +9 -4
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +135 -153
- package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.d.ts +33 -2
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +621 -159
- package/dist/systems/window/corner-frame.d.ts +10 -0
- package/dist/systems/window/corner-frame.d.ts.map +1 -0
- package/dist/systems/window/corner-frame.js +17 -0
- package/dist/systems/window/window-system.d.ts +1 -1
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +71 -27
- package/package.json +10 -10
- package/dist/systems/fence/fence-system.d.ts +0 -22
- package/dist/systems/fence/fence-system.d.ts.map +0 -1
- package/dist/systems/fence/fence-system.js +0 -357
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { containsPoint, isScriptedNode, scriptedOrigin, scriptedSize, scriptInteractive, useInteractive, } from '@pascal-app/core';
|
|
4
|
+
import { evaluateRecipe, } from '@pascal-app/core/procedural-items';
|
|
4
5
|
import { Html } from '@react-three/drei';
|
|
5
|
-
import { createPortal, useFrame } from '@react-three/fiber';
|
|
6
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
6
|
+
import { createPortal, useFrame, useThree } from '@react-three/fiber';
|
|
7
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
7
8
|
import { LoopRepeat, MathUtils, Vector3, } from 'three';
|
|
8
9
|
import { useShallow } from 'zustand/react/shallow';
|
|
10
|
+
import { SCENE_LAYER } from '../../lib/layers';
|
|
11
|
+
import { decorateProceduralEmission, proceduralSlotMeshes, setProceduralEmission, } from '../../lib/procedural-emission';
|
|
12
|
+
import { useItemLightPool } from '../../store/use-item-light-pool';
|
|
9
13
|
import useViewer from '../../store/use-viewer';
|
|
10
14
|
import { ControlWidget } from '../../systems/interactive/control-widget';
|
|
15
|
+
import { proceduralControlDescriptors } from '../../systems/interactive/procedural-controls';
|
|
16
|
+
import { ScriptedClips } from '../../systems/interactive/scripted-clips';
|
|
11
17
|
/** Pull the interactive items out of a scene graph. Only items that actually
|
|
12
18
|
* carry effects (light / animation) are returned — everything else baked
|
|
13
19
|
* faithfully and needs no runtime help. */
|
|
@@ -18,6 +24,48 @@ export function buildGlbInteractiveItems(sceneGraph) {
|
|
|
18
24
|
const items = [];
|
|
19
25
|
for (const [id, raw] of Object.entries(nodes)) {
|
|
20
26
|
const node = raw;
|
|
27
|
+
if (node?.type === 'procedural-item') {
|
|
28
|
+
const procedural = raw;
|
|
29
|
+
const evaluation = evaluateRecipe(procedural.recipe, procedural.parameters);
|
|
30
|
+
if (!evaluation.lights.length && !evaluation.motions.length)
|
|
31
|
+
continue;
|
|
32
|
+
items.push({
|
|
33
|
+
pascalId: id,
|
|
34
|
+
label: procedural.name ?? id,
|
|
35
|
+
height: evaluation.max[1],
|
|
36
|
+
interactive: { controls: [], effects: [] },
|
|
37
|
+
procedural: {
|
|
38
|
+
lights: evaluation.lights,
|
|
39
|
+
parts: procedural.recipe.parts,
|
|
40
|
+
recipe: procedural.recipe,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// A node built from a script: its controls come from what the script
|
|
46
|
+
// emitted, as in the editor.
|
|
47
|
+
if (isScriptedNode(raw)) {
|
|
48
|
+
const scripted = raw;
|
|
49
|
+
const interactive = scriptInteractive(scripted.source.manifest, scriptedOrigin(scripted));
|
|
50
|
+
if (!interactive)
|
|
51
|
+
continue;
|
|
52
|
+
const [, height] = scriptedSize(scripted.source.manifest);
|
|
53
|
+
items.push({
|
|
54
|
+
pascalId: id,
|
|
55
|
+
label: scripted.name ??
|
|
56
|
+
(scripted.type === 'item'
|
|
57
|
+
? scripted.asset.name
|
|
58
|
+
: `${scripted.type[0].toUpperCase()}${scripted.type.slice(1)}`),
|
|
59
|
+
// A window or door is placed by its centre, everything else by its base.
|
|
60
|
+
height: scripted.type === 'window' || scripted.type === 'door'
|
|
61
|
+
? height / 2
|
|
62
|
+
: height * (scripted.type === 'item' ? (scripted.scale?.[1] ?? 1) : 1),
|
|
63
|
+
interactive,
|
|
64
|
+
scripted: true,
|
|
65
|
+
localScale: scripted.type === 'item' ? scripted.scale : [1, 1, 1],
|
|
66
|
+
});
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
21
69
|
if (node?.type !== 'item')
|
|
22
70
|
continue;
|
|
23
71
|
const interactive = node.asset?.interactive;
|
|
@@ -34,6 +82,86 @@ export function buildGlbInteractiveItems(sceneGraph) {
|
|
|
34
82
|
}
|
|
35
83
|
return items;
|
|
36
84
|
}
|
|
85
|
+
export function buildGlbLightRegs(items, identity) {
|
|
86
|
+
const regs = [];
|
|
87
|
+
for (const item of items) {
|
|
88
|
+
const object = identity.get(item.pascalId);
|
|
89
|
+
if (!object)
|
|
90
|
+
continue;
|
|
91
|
+
if (item.procedural) {
|
|
92
|
+
const groups = new Map();
|
|
93
|
+
object.traverse((child) => {
|
|
94
|
+
const motion = child.userData.proceduralMotion;
|
|
95
|
+
if (motion?.groupId)
|
|
96
|
+
groups.set(motion.groupId, child);
|
|
97
|
+
});
|
|
98
|
+
for (const light of item.procedural.lights) {
|
|
99
|
+
const motion = light.motionGroup ? groups.get(light.motionGroup) : undefined;
|
|
100
|
+
const local = new Vector3(...light.position);
|
|
101
|
+
// A nested group's position is relative to its parent group: the design-space pivot
|
|
102
|
+
// is the sum along its chain of motion groups.
|
|
103
|
+
for (let group = motion; group && group !== object; group = group.parent ?? undefined)
|
|
104
|
+
if (group.userData.proceduralMotion)
|
|
105
|
+
local.sub(group.position);
|
|
106
|
+
regs.push({
|
|
107
|
+
key: `${item.pascalId}:procedural:${light.id}`,
|
|
108
|
+
nodeId: item.pascalId,
|
|
109
|
+
object,
|
|
110
|
+
color: light.color,
|
|
111
|
+
distance: light.distance,
|
|
112
|
+
getWorldPosition: (out) => {
|
|
113
|
+
const anchor = motion ?? object;
|
|
114
|
+
anchor.updateWorldMatrix(true, false);
|
|
115
|
+
out.copy(local).applyMatrix4(anchor.matrixWorld);
|
|
116
|
+
},
|
|
117
|
+
getIntensity: () => light.intensity,
|
|
118
|
+
isOn: () => useInteractive.getState().procedural[item.pascalId]?.lightsOn ??
|
|
119
|
+
useInteractive.getState().lampDefault,
|
|
120
|
+
levelId: findLevelId(object),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const controls = item.interactive.controls;
|
|
125
|
+
const toggleIndex = controls.findIndex((control) => control.kind === 'toggle');
|
|
126
|
+
const sliderIndex = controls.findIndex((control) => control.kind === 'slider');
|
|
127
|
+
const slider = sliderIndex >= 0 ? controls[sliderIndex] : null;
|
|
128
|
+
item.interactive.effects.forEach((effect, index) => {
|
|
129
|
+
if (effect.kind !== 'light')
|
|
130
|
+
return;
|
|
131
|
+
regs.push({
|
|
132
|
+
key: `${item.pascalId}:${index}`,
|
|
133
|
+
nodeId: item.pascalId,
|
|
134
|
+
object,
|
|
135
|
+
color: effect.color,
|
|
136
|
+
distance: effect.distance ?? 0,
|
|
137
|
+
getWorldPosition: (out) => {
|
|
138
|
+
object.updateWorldMatrix(true, false);
|
|
139
|
+
if (item.scripted) {
|
|
140
|
+
const scale = item.localScale ?? [1, 1, 1];
|
|
141
|
+
out
|
|
142
|
+
.set(effect.offset[0] * scale[0], effect.offset[1] * scale[1], effect.offset[2] * scale[2])
|
|
143
|
+
.applyMatrix4(object.matrixWorld);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
object.getWorldPosition(out);
|
|
147
|
+
out.set(out.x + effect.offset[0], out.y + effect.offset[1], out.z + effect.offset[2]);
|
|
148
|
+
},
|
|
149
|
+
getIntensity: () => {
|
|
150
|
+
const values = useInteractive.getState().items[item.pascalId]?.controlValues;
|
|
151
|
+
const raw = slider ? (values?.[sliderIndex] ?? slider.min) : 1;
|
|
152
|
+
const fraction = slider && slider.max > slider.min ? (raw - slider.min) / (slider.max - slider.min) : 1;
|
|
153
|
+
return MathUtils.lerp(effect.intensityRange[0], effect.intensityRange[1], fraction);
|
|
154
|
+
},
|
|
155
|
+
isOn: () => {
|
|
156
|
+
const values = useInteractive.getState().items[item.pascalId]?.controlValues;
|
|
157
|
+
return toggleIndex < 0 || Boolean(values?.[toggleIndex]);
|
|
158
|
+
},
|
|
159
|
+
levelId: findLevelId(object),
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return regs;
|
|
164
|
+
}
|
|
37
165
|
const _itemPos = new Vector3();
|
|
38
166
|
/**
|
|
39
167
|
* Re-creates the item-driven interactivity the parametric viewer has — pooled
|
|
@@ -43,18 +171,26 @@ const _itemPos = new Vector3();
|
|
|
43
171
|
* the GLB itself, so the artifact stays integrator-clean.
|
|
44
172
|
*/
|
|
45
173
|
export function GlbInteractive({ items, identity, zones, actions, levelOrder, }) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
174
|
+
const scene = useThree((state) => state.scene);
|
|
175
|
+
useLayoutEffect(() => {
|
|
176
|
+
useItemLightPool.getState().setBakedCanvas(scene, true);
|
|
177
|
+
return () => useItemLightPool.getState().setBakedCanvas(scene, false);
|
|
178
|
+
}, [scene]);
|
|
179
|
+
// Baked catalog animation toggles start on; light toggles follow the current
|
|
180
|
+
// theme; an authored object's toggles keep their own defaults, as in the
|
|
181
|
+
// editor. Clear per-item state on unmount so it cannot carry into another scene.
|
|
51
182
|
useEffect(() => {
|
|
52
183
|
const store = useInteractive.getState();
|
|
53
184
|
for (const item of items) {
|
|
54
|
-
store.initItem(item.pascalId, item.interactive);
|
|
55
|
-
item.
|
|
56
|
-
|
|
57
|
-
|
|
185
|
+
store.initItem(item.pascalId, item.interactive, true);
|
|
186
|
+
if (item.scripted)
|
|
187
|
+
continue;
|
|
188
|
+
const lampIndex = item.interactive.effects.some((effect) => effect.kind === 'light')
|
|
189
|
+
? item.interactive.controls.findIndex((control) => control.kind === 'toggle')
|
|
190
|
+
: -1;
|
|
191
|
+
item.interactive.controls.forEach((control, index) => {
|
|
192
|
+
if (control.kind === 'toggle' && index !== lampIndex)
|
|
193
|
+
store.setControlValue(item.pascalId, index, true);
|
|
58
194
|
});
|
|
59
195
|
}
|
|
60
196
|
return () => {
|
|
@@ -64,40 +200,12 @@ export function GlbInteractive({ items, identity, zones, actions, levelOrder, })
|
|
|
64
200
|
};
|
|
65
201
|
}, [items]);
|
|
66
202
|
const animationItems = useMemo(() => items.filter((item) => item.interactive.effects.some((e) => e.kind === 'animation')), [items]);
|
|
67
|
-
|
|
68
|
-
// node. Fed to a fixed pool (below) rather than mounting a light per item.
|
|
69
|
-
const lightRegs = useMemo(() => {
|
|
70
|
-
const regs = [];
|
|
71
|
-
for (const item of items) {
|
|
72
|
-
const effect = item.interactive.effects.find((e) => e.kind === 'light');
|
|
73
|
-
if (!effect)
|
|
74
|
-
continue;
|
|
75
|
-
const object = identity.get(item.pascalId);
|
|
76
|
-
if (!object)
|
|
77
|
-
continue;
|
|
78
|
-
const controls = item.interactive.controls;
|
|
79
|
-
const toggleIndex = controls.findIndex((c) => c.kind === 'toggle');
|
|
80
|
-
const sliderIndex = controls.findIndex((c) => c.kind === 'slider');
|
|
81
|
-
const slider = sliderIndex >= 0 ? controls[sliderIndex] : null;
|
|
82
|
-
regs.push({
|
|
83
|
-
key: item.pascalId,
|
|
84
|
-
object,
|
|
85
|
-
effect,
|
|
86
|
-
toggleIndex,
|
|
87
|
-
sliderIndex,
|
|
88
|
-
hasSlider: !!slider,
|
|
89
|
-
sliderMin: slider?.min ?? 0,
|
|
90
|
-
sliderMax: slider?.max ?? 1,
|
|
91
|
-
levelId: findLevelId(object),
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
return regs;
|
|
95
|
-
}, [items, identity]);
|
|
203
|
+
const lightRegs = useMemo(() => buildGlbLightRegs(items, identity), [items, identity]);
|
|
96
204
|
const levelIndexById = useMemo(() => new Map(levelOrder.map((id, i) => [id, i])), [levelOrder]);
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
// item's world position can be point-tested regardless of level stacking.
|
|
205
|
+
// Project the zone's baked-local polygon into world space so focused zone
|
|
206
|
+
// membership still works after level stacking moves its parent.
|
|
100
207
|
const focusedZoneId = useViewer((s) => s.selection.zoneId);
|
|
208
|
+
const selectedIds = useViewer((s) => s.selection.selectedIds);
|
|
101
209
|
const worldPolygon = useMemo(() => {
|
|
102
210
|
if (!focusedZoneId)
|
|
103
211
|
return null;
|
|
@@ -105,16 +213,51 @@ export function GlbInteractive({ items, identity, zones, actions, levelOrder, })
|
|
|
105
213
|
if (!zone)
|
|
106
214
|
return null;
|
|
107
215
|
zone.node.updateWorldMatrix(true, false);
|
|
108
|
-
|
|
216
|
+
const project = (ring) => ring.map(([x, z]) => {
|
|
109
217
|
const v = new Vector3(x, 0, z).applyMatrix4(zone.node.matrixWorld);
|
|
110
218
|
return [v.x, v.z];
|
|
111
219
|
});
|
|
220
|
+
return { outer: project(zone.polygon), holes: (zone.holes ?? []).map(project) };
|
|
112
221
|
}, [focusedZoneId, zones]);
|
|
113
|
-
return (_jsxs(_Fragment, { children: [_jsx(GlbItemLights, { levelIndexById: levelIndexById, regs: lightRegs }), animationItems.map((item) => (_jsx(
|
|
222
|
+
return (_jsxs(_Fragment, { children: [_jsx(GlbItemLights, { levelIndexById: levelIndexById, regs: lightRegs }), animationItems.map((item) => item.scripted ? (_jsx(GlbScriptedClips, { actions: actions, item: item }, item.pascalId)) : (_jsx(GlbItemAnimation, { actions: actions, item: item }, item.pascalId))), items
|
|
223
|
+
.filter((item) => item.procedural?.lights.length)
|
|
224
|
+
.map((item) => {
|
|
225
|
+
const object = identity.get(item.pascalId);
|
|
226
|
+
return object ? (_jsx(GlbProceduralEmission, { item: item, object: object }, item.pascalId)) : null;
|
|
227
|
+
}), items
|
|
228
|
+
.filter((item) => worldPolygon || selectedIds.includes(item.pascalId))
|
|
229
|
+
.map((item) => {
|
|
114
230
|
const object = identity.get(item.pascalId);
|
|
115
|
-
return object ? (_jsx(GlbItemControls, { item: item, object: object, worldPolygon: worldPolygon }, item.pascalId)) : null;
|
|
231
|
+
return object ? (_jsx(GlbItemControls, { isSelected: selectedIds.includes(item.pascalId), item: item, object: object, worldPolygon: worldPolygon }, item.pascalId)) : null;
|
|
116
232
|
})] }));
|
|
117
233
|
}
|
|
234
|
+
function GlbProceduralEmission({ item, object }) {
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
const lights = item.procedural?.lights ?? [];
|
|
237
|
+
const restore = decorateProceduralEmission(object, lights, true);
|
|
238
|
+
const update = () => {
|
|
239
|
+
const on = useInteractive.getState().procedural[item.pascalId]?.lightsOn ??
|
|
240
|
+
useInteractive.getState().lampDefault;
|
|
241
|
+
const slots = new Set(lights.map((light) => light.emissiveSlot).filter(Boolean));
|
|
242
|
+
for (const mesh of proceduralSlotMeshes(object, slots)) {
|
|
243
|
+
const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
|
|
244
|
+
for (const material of materials)
|
|
245
|
+
setProceduralEmission(material, on);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
update();
|
|
249
|
+
const unsubscribe = useInteractive.subscribe((state, previous) => {
|
|
250
|
+
if (state.procedural[item.pascalId] !== previous.procedural[item.pascalId] ||
|
|
251
|
+
state.lampDefault !== previous.lampDefault)
|
|
252
|
+
update();
|
|
253
|
+
});
|
|
254
|
+
return () => {
|
|
255
|
+
unsubscribe();
|
|
256
|
+
restore();
|
|
257
|
+
};
|
|
258
|
+
}, [item, object]);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
118
261
|
// ── Pooled item lights ──────────────────────────────────────────────────────
|
|
119
262
|
//
|
|
120
263
|
// Mirrors the parametric `ItemLightSystem`: a fixed pool of point lights is
|
|
@@ -142,15 +285,21 @@ function findLevelId(object) {
|
|
|
142
285
|
}
|
|
143
286
|
return null;
|
|
144
287
|
}
|
|
145
|
-
function
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
288
|
+
function isRendered(object) {
|
|
289
|
+
let current = object;
|
|
290
|
+
while (current) {
|
|
291
|
+
if (!current.visible || !current.layers.isEnabled(SCENE_LAYER))
|
|
292
|
+
return false;
|
|
293
|
+
current = current.parent;
|
|
149
294
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
function scoreReg(reg, selectedLevelId, levelMode, levelIndexById) {
|
|
298
|
+
if (!reg.isOn() || !isRendered(reg.object))
|
|
299
|
+
return Number.POSITIVE_INFINITY;
|
|
300
|
+
if (selectedLevelId && reg.levelId !== selectedLevelId && levelMode === 'solo')
|
|
301
|
+
return Number.POSITIVE_INFINITY;
|
|
302
|
+
reg.getWorldPosition(_lightWorld);
|
|
154
303
|
_dir.copy(_lightWorld).sub(_camPos).normalize();
|
|
155
304
|
const angular = 1 - _camFwd.dot(_dir);
|
|
156
305
|
const dist = _camPos.distanceTo(_lightWorld) / 200;
|
|
@@ -173,7 +322,6 @@ function GlbItemLights({ regs, levelIndexById, }) {
|
|
|
173
322
|
const regByKey = useMemo(() => new Map(regs.map((r) => [r.key, r])), [regs]);
|
|
174
323
|
useFrame(({ camera }, delta) => {
|
|
175
324
|
const dt = Math.min(delta, 0.1);
|
|
176
|
-
const interactiveState = useInteractive.getState();
|
|
177
325
|
camera.getWorldPosition(_camPos);
|
|
178
326
|
camera.getWorldDirection(_camFwd);
|
|
179
327
|
const camMoved = _camPos.distanceTo(prevCamPos.current) > CAM_MOVE_DIST ||
|
|
@@ -188,7 +336,7 @@ function GlbItemLights({ regs, levelIndexById, }) {
|
|
|
188
336
|
const levelMode = viewer.levelMode;
|
|
189
337
|
const scored = regs.map((reg) => ({
|
|
190
338
|
key: reg.key,
|
|
191
|
-
score: scoreReg(reg, selectedLevelId, levelMode, levelIndexById
|
|
339
|
+
score: scoreReg(reg, selectedLevelId, levelMode, levelIndexById),
|
|
192
340
|
}));
|
|
193
341
|
scored.sort((a, b) => a.score - b.score);
|
|
194
342
|
const scoreByKey = new Map(scored.map((s) => [s.key, s.score]));
|
|
@@ -244,8 +392,8 @@ function GlbItemLights({ regs, levelIndexById, }) {
|
|
|
244
392
|
const light = lightRefs.current[freeSlot];
|
|
245
393
|
const reg = regByKey.get(key);
|
|
246
394
|
if (light && reg) {
|
|
247
|
-
light.color.set(reg.
|
|
248
|
-
light.distance = reg.
|
|
395
|
+
light.color.set(reg.color);
|
|
396
|
+
light.distance = reg.distance;
|
|
249
397
|
}
|
|
250
398
|
}
|
|
251
399
|
}
|
|
@@ -283,8 +431,8 @@ function GlbItemLights({ regs, levelIndexById, }) {
|
|
|
283
431
|
if (slot.key) {
|
|
284
432
|
const reg = regByKey.get(slot.key);
|
|
285
433
|
if (reg) {
|
|
286
|
-
light.color.set(reg.
|
|
287
|
-
light.distance = reg.
|
|
434
|
+
light.color.set(reg.color);
|
|
435
|
+
light.distance = reg.distance;
|
|
288
436
|
}
|
|
289
437
|
}
|
|
290
438
|
}
|
|
@@ -299,24 +447,12 @@ function GlbItemLights({ regs, levelIndexById, }) {
|
|
|
299
447
|
slot.key = null;
|
|
300
448
|
continue;
|
|
301
449
|
}
|
|
302
|
-
reg.
|
|
303
|
-
light.position.
|
|
304
|
-
const
|
|
305
|
-
const isOn = reg.toggleIndex >= 0 ? Boolean(values?.[reg.toggleIndex]) : true;
|
|
306
|
-
let t = 1;
|
|
307
|
-
if (reg.hasSlider) {
|
|
308
|
-
const raw = values?.[reg.sliderIndex] ?? reg.sliderMin;
|
|
309
|
-
t =
|
|
310
|
-
reg.sliderMax > reg.sliderMin
|
|
311
|
-
? (raw - reg.sliderMin) / (reg.sliderMax - reg.sliderMin)
|
|
312
|
-
: 1;
|
|
313
|
-
}
|
|
314
|
-
const targetIntensity = isOn
|
|
315
|
-
? MathUtils.lerp(reg.effect.intensityRange[0], reg.effect.intensityRange[1], t)
|
|
316
|
-
: reg.effect.intensityRange[0];
|
|
450
|
+
reg.getWorldPosition(_lightWorld);
|
|
451
|
+
light.position.copy(_lightWorld);
|
|
452
|
+
const targetIntensity = reg.isOn() && isRendered(reg.object) ? reg.getIntensity() : 0;
|
|
317
453
|
light.intensity = MathUtils.lerp(light.intensity, targetIntensity, dt * 12);
|
|
318
454
|
}
|
|
319
|
-
});
|
|
455
|
+
}, 6);
|
|
320
456
|
return (_jsx(_Fragment, { children: Array.from({ length: POOL_SIZE }, (_, i) => (_jsx("pointLight", { castShadow: false, intensity: 0, ref: (el) => {
|
|
321
457
|
lightRefs.current[i] = el;
|
|
322
458
|
} }, i))) }));
|
|
@@ -345,16 +481,47 @@ function GlbItemAnimation({ item, actions, }) {
|
|
|
345
481
|
}, [actions, item.pascalId, isOn]);
|
|
346
482
|
return null;
|
|
347
483
|
}
|
|
484
|
+
/** Plays an authored object's baked clips through the shared `ScriptedClips`,
|
|
485
|
+
* addressing each by the name the script gave it. */
|
|
486
|
+
function GlbScriptedClips({ item, actions, }) {
|
|
487
|
+
// drei creates each action on first read, once the scene root is mounted, so
|
|
488
|
+
// forward reads instead of copying the actions during render.
|
|
489
|
+
const scriptActions = useMemo(() => {
|
|
490
|
+
const prefix = `${item.pascalId}: `;
|
|
491
|
+
const forwarded = {};
|
|
492
|
+
for (const name of Object.keys(actions)) {
|
|
493
|
+
if (!name.startsWith(prefix))
|
|
494
|
+
continue;
|
|
495
|
+
Object.defineProperty(forwarded, name.slice(prefix.length), {
|
|
496
|
+
enumerable: true,
|
|
497
|
+
get: () => actions[name],
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return forwarded;
|
|
501
|
+
}, [actions, item.pascalId]);
|
|
502
|
+
return (_jsx(ScriptedClips, { actions: scriptActions, interactive: item.interactive, nodeId: item.pascalId }));
|
|
503
|
+
}
|
|
348
504
|
const FADE_MS = 300;
|
|
349
|
-
/** Controls overlay for
|
|
350
|
-
|
|
351
|
-
function GlbItemControls({ item, object, worldPolygon, }) {
|
|
505
|
+
/** Controls overlay for a selected item or one inside the focused zone. */
|
|
506
|
+
function GlbItemControls({ item, object, worldPolygon, isSelected, }) {
|
|
352
507
|
const controlValues = useInteractive(useShallow((s) => s.items[item.pascalId]?.controlValues));
|
|
508
|
+
const proceduralState = useInteractive((s) => s.procedural[item.pascalId]);
|
|
509
|
+
const lampDefault = useInteractive((s) => s.lampDefault);
|
|
353
510
|
const setControlValue = useInteractive((s) => s.setControlValue);
|
|
354
|
-
|
|
355
|
-
|
|
511
|
+
const togglePart = useInteractive((s) => s.toggleProceduralPart);
|
|
512
|
+
const toggleLights = useInteractive((s) => s.toggleProceduralLights);
|
|
513
|
+
const descriptors = item.procedural
|
|
514
|
+
? proceduralControlDescriptors(item.procedural.recipe ?? { parts: item.procedural.parts }, proceduralState, (partId) => togglePart(item.pascalId, partId), () => toggleLights(item.pascalId), lampDefault)
|
|
515
|
+
: item.interactive.controls.map((control, index) => ({
|
|
516
|
+
key: String(index),
|
|
517
|
+
control,
|
|
518
|
+
value: controlValues?.[index] ?? false,
|
|
519
|
+
onChange: (value) => setControlValue(item.pascalId, index, value),
|
|
520
|
+
}));
|
|
521
|
+
let visible = isSelected;
|
|
522
|
+
if (worldPolygon) {
|
|
356
523
|
object.getWorldPosition(_itemPos);
|
|
357
|
-
visible =
|
|
524
|
+
visible = visible || containsPoint([worldPolygon], [_itemPos.x, _itemPos.z]);
|
|
358
525
|
}
|
|
359
526
|
// Fade in on mount and fade out before unmounting the <Html>.
|
|
360
527
|
const [mounted, setMounted] = useState(false);
|
|
@@ -375,7 +542,7 @@ function GlbItemControls({ item, object, worldPolygon, }) {
|
|
|
375
542
|
const timeout = setTimeout(() => setMounted(false), FADE_MS);
|
|
376
543
|
return () => clearTimeout(timeout);
|
|
377
544
|
}, [visible]);
|
|
378
|
-
if (!(mounted &&
|
|
545
|
+
if (!(mounted && descriptors.length))
|
|
379
546
|
return null;
|
|
380
547
|
return createPortal(_jsx(Html, { center: true, distanceFactor: 8, eps: -1, position: [0, item.height + 0.3, 0], zIndexRange: [20, 0], children: _jsx("div", { onClick: (e) => e.stopPropagation(), onPointerDown: (e) => e.stopPropagation(), onPointerUp: (e) => e.stopPropagation(), style: {
|
|
381
548
|
display: 'flex',
|
|
@@ -390,5 +557,5 @@ function GlbItemControls({ item, object, worldPolygon, }) {
|
|
|
390
557
|
userSelect: 'none',
|
|
391
558
|
opacity: shown ? 1 : 0,
|
|
392
559
|
transition: `opacity ${FADE_MS}ms ease`,
|
|
393
|
-
}, children:
|
|
560
|
+
}, children: descriptors.map((descriptor) => (_jsx(ControlWidget, { control: descriptor.control, onChange: descriptor.onChange, value: descriptor.value }, descriptor.key))) }) }), object);
|
|
394
561
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Object3D } from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* Baked nodes whose loop clips no other controller plays (a plugin kind's
|
|
4
|
+
* mechanism, such as an articulated asset's joints), read from the GLB alone:
|
|
5
|
+
* `pascalId` → its `: loop` clip names. `owned` holds the nodes procedural
|
|
6
|
+
* playback or the scene graph's interactive items already drive; openables
|
|
7
|
+
* keep their open/close path.
|
|
8
|
+
*/
|
|
9
|
+
export declare function bakedLoopMechanisms(identity: ReadonlyMap<string, Object3D>, owned: ReadonlySet<string>): Map<string, string[]>;
|
|
10
|
+
//# sourceMappingURL=glb-mechanisms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glb-mechanisms.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-mechanisms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAErC;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,EACvC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GACzB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAWvB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baked nodes whose loop clips no other controller plays (a plugin kind's
|
|
3
|
+
* mechanism, such as an articulated asset's joints), read from the GLB alone:
|
|
4
|
+
* `pascalId` → its `: loop` clip names. `owned` holds the nodes procedural
|
|
5
|
+
* playback or the scene graph's interactive items already drive; openables
|
|
6
|
+
* keep their open/close path.
|
|
7
|
+
*/
|
|
8
|
+
export function bakedLoopMechanisms(identity, owned) {
|
|
9
|
+
const mechanisms = new Map();
|
|
10
|
+
for (const [id, object] of identity) {
|
|
11
|
+
const { clips, openable } = object.userData;
|
|
12
|
+
if (openable || owned.has(id) || !Array.isArray(clips))
|
|
13
|
+
continue;
|
|
14
|
+
const loops = clips.filter((clip) => typeof clip === 'string' && clip.endsWith(': loop'));
|
|
15
|
+
if (loops.length)
|
|
16
|
+
mechanisms.set(id, loops);
|
|
17
|
+
}
|
|
18
|
+
return mechanisms;
|
|
19
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { bakePolicyOf, isNodeKindEnabled, nodeRegistry, } from '@pascal-app/core';
|
|
4
4
|
import { createPortal } from '@react-three/fiber';
|
|
5
5
|
import { memo, Suspense } from 'react';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { nodeRegistry } from '@pascal-app/core';
|
|
4
4
|
import { createPortal } from '@react-three/fiber';
|
|
5
5
|
import { Fragment, lazy, memo, Suspense, useMemo } from 'react';
|
|
@@ -1,41 +1,57 @@
|
|
|
1
1
|
import { type AnyNode } from '@pascal-app/core';
|
|
2
|
+
import * as THREE from 'three';
|
|
2
3
|
import { type GlbInteractiveItem } from './glb-interactive';
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* A pointer on the baked building, handed to the host's selection rules: the
|
|
6
|
+
* baked node under it (its `pascalId`) and the hit in its level's local XZ,
|
|
7
|
+
* or null for empty space.
|
|
8
|
+
*/
|
|
9
|
+
export type GlbPickEvent = {
|
|
10
|
+
type: 'click' | 'hover';
|
|
11
|
+
pick: {
|
|
12
|
+
nodeId: string;
|
|
13
|
+
point: [number, number] | null;
|
|
14
|
+
} | null;
|
|
15
|
+
modifiers: {
|
|
16
|
+
alt: boolean;
|
|
17
|
+
ctrl: boolean;
|
|
18
|
+
meta: boolean;
|
|
19
|
+
shift: boolean;
|
|
20
|
+
};
|
|
7
21
|
};
|
|
8
|
-
/** pascalId → display info, reported so a host can label the breadcrumb. */
|
|
9
|
-
export type GlbIdentity = Record<string, {
|
|
10
|
-
kind: string;
|
|
11
|
-
label: string;
|
|
12
|
-
}>;
|
|
13
|
-
/** What the cursor would act on at the current drill depth (for a hover label). */
|
|
14
|
-
export type GlbHover = {
|
|
15
|
-
kind: string;
|
|
16
|
-
label: string;
|
|
17
|
-
} | null;
|
|
18
22
|
/** Walkthrough HUD state, reported each frame: the floor/room the camera is in
|
|
19
|
-
* and the
|
|
23
|
+
* and the interactive part directly in view (for the reticle prompt). */
|
|
20
24
|
export type GlbWalkthrough = {
|
|
21
25
|
zoneLabel: string | null;
|
|
22
26
|
floorLabel: string | null;
|
|
23
27
|
door: {
|
|
24
28
|
label: string;
|
|
25
29
|
isOpen: boolean;
|
|
30
|
+
verb?: string;
|
|
26
31
|
} | null;
|
|
27
32
|
} | null;
|
|
33
|
+
type HitCandidate = {
|
|
34
|
+
object: THREE.Object3D;
|
|
35
|
+
point: THREE.Vector3;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The baked node under a pointer that a click can land on: the nearest hit
|
|
39
|
+
* whose identity is not a kind that only frames (site, building, level, zone,
|
|
40
|
+
* ceiling) and is not on a hidden floor, with the hit in its level's local XZ.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveGlbPick(hits: readonly HitCandidate[], identity: ReadonlyMap<string, THREE.Object3D>): (NonNullable<GlbPickEvent['pick']> & {
|
|
43
|
+
hitObject: THREE.Object3D;
|
|
44
|
+
}) | null;
|
|
28
45
|
/**
|
|
29
|
-
* GLB-consuming viewer scene
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* `
|
|
36
|
-
* host disables the parametric `SelectionManager` (`selectionManager="custom"`).
|
|
46
|
+
* GLB-consuming viewer scene. Loads a baked artifact and drives the viewer's
|
|
47
|
+
* presentation and interaction with no parametric scene graph. A pointer
|
|
48
|
+
* becomes a `GlbPickEvent` the host runs through its selection rules (the
|
|
49
|
+
* same rules as the parametric viewer); the shared `useViewer.selection` and
|
|
50
|
+
* `hoveredId` then drive the outline, and openables play their baked clips
|
|
51
|
+
* when clicked. The host disables the parametric `SelectionManager`
|
|
52
|
+
* (`selectionManager="custom"`).
|
|
37
53
|
*/
|
|
38
|
-
export declare function GlbScene({ url, interactiveItems, referenceNodes, replaceNodes,
|
|
54
|
+
export declare function GlbScene({ url, interactiveItems, referenceNodes, replaceNodes, onObjectsChange, onPick, onWalkthroughChange, }: {
|
|
39
55
|
url: string;
|
|
40
56
|
/** Light / animation effects + controls recovered from the DB scene graph,
|
|
41
57
|
* joined to the baked nodes by `pascalId` to re-light + re-animate the GLB. */
|
|
@@ -46,9 +62,11 @@ export declare function GlbScene({ url, interactiveItems, referenceNodes, replac
|
|
|
46
62
|
/** `bake: 'replace'` nodes (e.g. plugin trees): baked static but re-rendered
|
|
47
63
|
* live here via their `bakeReplaceRenderer`; the baked meshes are hidden. */
|
|
48
64
|
replaceNodes?: AnyNode[];
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
/** pascalId → baked object, so the host can place room pills and frame nodes. */
|
|
66
|
+
onObjectsChange?: (objects: ReadonlyMap<string, THREE.Object3D>) => void;
|
|
67
|
+
/** Clicks and hovers on the building; without it the scene is look-only. */
|
|
68
|
+
onPick?: (event: GlbPickEvent) => void;
|
|
52
69
|
onWalkthroughChange?: (state: GlbWalkthrough) => void;
|
|
53
70
|
}): import("react").JSX.Element;
|
|
71
|
+
export {};
|
|
54
72
|
//# sourceMappingURL=glb-scene.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glb-scene.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-scene.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"glb-scene.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-scene.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAYb,MAAM,kBAAkB,CAAA;AAQzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAS9B,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAmB3E;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IAC/D,SAAS,EAAE;QAAE,GAAG,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAA;CAC1E,CAAA;AAKD;0EAC0E;AAC1E,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC/D,GAAG,IAAI,CAAA;AAoDR,KAAK,YAAY,GAAG;IAAE,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAA;CAAE,CAAA;AA0CpE;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,SAAS,YAAY,EAAE,EAC7B,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,GAC5C,CAAC,WAAW,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG;IAAE,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAA;CAAE,CAAC,GAAG,IAAI,CAkB5E;AASD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,MAAM,EACN,mBAAmB,GACpB,EAAE;IACD,GAAG,EAAE,MAAM,CAAA;IACX;oFACgF;IAChF,gBAAgB,CAAC,EAAE,kBAAkB,EAAE,CAAA;IACvC;mFAC+E;IAC/E,cAAc,CAAC,EAAE,OAAO,EAAE,CAAA;IAC1B;kFAC8E;IAC9E,YAAY,CAAC,EAAE,OAAO,EAAE,CAAA;IACxB,iFAAiF;IACjF,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAA;IACxE,4EAA4E;IAC5E,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IACtC,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;CACtD,+BA8xBA"}
|