@pascal-app/viewer 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/dist/components/error-boundary.d.ts +6 -0
  2. package/dist/components/error-boundary.d.ts.map +1 -1
  3. package/dist/components/error-boundary.js +6 -0
  4. package/dist/components/renderers/node-renderer.d.ts +5 -1
  5. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  6. package/dist/components/renderers/node-renderer.js +5 -2
  7. package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -1
  8. package/dist/components/renderers/parametric-node-renderer.js +7 -4
  9. package/dist/components/viewer/bvh-ecctrl.d.ts +77 -0
  10. package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -0
  11. package/dist/components/viewer/bvh-ecctrl.js +552 -0
  12. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  13. package/dist/components/viewer/frame-limiter.js +39 -4
  14. package/dist/components/viewer/glb-interactive.d.ts +41 -0
  15. package/dist/components/viewer/glb-interactive.d.ts.map +1 -0
  16. package/dist/components/viewer/glb-interactive.js +394 -0
  17. package/dist/components/viewer/glb-reference-nodes.d.ts +29 -0
  18. package/dist/components/viewer/glb-reference-nodes.d.ts.map +1 -0
  19. package/dist/components/viewer/glb-reference-nodes.js +80 -0
  20. package/dist/components/viewer/glb-replace-instances.d.ts +18 -0
  21. package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -0
  22. package/dist/components/viewer/glb-replace-instances.js +61 -0
  23. package/dist/components/viewer/glb-scene.d.ts +54 -0
  24. package/dist/components/viewer/glb-scene.d.ts.map +1 -0
  25. package/dist/components/viewer/glb-scene.js +920 -0
  26. package/dist/components/viewer/glb-walkthrough-controller.d.ts +12 -0
  27. package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -0
  28. package/dist/components/viewer/glb-walkthrough-controller.js +357 -0
  29. package/dist/components/viewer/index.d.ts +19 -2
  30. package/dist/components/viewer/index.d.ts.map +1 -1
  31. package/dist/components/viewer/index.js +104 -12
  32. package/dist/components/viewer/lights.d.ts.map +1 -1
  33. package/dist/components/viewer/lights.js +36 -5
  34. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  35. package/dist/components/viewer/perf-monitor.js +17 -2
  36. package/dist/components/viewer/post-processing.d.ts +7 -1
  37. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  38. package/dist/components/viewer/post-processing.js +179 -24
  39. package/dist/components/viewer/registered-systems.d.ts.map +1 -1
  40. package/dist/components/viewer/registered-systems.js +6 -4
  41. package/dist/components/viewer/scene-bvh.d.ts +2 -1
  42. package/dist/components/viewer/scene-bvh.d.ts.map +1 -1
  43. package/dist/components/viewer/scene-bvh.js +3 -0
  44. package/dist/components/viewer/scene-bvh.test.d.ts +2 -0
  45. package/dist/components/viewer/scene-bvh.test.d.ts.map +1 -0
  46. package/dist/components/viewer/scene-bvh.test.js +13 -0
  47. package/dist/components/viewer/scene-environment.d.ts +2 -0
  48. package/dist/components/viewer/scene-environment.d.ts.map +1 -0
  49. package/dist/components/viewer/scene-environment.js +79 -0
  50. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  51. package/dist/components/viewer/selection-manager.js +18 -10
  52. package/dist/components/viewer/walkthrough-controls.d.ts +1 -0
  53. package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -1
  54. package/dist/components/viewer/walkthrough-controls.js +20 -0
  55. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  56. package/dist/hooks/use-gltf-ktx2.js +3 -22
  57. package/dist/hooks/use-node-events.d.ts +1 -1
  58. package/dist/hooks/use-node-events.d.ts.map +1 -1
  59. package/dist/hooks/use-node-events.js +22 -14
  60. package/dist/index.d.ts +19 -8
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +22 -8
  63. package/dist/lib/backdrop.d.ts +33 -0
  64. package/dist/lib/backdrop.d.ts.map +1 -0
  65. package/dist/lib/backdrop.js +92 -0
  66. package/dist/lib/box-uv.d.ts +13 -0
  67. package/dist/lib/box-uv.d.ts.map +1 -0
  68. package/dist/lib/box-uv.js +32 -0
  69. package/dist/lib/csg-utils.d.ts +3 -9
  70. package/dist/lib/csg-utils.d.ts.map +1 -1
  71. package/dist/lib/csg-utils.js +65 -5
  72. package/dist/lib/csg-utils.test.d.ts +2 -0
  73. package/dist/lib/csg-utils.test.d.ts.map +1 -0
  74. package/dist/lib/csg-utils.test.js +45 -0
  75. package/dist/lib/edge-style.d.ts +1 -0
  76. package/dist/lib/edge-style.d.ts.map +1 -1
  77. package/dist/lib/edge-style.js +26 -6
  78. package/dist/lib/ink-edges.d.ts +1 -1
  79. package/dist/lib/ink-edges.d.ts.map +1 -1
  80. package/dist/lib/ink-edges.js +13 -7
  81. package/dist/lib/ktx2-loader.d.ts +43 -0
  82. package/dist/lib/ktx2-loader.d.ts.map +1 -0
  83. package/dist/lib/ktx2-loader.js +136 -0
  84. package/dist/lib/layers.d.ts +10 -0
  85. package/dist/lib/layers.d.ts.map +1 -1
  86. package/dist/lib/layers.js +10 -0
  87. package/dist/lib/materials.d.ts +14 -1
  88. package/dist/lib/materials.d.ts.map +1 -1
  89. package/dist/lib/materials.js +103 -26
  90. package/dist/lib/scene-themes.d.ts +1 -0
  91. package/dist/lib/scene-themes.d.ts.map +1 -1
  92. package/dist/lib/scene-themes.js +25 -16
  93. package/dist/lib/shadow-only.d.ts +4 -0
  94. package/dist/lib/shadow-only.d.ts.map +1 -0
  95. package/dist/lib/shadow-only.js +36 -0
  96. package/dist/lib/tsl-compat.d.ts +12 -0
  97. package/dist/lib/tsl-compat.d.ts.map +1 -0
  98. package/dist/lib/tsl-compat.js +13 -0
  99. package/dist/store/use-viewer.d.ts +60 -18
  100. package/dist/store/use-viewer.d.ts.map +1 -1
  101. package/dist/store/use-viewer.js +175 -3
  102. package/dist/store/use-viewer.test.d.ts +2 -0
  103. package/dist/store/use-viewer.test.d.ts.map +1 -0
  104. package/dist/store/use-viewer.test.js +42 -0
  105. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  106. package/dist/systems/ceiling/ceiling-system.js +4 -1
  107. package/dist/systems/door/door-animation.test.d.ts +2 -0
  108. package/dist/systems/door/door-animation.test.d.ts.map +1 -0
  109. package/dist/systems/door/door-animation.test.js +51 -0
  110. package/dist/systems/door/door-floor-alignment.test.d.ts +2 -0
  111. package/dist/systems/door/door-floor-alignment.test.d.ts.map +1 -0
  112. package/dist/systems/door/door-floor-alignment.test.js +50 -0
  113. package/dist/systems/door/door-system.d.ts +24 -0
  114. package/dist/systems/door/door-system.d.ts.map +1 -1
  115. package/dist/systems/door/door-system.js +494 -161
  116. package/dist/systems/fence/fence-system.d.ts +17 -0
  117. package/dist/systems/fence/fence-system.d.ts.map +1 -1
  118. package/dist/systems/fence/fence-system.js +221 -53
  119. package/dist/systems/geometry/geometry-system.d.ts +7 -0
  120. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  121. package/dist/systems/geometry/geometry-system.js +95 -26
  122. package/dist/systems/geometry/geometry-system.test.d.ts +2 -0
  123. package/dist/systems/geometry/geometry-system.test.d.ts.map +1 -0
  124. package/dist/systems/geometry/geometry-system.test.js +30 -0
  125. package/dist/systems/interactive/control-widget.d.ts +10 -0
  126. package/dist/systems/interactive/control-widget.d.ts.map +1 -0
  127. package/dist/systems/interactive/control-widget.js +35 -0
  128. package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
  129. package/dist/systems/interactive/interactive-system.js +55 -56
  130. package/dist/systems/item/item-system.d.ts.map +1 -1
  131. package/dist/systems/item/item-system.js +20 -6
  132. package/dist/systems/level/level-system.d.ts.map +1 -1
  133. package/dist/systems/level/level-system.js +28 -4
  134. package/dist/systems/level/level-utils.d.ts +0 -3
  135. package/dist/systems/level/level-utils.d.ts.map +1 -1
  136. package/dist/systems/level/level-utils.js +2 -41
  137. package/dist/systems/roof/roof-materials.d.ts.map +1 -1
  138. package/dist/systems/roof/roof-materials.js +29 -8
  139. package/dist/systems/roof/roof-system.d.ts +21 -8
  140. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  141. package/dist/systems/roof/roof-system.js +1079 -221
  142. package/dist/systems/slab/slab-system.d.ts +5 -3
  143. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  144. package/dist/systems/slab/slab-system.js +41 -23
  145. package/dist/systems/slab/slab-system.test.js +3 -2
  146. package/dist/systems/stair/stair-system.js +6 -3
  147. package/dist/systems/wall/opening-cutout-geometry.d.ts +31 -0
  148. package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -0
  149. package/dist/systems/wall/opening-cutout-geometry.js +167 -0
  150. package/dist/systems/wall/opening-cutout-geometry.test.d.ts +2 -0
  151. package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +1 -0
  152. package/dist/systems/wall/opening-cutout-geometry.test.js +172 -0
  153. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  154. package/dist/systems/wall/wall-cutout.js +46 -17
  155. package/dist/systems/wall/wall-materials.d.ts +11 -7
  156. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  157. package/dist/systems/wall/wall-materials.js +244 -35
  158. package/dist/systems/wall/wall-opening-cutout.test.d.ts +2 -0
  159. package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +1 -0
  160. package/dist/systems/wall/wall-opening-cutout.test.js +59 -0
  161. package/dist/systems/wall/wall-support-extension.test.d.ts +2 -0
  162. package/dist/systems/wall/wall-support-extension.test.d.ts.map +1 -0
  163. package/dist/systems/wall/wall-support-extension.test.js +48 -0
  164. package/dist/systems/wall/wall-system.d.ts +2 -2
  165. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  166. package/dist/systems/wall/wall-system.js +329 -174
  167. package/dist/systems/window/window-animation-system.d.ts +12 -0
  168. package/dist/systems/window/window-animation-system.d.ts.map +1 -1
  169. package/dist/systems/window/window-animation-system.js +16 -5
  170. package/dist/systems/window/window-system.d.ts +7 -0
  171. package/dist/systems/window/window-system.d.ts.map +1 -1
  172. package/dist/systems/window/window-system.js +240 -14
  173. package/package.json +4 -4
  174. package/dist/components/viewer/ground-occluder.d.ts +0 -2
  175. package/dist/components/viewer/ground-occluder.d.ts.map +0 -1
  176. package/dist/components/viewer/ground-occluder.js +0 -67
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glb-interactive.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-interactive.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,WAAW,EAGhB,KAAK,UAAU,EAGhB,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EACL,KAAK,eAAe,EAGpB,KAAK,QAAQ,EAGd,MAAM,OAAO,CAAA;AAKd;;kFAEkF;AAClF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,SAAS,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,WAAW,CAAA;CACzB,CAAA;AAED,8EAA8E;AAC9E,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAA;CAC5B,CAAA;AAED;;4CAE4C;AAC5C,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GACxC,kBAAkB,EAAE,CAuBtB;AAID;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,QAAQ,EACR,KAAK,EACL,OAAO,EACP,UAAU,GACX,EAAE;IACD,KAAK,EAAE,kBAAkB,EAAE,CAAA;IAC3B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAC/B,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB;8BAC0B;IAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC,CAAA;IAC/C;gFAC4E;IAC5E,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB,+BA6FA"}
@@ -0,0 +1,394 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { pointInPolygon, useInteractive, } from '@pascal-app/core';
4
+ import { Html } from '@react-three/drei';
5
+ import { createPortal, useFrame } from '@react-three/fiber';
6
+ import { useEffect, useMemo, useRef, useState } from 'react';
7
+ import { LoopRepeat, MathUtils, Vector3, } from 'three';
8
+ import { useShallow } from 'zustand/react/shallow';
9
+ import useViewer from '../../store/use-viewer';
10
+ import { ControlWidget } from '../../systems/interactive/control-widget';
11
+ /** Pull the interactive items out of a scene graph. Only items that actually
12
+ * carry effects (light / animation) are returned — everything else baked
13
+ * faithfully and needs no runtime help. */
14
+ export function buildGlbInteractiveItems(sceneGraph) {
15
+ const nodes = sceneGraph?.nodes;
16
+ if (!nodes)
17
+ return [];
18
+ const items = [];
19
+ for (const [id, raw] of Object.entries(nodes)) {
20
+ const node = raw;
21
+ if (node?.type !== 'item')
22
+ continue;
23
+ const interactive = node.asset?.interactive;
24
+ if (!interactive?.effects?.length)
25
+ continue;
26
+ const dims = node.asset?.dimensions ?? [1, 1, 1];
27
+ const scaleY = node.scale?.[1] ?? 1;
28
+ items.push({
29
+ pascalId: id,
30
+ label: node.asset?.name ?? id,
31
+ height: (dims[1] ?? 1) * scaleY,
32
+ interactive,
33
+ });
34
+ }
35
+ return items;
36
+ }
37
+ const _itemPos = new Vector3();
38
+ /**
39
+ * Re-creates the item-driven interactivity the parametric viewer has — pooled
40
+ * lights, ambient animation, and the controls overlay — on top of a baked GLB.
41
+ * Effects come from the DB scene graph (`items`); world transforms come from the
42
+ * baked Object3Ds (`identity`), joined on `pascalId`. Nothing is stamped into
43
+ * the GLB itself, so the artifact stays integrator-clean.
44
+ */
45
+ export function GlbInteractive({ items, identity, zones, actions, levelOrder, }) {
46
+ // Seed control state for every interactive item. The viewer shows a baked
47
+ // scene "lit": toggles default ON (the editor defaults them off) and sliders
48
+ // to their authored default, so lamps glow and fans spin on load. Explicit
49
+ // overlay toggles then win. Cleared on unmount so the global store never
50
+ // carries state across scenes.
51
+ useEffect(() => {
52
+ const store = useInteractive.getState();
53
+ for (const item of items) {
54
+ store.initItem(item.pascalId, item.interactive);
55
+ item.interactive.controls.forEach((control, i) => {
56
+ if (control.kind === 'toggle')
57
+ store.setControlValue(item.pascalId, i, true);
58
+ });
59
+ }
60
+ return () => {
61
+ const store = useInteractive.getState();
62
+ for (const item of items)
63
+ store.removeItem(item.pascalId);
64
+ };
65
+ }, [items]);
66
+ const animationItems = useMemo(() => items.filter((item) => item.interactive.effects.some((e) => e.kind === 'animation')), [items]);
67
+ // Light registrations: one per item with a light effect, joined to its baked
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]);
96
+ const levelIndexById = useMemo(() => new Map(levelOrder.map((id, i) => [id, i])), [levelOrder]);
97
+ // Controls overlay is scoped to the focused zone (matches the parametric
98
+ // viewer). Project the zone's baked-local polygon into world space once so an
99
+ // item's world position can be point-tested regardless of level stacking.
100
+ const focusedZoneId = useViewer((s) => s.selection.zoneId);
101
+ const worldPolygon = useMemo(() => {
102
+ if (!focusedZoneId)
103
+ return null;
104
+ const zone = zones.find((z) => z.id === focusedZoneId);
105
+ if (!zone)
106
+ return null;
107
+ zone.node.updateWorldMatrix(true, false);
108
+ return zone.polygon.map(([x, z]) => {
109
+ const v = new Vector3(x, 0, z).applyMatrix4(zone.node.matrixWorld);
110
+ return [v.x, v.z];
111
+ });
112
+ }, [focusedZoneId, zones]);
113
+ return (_jsxs(_Fragment, { children: [_jsx(GlbItemLights, { levelIndexById: levelIndexById, regs: lightRegs }), animationItems.map((item) => (_jsx(GlbItemAnimation, { actions: actions, item: item }, item.pascalId))), items.map((item) => {
114
+ const object = identity.get(item.pascalId);
115
+ return object ? (_jsx(GlbItemControls, { item: item, object: object, worldPolygon: worldPolygon }, item.pascalId)) : null;
116
+ })] }));
117
+ }
118
+ // ── Pooled item lights ──────────────────────────────────────────────────────
119
+ //
120
+ // Mirrors the parametric `ItemLightSystem`: a fixed pool of point lights is
121
+ // assigned to the nearest/most-visible lit items each tick (camera-proximity
122
+ // scored, with hysteresis), snapped to the item's world position + offset, and
123
+ // faded in/out on reassignment. Mounting a light per item instead would blow
124
+ // the renderer's light budget on a large house.
125
+ const POOL_SIZE = 12;
126
+ const REASSIGN_INTERVAL = 0.2;
127
+ const HYSTERESIS = 0.15;
128
+ const CAM_MOVE_DIST = 0.5;
129
+ const CAM_ROT_DOT = 0.995;
130
+ const _camPos = new Vector3();
131
+ const _camFwd = new Vector3();
132
+ const _dir = new Vector3();
133
+ const _lightWorld = new Vector3();
134
+ /** The nearest level-identity ancestor's pascalId, for the level factor. */
135
+ function findLevelId(object) {
136
+ let cur = object;
137
+ while (cur) {
138
+ const ud = cur.userData;
139
+ if (ud.kind === 'level' && ud.pascalId)
140
+ return ud.pascalId;
141
+ cur = cur.parent;
142
+ }
143
+ return null;
144
+ }
145
+ function scoreReg(reg, selectedLevelId, levelMode, levelIndexById, interactiveState) {
146
+ // Toggled-off lights contribute no illumination — drop them from the pool.
147
+ if (reg.toggleIndex >= 0 && !interactiveState.items[reg.key]?.controlValues?.[reg.toggleIndex]) {
148
+ return Number.POSITIVE_INFINITY;
149
+ }
150
+ reg.object.getWorldPosition(_lightWorld);
151
+ _lightWorld.x += reg.effect.offset[0];
152
+ _lightWorld.y += reg.effect.offset[1];
153
+ _lightWorld.z += reg.effect.offset[2];
154
+ _dir.copy(_lightWorld).sub(_camPos).normalize();
155
+ const angular = 1 - _camFwd.dot(_dir);
156
+ const dist = _camPos.distanceTo(_lightWorld) / 200;
157
+ let levelPenalty = 0;
158
+ if (selectedLevelId) {
159
+ if (reg.levelId !== selectedLevelId)
160
+ levelPenalty = levelMode === 'solo' ? 100 : 0.8;
161
+ }
162
+ else if (reg.levelId && (levelIndexById.get(reg.levelId) ?? 0) !== 0) {
163
+ levelPenalty = 0.3;
164
+ }
165
+ return angular * 0.7 + dist * 0.3 + levelPenalty;
166
+ }
167
+ function GlbItemLights({ regs, levelIndexById, }) {
168
+ const lightRefs = useRef(Array.from({ length: POOL_SIZE }, () => null));
169
+ const slots = useRef(Array.from({ length: POOL_SIZE }, () => ({ key: null, pendingKey: null, isFadingOut: false })));
170
+ const reassignTimer = useRef(0);
171
+ const prevCamPos = useRef(new Vector3());
172
+ const prevCamFwd = useRef(new Vector3(0, 0, -1));
173
+ const regByKey = useMemo(() => new Map(regs.map((r) => [r.key, r])), [regs]);
174
+ useFrame(({ camera }, delta) => {
175
+ const dt = Math.min(delta, 0.1);
176
+ const interactiveState = useInteractive.getState();
177
+ camera.getWorldPosition(_camPos);
178
+ camera.getWorldDirection(_camFwd);
179
+ const camMoved = _camPos.distanceTo(prevCamPos.current) > CAM_MOVE_DIST ||
180
+ _camFwd.dot(prevCamFwd.current) < CAM_ROT_DOT;
181
+ reassignTimer.current -= delta;
182
+ if (reassignTimer.current <= 0 || camMoved) {
183
+ reassignTimer.current = REASSIGN_INTERVAL;
184
+ prevCamPos.current.copy(_camPos);
185
+ prevCamFwd.current.copy(_camFwd);
186
+ const viewer = useViewer.getState();
187
+ const selectedLevelId = viewer.selection.levelId;
188
+ const levelMode = viewer.levelMode;
189
+ const scored = regs.map((reg) => ({
190
+ key: reg.key,
191
+ score: scoreReg(reg, selectedLevelId, levelMode, levelIndexById, interactiveState),
192
+ }));
193
+ scored.sort((a, b) => a.score - b.score);
194
+ const scoreByKey = new Map(scored.map((s) => [s.key, s.score]));
195
+ const desired = scored
196
+ .filter((s) => Number.isFinite(s.score))
197
+ .slice(0, POOL_SIZE)
198
+ .map((s) => s.key);
199
+ const currentlyAssigned = new Map();
200
+ for (let i = 0; i < POOL_SIZE; i++) {
201
+ const s = slots.current[i];
202
+ const k = s?.key ?? s?.pendingKey;
203
+ if (k)
204
+ currentlyAssigned.set(k, i);
205
+ }
206
+ const usedSlots = new Set();
207
+ const assignedKeys = new Set();
208
+ // Pass 1: keep existing slots whose key is still wanted.
209
+ for (const key of desired) {
210
+ const existingSlot = currentlyAssigned.get(key);
211
+ if (existingSlot !== undefined && !usedSlots.has(existingSlot)) {
212
+ usedSlots.add(existingSlot);
213
+ assignedKeys.add(key);
214
+ }
215
+ }
216
+ // Pass 2: assign the rest to free slots, evicting only on a clear win.
217
+ let freeSlot = 0;
218
+ for (const key of desired) {
219
+ if (assignedKeys.has(key))
220
+ continue;
221
+ while (freeSlot < POOL_SIZE && usedSlots.has(freeSlot))
222
+ freeSlot++;
223
+ if (freeSlot >= POOL_SIZE)
224
+ break;
225
+ const freeSlotData = slots.current[freeSlot];
226
+ const currentKey = freeSlotData ? (freeSlotData.key ?? freeSlotData.pendingKey) : null;
227
+ if (currentKey && !desired.includes(currentKey)) {
228
+ const currentScore = scoreByKey.get(currentKey) ?? Number.POSITIVE_INFINITY;
229
+ const newScore = scoreByKey.get(key) ?? 0;
230
+ if (currentScore - newScore < HYSTERESIS) {
231
+ freeSlot++;
232
+ continue;
233
+ }
234
+ }
235
+ usedSlots.add(freeSlot);
236
+ assignedKeys.add(key);
237
+ const slot = slots.current[freeSlot];
238
+ if (slot && slot.key !== key) {
239
+ slot.pendingKey = key;
240
+ slot.isFadingOut = slot.key !== null;
241
+ if (!slot.isFadingOut) {
242
+ slot.key = key;
243
+ slot.pendingKey = null;
244
+ const light = lightRefs.current[freeSlot];
245
+ const reg = regByKey.get(key);
246
+ if (light && reg) {
247
+ light.color.set(reg.effect.color);
248
+ light.distance = reg.effect.distance ?? 0;
249
+ }
250
+ }
251
+ }
252
+ freeSlot++;
253
+ }
254
+ // Retire slots whose key is no longer wanted.
255
+ for (let i = 0; i < POOL_SIZE; i++) {
256
+ if (!usedSlots.has(i)) {
257
+ const slot = slots.current[i];
258
+ if (slot?.key && !desired.includes(slot.key)) {
259
+ slot.pendingKey = null;
260
+ slot.isFadingOut = true;
261
+ }
262
+ }
263
+ }
264
+ }
265
+ // Per-frame: fade, snap position, and track intensity from control state.
266
+ // The pool lights stay permanently `visible` — only `intensity` is animated
267
+ // (an idle light just lerps to 0). Toggling `visible` would change the
268
+ // active-light count, which forces the WebGPU renderer to recompile every
269
+ // material's lighting node — a hard frame-time spike on every reassignment
270
+ // (i.e. on every camera move). Keeping the count fixed avoids that entirely.
271
+ for (let i = 0; i < POOL_SIZE; i++) {
272
+ const light = lightRefs.current[i];
273
+ const slot = slots.current[i];
274
+ if (!(light && slot))
275
+ continue;
276
+ if (slot.isFadingOut) {
277
+ light.intensity = MathUtils.lerp(light.intensity, 0, dt * 12);
278
+ if (light.intensity < 0.01) {
279
+ light.intensity = 0;
280
+ slot.isFadingOut = false;
281
+ slot.key = slot.pendingKey;
282
+ slot.pendingKey = null;
283
+ if (slot.key) {
284
+ const reg = regByKey.get(slot.key);
285
+ if (reg) {
286
+ light.color.set(reg.effect.color);
287
+ light.distance = reg.effect.distance ?? 0;
288
+ }
289
+ }
290
+ }
291
+ continue;
292
+ }
293
+ if (!slot.key) {
294
+ light.intensity = MathUtils.lerp(light.intensity, 0, dt * 12);
295
+ continue;
296
+ }
297
+ const reg = regByKey.get(slot.key);
298
+ if (!reg) {
299
+ slot.key = null;
300
+ continue;
301
+ }
302
+ reg.object.getWorldPosition(_lightWorld);
303
+ light.position.set(_lightWorld.x + reg.effect.offset[0], _lightWorld.y + reg.effect.offset[1], _lightWorld.z + reg.effect.offset[2]);
304
+ const values = interactiveState.items[reg.key]?.controlValues;
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];
317
+ light.intensity = MathUtils.lerp(light.intensity, targetIntensity, dt * 12);
318
+ }
319
+ });
320
+ return (_jsx(_Fragment, { children: Array.from({ length: POOL_SIZE }, (_, i) => (_jsx("pointLight", { castShadow: false, intensity: 0, ref: (el) => {
321
+ lightRefs.current[i] = el;
322
+ } }, i))) }));
323
+ }
324
+ /** Plays an item's baked ambient loop (a fan's spin), gated on its toggle.
325
+ * The clip and its targets are already in the GLB; we only start/stop it. */
326
+ function GlbItemAnimation({ item, actions, }) {
327
+ const values = useInteractive(useShallow((s) => s.items[item.pascalId]?.controlValues));
328
+ const toggleIndex = item.interactive.controls.findIndex((c) => c.kind === 'toggle');
329
+ const isOn = toggleIndex >= 0 ? Boolean(values?.[toggleIndex] ?? true) : true;
330
+ useEffect(() => {
331
+ const action = actions[`${item.pascalId}: loop`];
332
+ if (!action)
333
+ return;
334
+ action.loop = LoopRepeat;
335
+ action.clampWhenFinished = false;
336
+ if (isOn) {
337
+ action.enabled = true;
338
+ action.paused = false;
339
+ if (!action.isRunning())
340
+ action.play();
341
+ }
342
+ else {
343
+ action.stop();
344
+ }
345
+ }, [actions, item.pascalId, isOn]);
346
+ return null;
347
+ }
348
+ const FADE_MS = 300;
349
+ /** Controls overlay for one item — fades in while the item sits inside the
350
+ * focused zone, portaled above the baked node. */
351
+ function GlbItemControls({ item, object, worldPolygon, }) {
352
+ const controlValues = useInteractive(useShallow((s) => s.items[item.pascalId]?.controlValues));
353
+ const setControlValue = useInteractive((s) => s.setControlValue);
354
+ let visible = false;
355
+ if (worldPolygon?.length) {
356
+ object.getWorldPosition(_itemPos);
357
+ visible = pointInPolygon(_itemPos.x, _itemPos.z, worldPolygon);
358
+ }
359
+ // Fade in on mount and fade out before unmounting the <Html>.
360
+ const [mounted, setMounted] = useState(false);
361
+ const [shown, setShown] = useState(false);
362
+ useEffect(() => {
363
+ if (visible) {
364
+ setMounted(true);
365
+ let raf2 = 0;
366
+ const raf1 = requestAnimationFrame(() => {
367
+ raf2 = requestAnimationFrame(() => setShown(true));
368
+ });
369
+ return () => {
370
+ cancelAnimationFrame(raf1);
371
+ cancelAnimationFrame(raf2);
372
+ };
373
+ }
374
+ setShown(false);
375
+ const timeout = setTimeout(() => setMounted(false), FADE_MS);
376
+ return () => clearTimeout(timeout);
377
+ }, [visible]);
378
+ if (!(mounted && controlValues))
379
+ return null;
380
+ 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
+ display: 'flex',
382
+ flexDirection: 'column',
383
+ gap: 6,
384
+ background: 'rgba(0,0,0,0.75)',
385
+ backdropFilter: 'blur(8px)',
386
+ borderRadius: 8,
387
+ padding: '8px 12px',
388
+ minWidth: 120,
389
+ pointerEvents: visible ? 'auto' : 'none',
390
+ userSelect: 'none',
391
+ opacity: shown ? 1 : 0,
392
+ transition: `opacity ${FADE_MS}ms ease`,
393
+ }, children: item.interactive.controls.map((control, i) => (_jsx(ControlWidget, { control: control, onChange: (v) => setControlValue(item.pascalId, i, v), value: controlValues[i] ?? false }, i))) }) }), object);
394
+ }
@@ -0,0 +1,29 @@
1
+ import { type AnyNode, type SceneGraph } from '@pascal-app/core';
2
+ import type { Object3D } from 'three';
3
+ /**
4
+ * Kinds with `def.bake === 'strip'` (scans/LiDAR, guides/floorplan images) are
5
+ * excluded from the baked GLB — heavy reference assets stored elsewhere. The GLB
6
+ * viewer re-adds them at runtime from the scene graph, portaled into their parent
7
+ * level's baked node so they ride level stacking, using the same registry
8
+ * renderers as the parametric viewer. Selection is registry-driven; scan/guide
9
+ * privacy is enforced upstream (`show_*_public`), so a disallowed asset is never
10
+ * even fetched — we still honour the flags here as a second gate.
11
+ */
12
+ export declare function buildGlbReferenceNodes(sceneGraph: SceneGraph | null | undefined, allow: {
13
+ scans: boolean;
14
+ guides: boolean;
15
+ }): AnyNode[];
16
+ /**
17
+ * Kinds with `def.bake === 'replace'` — baked as static geometry (so plain glTF
18
+ * viewers still show them) but re-rendered live here, since their runtime look
19
+ * differs from a frozen snapshot (shader wind, interactivity). `GlbScene` hides
20
+ * the baked meshes for these kinds; this feeds them back through the same
21
+ * portal-into-level path as reference nodes. Registry-driven; no privacy gate
22
+ * (dynamic scene content, not user uploads).
23
+ */
24
+ export declare function buildGlbReplaceNodes(sceneGraph: SceneGraph | null | undefined): AnyNode[];
25
+ export declare const GlbReferenceNodes: import("react").MemoExoticComponent<({ nodes, identity, }: {
26
+ nodes: AnyNode[];
27
+ identity: Map<string, Object3D>;
28
+ }) => import("react").JSX.Element>;
29
+ //# sourceMappingURL=glb-reference-nodes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glb-reference-nodes.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-reference-nodes.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAKZ,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAGrC;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,EACzC,KAAK,EAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GACzC,OAAO,EAAE,CAaX;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,EAAE,CAUzF;AAQD,eAAO,MAAM,iBAAiB,6DAG3B;IACD,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CAChC,iCASC,CAAA"}
@@ -0,0 +1,80 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { bakePolicyOf, isNodeKindEnabled, nodeRegistry, } from '@pascal-app/core';
4
+ import { createPortal } from '@react-three/fiber';
5
+ import { memo, Suspense } from 'react';
6
+ import { getRegistryRenderer } from '../renderers/node-renderer';
7
+ /**
8
+ * Kinds with `def.bake === 'strip'` (scans/LiDAR, guides/floorplan images) are
9
+ * excluded from the baked GLB — heavy reference assets stored elsewhere. The GLB
10
+ * viewer re-adds them at runtime from the scene graph, portaled into their parent
11
+ * level's baked node so they ride level stacking, using the same registry
12
+ * renderers as the parametric viewer. Selection is registry-driven; scan/guide
13
+ * privacy is enforced upstream (`show_*_public`), so a disallowed asset is never
14
+ * even fetched — we still honour the flags here as a second gate.
15
+ */
16
+ export function buildGlbReferenceNodes(sceneGraph, allow) {
17
+ const nodes = sceneGraph?.nodes;
18
+ if (!nodes)
19
+ return [];
20
+ const out = [];
21
+ for (const raw of Object.values(nodes)) {
22
+ const node = raw;
23
+ if (!isNodeKindEnabled(node.type, sceneGraph?.installedPlugins))
24
+ continue;
25
+ if (bakePolicyOf(node.type) !== 'strip')
26
+ continue;
27
+ if (node.type === 'scan' && !allow.scans)
28
+ continue;
29
+ if (node.type === 'guide' && !allow.guides)
30
+ continue;
31
+ out.push(node);
32
+ }
33
+ return out;
34
+ }
35
+ /**
36
+ * Kinds with `def.bake === 'replace'` — baked as static geometry (so plain glTF
37
+ * viewers still show them) but re-rendered live here, since their runtime look
38
+ * differs from a frozen snapshot (shader wind, interactivity). `GlbScene` hides
39
+ * the baked meshes for these kinds; this feeds them back through the same
40
+ * portal-into-level path as reference nodes. Registry-driven; no privacy gate
41
+ * (dynamic scene content, not user uploads).
42
+ */
43
+ export function buildGlbReplaceNodes(sceneGraph) {
44
+ const nodes = sceneGraph?.nodes;
45
+ if (!nodes)
46
+ return [];
47
+ const out = [];
48
+ for (const raw of Object.values(nodes)) {
49
+ const node = raw;
50
+ if (!isNodeKindEnabled(node.type, sceneGraph?.installedPlugins))
51
+ continue;
52
+ if (bakePolicyOf(node.type) === 'replace')
53
+ out.push(node);
54
+ }
55
+ return out;
56
+ }
57
+ // Memoized: `GlbScene` re-renders every frame during camera movement (hover
58
+ // raycast, walkthrough HUD). Without memo, all rebuilt nodes reconcile each
59
+ // frame — negligible for one or two scans/guides, but a `replace` kind can put
60
+ // dozens of nodes here (e.g. a forest), so each frame reconciles dozens of
61
+ // portals + submeshes. `nodes` and `identity` are stable references (page-level
62
+ // `useState` / `useMemo([gltf.scene])`), so memo short-circuits cleanly.
63
+ export const GlbReferenceNodes = memo(function GlbReferenceNodes({ nodes, identity, }) {
64
+ return (_jsx(_Fragment, { children: nodes.map((node) => {
65
+ const anchor = node.parentId ? identity.get(node.parentId) : undefined;
66
+ return anchor ? _jsx(GlbReferenceNode, { anchor: anchor, node: node }, node.id) : null;
67
+ }) }));
68
+ });
69
+ /** Render one `strip` node (scan/guide) via its registry `renderer`, portaled
70
+ * into its parent level's baked Object3D (so the node's level-local transform
71
+ * resolves to the same world pose as the parametric scene). Memoized so an
72
+ * unchanged `(node, anchor)` skips the subtree when the parent re-renders on
73
+ * camera move. (`replace` kinds use the collective `GlbReplaceInstances` path.) */
74
+ const GlbReferenceNode = memo(function GlbReferenceNode({ node, anchor, }) {
75
+ const source = nodeRegistry.get(node.type)?.renderer;
76
+ const Renderer = source ? getRegistryRenderer(source) : null;
77
+ if (!Renderer)
78
+ return null;
79
+ return createPortal(_jsx(Suspense, { fallback: null, children: _jsx(Renderer, { node: node }) }), anchor);
80
+ });
@@ -0,0 +1,18 @@
1
+ import { type AnyNode } from '@pascal-app/core';
2
+ import type { Object3D } from 'three';
3
+ /**
4
+ * Re-renders `bake: 'replace'` kinds (e.g. plugin trees) live over the baked GLB.
5
+ * The baked static meshes are hidden by `GlbScene`; these nodes are grouped by
6
+ * `(parent level, kind)` and each group is handed to the kind's collective
7
+ * `bakeReplaceRenderer` (an instanced renderer), portaled into that level's baked
8
+ * `Object3D`. Local-space instances therefore ride level stacking/explode for
9
+ * free, and a forest stays a few instanced draw calls.
10
+ *
11
+ * Memoized: `GlbScene` re-renders each frame on camera move; `nodes` and
12
+ * `identity` are stable refs, so this whole subtree short-circuits.
13
+ */
14
+ export declare const GlbReplaceInstances: import("react").MemoExoticComponent<({ nodes, identity, }: {
15
+ nodes: AnyNode[];
16
+ identity: Map<string, Object3D>;
17
+ }) => import("react").JSX.Element>;
18
+ //# sourceMappingURL=glb-replace-instances.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glb-replace-instances.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-replace-instances.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAA0C,MAAM,kBAAkB,CAAA;AAGvF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAerC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,6DAG7B;IACD,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CAChC,iCAyCC,CAAA"}
@@ -0,0 +1,61 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { nodeRegistry } from '@pascal-app/core';
4
+ import { createPortal } from '@react-three/fiber';
5
+ import { Fragment, lazy, memo, Suspense, useMemo } from 'react';
6
+ // Lazy components cached by their source so React.lazy isn't re-invoked per render.
7
+ const lazyCache = new WeakMap();
8
+ function getReplaceRenderer(source) {
9
+ const cached = lazyCache.get(source);
10
+ if (cached)
11
+ return cached;
12
+ const Comp = lazy(source.module);
13
+ lazyCache.set(source, Comp);
14
+ return Comp;
15
+ }
16
+ /**
17
+ * Re-renders `bake: 'replace'` kinds (e.g. plugin trees) live over the baked GLB.
18
+ * The baked static meshes are hidden by `GlbScene`; these nodes are grouped by
19
+ * `(parent level, kind)` and each group is handed to the kind's collective
20
+ * `bakeReplaceRenderer` (an instanced renderer), portaled into that level's baked
21
+ * `Object3D`. Local-space instances therefore ride level stacking/explode for
22
+ * free, and a forest stays a few instanced draw calls.
23
+ *
24
+ * Memoized: `GlbScene` re-renders each frame on camera move; `nodes` and
25
+ * `identity` are stable refs, so this whole subtree short-circuits.
26
+ */
27
+ export const GlbReplaceInstances = memo(function GlbReplaceInstances({ nodes, identity, }) {
28
+ const byLevel = useMemo(() => {
29
+ const levels = new Map();
30
+ for (const node of nodes) {
31
+ const parentId = node.parentId;
32
+ if (!parentId)
33
+ continue;
34
+ let byKind = levels.get(parentId);
35
+ if (!byKind) {
36
+ byKind = new Map();
37
+ levels.set(parentId, byKind);
38
+ }
39
+ const list = byKind.get(node.type);
40
+ if (list)
41
+ list.push(node);
42
+ else
43
+ byKind.set(node.type, [node]);
44
+ }
45
+ return levels;
46
+ }, [nodes]);
47
+ const portals = [];
48
+ byLevel.forEach((byKind, parentId) => {
49
+ const anchor = identity.get(parentId);
50
+ if (!anchor)
51
+ return;
52
+ byKind.forEach((kindNodes, kind) => {
53
+ const source = nodeRegistry.get(kind)?.bakeReplaceRenderer;
54
+ if (!source)
55
+ return;
56
+ const Renderer = getReplaceRenderer(source);
57
+ portals.push(_jsx(Fragment, { children: createPortal(_jsx(Suspense, { fallback: null, children: _jsx(Renderer, { nodes: kindNodes }) }), anchor) }, `${parentId}:${kind}`));
58
+ });
59
+ });
60
+ return _jsx(_Fragment, { children: portals });
61
+ });
@@ -0,0 +1,54 @@
1
+ import { type AnyNode } from '@pascal-app/core';
2
+ import { type GlbInteractiveItem } from './glb-interactive';
3
+ /** A building floor discovered in the baked GLB, ordered bottom-to-top. */
4
+ export type GlbLevel = {
5
+ id: `level_${string}`;
6
+ label: string;
7
+ };
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
+ /** Walkthrough HUD state, reported each frame: the floor/room the camera is in
19
+ * and the openable door/window directly in view (for the reticle prompt). */
20
+ export type GlbWalkthrough = {
21
+ zoneLabel: string | null;
22
+ floorLabel: string | null;
23
+ door: {
24
+ label: string;
25
+ isOpen: boolean;
26
+ } | null;
27
+ } | null;
28
+ /**
29
+ * GLB-consuming viewer scene (plan phase 2). Loads a baked artifact and drives
30
+ * the editor's presentation/interaction with no parametric scene graph. Hover
31
+ * and click resolve through the drill hierarchy (building → level → zone →
32
+ * object): the cursor targets the floor in the building view, the room or
33
+ * structure in a level, and items/structure inside a room. Selection feeds the
34
+ * existing outline post-FX, openables play their baked clips, and the shared
35
+ * `useViewer.selection` (with its hierarchy guard) holds the drill state. The
36
+ * host disables the parametric `SelectionManager` (`selectionManager="custom"`).
37
+ */
38
+ export declare function GlbScene({ url, interactiveItems, referenceNodes, replaceNodes, onLevelsChange, onIdentityChange, onHoverChange, onWalkthroughChange, }: {
39
+ url: string;
40
+ /** Light / animation effects + controls recovered from the DB scene graph,
41
+ * joined to the baked nodes by `pascalId` to re-light + re-animate the GLB. */
42
+ interactiveItems?: GlbInteractiveItem[];
43
+ /** Scan / guide nodes from the scene graph, re-added at runtime (they're
44
+ * stripped from the bake). Already filtered by the privacy flags upstream. */
45
+ referenceNodes?: AnyNode[];
46
+ /** `bake: 'replace'` nodes (e.g. plugin trees): baked static but re-rendered
47
+ * live here via their `bakeReplaceRenderer`; the baked meshes are hidden. */
48
+ replaceNodes?: AnyNode[];
49
+ onLevelsChange?: (levels: GlbLevel[]) => void;
50
+ onIdentityChange?: (identity: GlbIdentity) => void;
51
+ onHoverChange?: (hover: GlbHover) => void;
52
+ onWalkthroughChange?: (state: GlbWalkthrough) => void;
53
+ }): import("react").JSX.Element;
54
+ //# sourceMappingURL=glb-scene.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glb-scene.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-scene.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,OAAO,EAAkC,MAAM,kBAAkB,CAAA;AAc/E,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAqB3E,2EAA2E;AAC3E,MAAM,MAAM,QAAQ,GAAG;IAAE,EAAE,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/D,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAEzE,mFAAmF;AACnF,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAA;AAE7D;8EAC8E;AAC9E,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,CAAA;KAAE,GAAG,IAAI,CAAA;CAChD,GAAG,IAAI,CAAA;AA8NR;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,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,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAA;IAC7C,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,KAAK,IAAI,CAAA;IAClD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAA;IACzC,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAA;CACtD,+BAgzBA"}