@pascal-app/viewer 1.0.6 → 1.1.0
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/README.md +54 -21
- 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-replace-instances.d.ts +6 -3
- package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -1
- package/dist/components/viewer/glb-replace-instances.js +5 -4
- 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 +5 -1
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +290 -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 +38 -10
- 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/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +2 -2
- 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/unsupported-gpu-fallback.d.ts.map +1 -1
- package/dist/components/viewer/unsupported-gpu-fallback.js +21 -1
- 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 +278 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +12 -1
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +2 -1
- package/dist/index.d.ts +26 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -8
- package/dist/lib/asset-url.d.ts +0 -1
- package/dist/lib/asset-url.d.ts.map +1 -1
- package/dist/lib/asset-url.js +3 -3
- 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 +54 -0
- package/dist/lib/daylight/room-map.d.ts +124 -0
- package/dist/lib/daylight/room-map.d.ts.map +1 -0
- package/dist/lib/daylight/room-map.js +630 -0
- package/dist/lib/daylight/room-scene.d.ts +56 -0
- package/dist/lib/daylight/room-scene.d.ts.map +1 -0
- package/dist/lib/daylight/room-scene.js +391 -0
- package/dist/lib/daylight/rooms.d.ts +292 -0
- package/dist/lib/daylight/rooms.d.ts.map +1 -0
- package/dist/lib/daylight/rooms.js +1268 -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/ktx2-loader.d.ts.map +1 -1
- package/dist/lib/ktx2-loader.js +2 -1
- 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/measurement-label.d.ts +4 -0
- package/dist/lib/measurement-label.d.ts.map +1 -0
- package/dist/lib/measurement-label.js +54 -0
- package/dist/lib/measurement-presentation.d.ts +5 -0
- package/dist/lib/measurement-presentation.d.ts.map +1 -0
- package/dist/lib/measurement-presentation.js +8 -0
- 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/pointer-events.d.ts.map +1 -1
- package/dist/lib/pointer-events.js +3 -3
- 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 +13 -10
- 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/lib/viewer-config.d.ts +21 -0
- package/dist/lib/viewer-config.d.ts.map +1 -0
- package/dist/lib/viewer-config.js +21 -0
- package/dist/lib/viewer-preference-storage.d.ts +7 -0
- package/dist/lib/viewer-preference-storage.d.ts.map +1 -0
- package/dist/lib/viewer-preference-storage.js +23 -0
- 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 +62 -59
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +106 -19
- 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/elevator/elevator-interaction-system.d.ts +16 -2
- package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-interaction-system.js +41 -8
- package/dist/systems/geometry/geometry-status.d.ts +8 -0
- package/dist/systems/geometry/geometry-status.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-status.js +24 -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 +582 -168
- 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 +11 -0
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +86 -25
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type AnyNodeId, type Interactive, type SceneGraph } from '@pascal-app/core';
|
|
2
2
|
import { type EvaluatedLight, type ProceduralItemNode } from '@pascal-app/core/procedural-items';
|
|
3
|
-
import { type AnimationAction, type Object3D
|
|
3
|
+
import { type AnimationAction, type Object3D } from 'three';
|
|
4
|
+
import { type PooledLight } from '../../systems/item-light/pooled-lights';
|
|
4
5
|
/** An interactive item recovered from the scene graph so the baked GLB can be
|
|
5
6
|
* re-lit / re-animated by joining on `pascalId`. The GLB carries the geometry
|
|
6
7
|
* + identity; the effects + controls live in the DB scene graph (no sidecar). */
|
|
@@ -21,13 +22,6 @@ export type GlbInteractiveItem = {
|
|
|
21
22
|
recipe?: Pick<ProceduralItemNode['recipe'], 'parts' | 'joints'>;
|
|
22
23
|
};
|
|
23
24
|
};
|
|
24
|
-
/** A baked zone's identity node + its local floor polygon (from `extras`). */
|
|
25
|
-
export type GlbZoneRef = {
|
|
26
|
-
id: string;
|
|
27
|
-
node: Object3D;
|
|
28
|
-
polygon: [number, number][];
|
|
29
|
-
holes?: [number, number][][];
|
|
30
|
-
};
|
|
31
25
|
/** Pull the interactive items out of a scene graph. Only items that actually
|
|
32
26
|
* carry effects (light / animation) are returned — everything else baked
|
|
33
27
|
* faithfully and needs no runtime help. */
|
|
@@ -40,24 +34,16 @@ export declare function buildGlbLightRegs(items: GlbInteractiveItem[], identity:
|
|
|
40
34
|
* baked Object3Ds (`identity`), joined on `pascalId`. Nothing is stamped into
|
|
41
35
|
* the GLB itself, so the artifact stays integrator-clean.
|
|
42
36
|
*/
|
|
43
|
-
export declare function GlbInteractive({ items, identity,
|
|
37
|
+
export declare function GlbInteractive({ items, identity, actions, }: {
|
|
44
38
|
items: GlbInteractiveItem[];
|
|
45
39
|
identity: Map<string, Object3D>;
|
|
46
|
-
zones: GlbZoneRef[];
|
|
47
40
|
/** Baked animation actions keyed by clip name — ambient item loops play from
|
|
48
41
|
* `<pascalId>: loop`. */
|
|
49
42
|
actions: Record<string, AnimationAction | null>;
|
|
50
|
-
/** Level pascalIds bottom-to-top, so the light pool can prefer ground-floor
|
|
51
|
-
* lights when nothing is focused (mirrors the parametric level factor). */
|
|
52
|
-
levelOrder: string[];
|
|
53
43
|
}): import("react").JSX.Element;
|
|
54
|
-
export type GlbLightReg = {
|
|
55
|
-
key: string;
|
|
44
|
+
export type GlbLightReg = PooledLight & {
|
|
56
45
|
nodeId: AnyNodeId;
|
|
57
46
|
object: Object3D;
|
|
58
|
-
color: string;
|
|
59
|
-
distance: number;
|
|
60
|
-
getWorldPosition: (out: Vector3) => void;
|
|
61
47
|
getIntensity: () => number;
|
|
62
48
|
isOn: () => boolean;
|
|
63
49
|
levelId: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glb-interactive.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-interactive.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"glb-interactive.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-interactive.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,WAAW,EAEhB,KAAK,UAAU,EAMhB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,KAAK,cAAc,EAEnB,KAAK,kBAAkB,EACxB,MAAM,mCAAmC,CAAA;AAG1C,OAAO,EACL,KAAK,eAAe,EAGpB,KAAK,QAAQ,EAGd,MAAM,OAAO,CAAA;AAWd,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,wCAAwC,CAAA;AAEvF;;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;IACxB;mEAC+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,UAAU,CAAC,EAAE;QACX,MAAM,EAAE,cAAc,EAAE,CAAA;QACxB,KAAK,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAA;QAC5C,+EAA+E;QAC/E,MAAM,CAAC,EAAE,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC,CAAA;KAChE,CAAA;CACF,CAAA;AAED;;4CAE4C;AAC5C,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GACxC,kBAAkB,EAAE,CAiEtB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,kBAAkB,EAAE,EAC3B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,GAC9B,WAAW,EAAE,CA2Ff;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,QAAQ,EACR,OAAO,GACR,EAAE;IACD,KAAK,EAAE,kBAAkB,EAAE,CAAA;IAC3B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAC/B;8BAC0B;IAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC,CAAA;CAChD,+BAuDA;AAsCD,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG;IACtC,MAAM,EAAE,SAAS,CAAA;IACjB,MAAM,EAAE,QAAQ,CAAA;IAChB,YAAY,EAAE,MAAM,MAAM,CAAA;IAC1B,IAAI,EAAE,MAAM,OAAO,CAAA;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB,CAAA"}
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { isScriptedNode, scriptedInteractive, scriptedSize, useInteractive, } from '@pascal-app/core';
|
|
4
4
|
import { evaluateRecipe, } from '@pascal-app/core/procedural-items';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { LoopRepeat, MathUtils, Vector3, } from 'three';
|
|
5
|
+
import { useThree } from '@react-three/fiber';
|
|
6
|
+
import { useEffect, useLayoutEffect, useMemo } from 'react';
|
|
7
|
+
import { LoopRepeat, MathUtils, Quaternion, Vector3, } from 'three';
|
|
9
8
|
import { useShallow } from 'zustand/react/shallow';
|
|
10
9
|
import { SCENE_LAYER } from '../../lib/layers';
|
|
11
10
|
import { decorateProceduralEmission, proceduralSlotMeshes, setProceduralEmission, } from '../../lib/procedural-emission';
|
|
12
11
|
import { useItemLightPool } from '../../store/use-item-light-pool';
|
|
13
12
|
import useViewer from '../../store/use-viewer';
|
|
14
|
-
import { ControlWidget } from '../../systems/interactive/control-widget';
|
|
15
|
-
import { proceduralControlDescriptors } from '../../systems/interactive/procedural-controls';
|
|
16
13
|
import { ScriptedClips } from '../../systems/interactive/scripted-clips';
|
|
14
|
+
import { PooledLights } from '../../systems/item-light/pooled-lights';
|
|
17
15
|
/** Pull the interactive items out of a scene graph. Only items that actually
|
|
18
16
|
* carry effects (light / animation) are returned — everything else baked
|
|
19
17
|
* faithfully and needs no runtime help. */
|
|
@@ -46,7 +44,7 @@ export function buildGlbInteractiveItems(sceneGraph) {
|
|
|
46
44
|
// emitted, as in the editor.
|
|
47
45
|
if (isScriptedNode(raw)) {
|
|
48
46
|
const scripted = raw;
|
|
49
|
-
const interactive =
|
|
47
|
+
const interactive = scriptedInteractive(scripted);
|
|
50
48
|
if (!interactive)
|
|
51
49
|
continue;
|
|
52
50
|
const [, height] = scriptedSize(scripted.source.manifest);
|
|
@@ -113,6 +111,7 @@ export function buildGlbLightRegs(items, identity) {
|
|
|
113
111
|
const anchor = motion ?? object;
|
|
114
112
|
anchor.updateWorldMatrix(true, false);
|
|
115
113
|
out.copy(local).applyMatrix4(anchor.matrixWorld);
|
|
114
|
+
return true;
|
|
116
115
|
},
|
|
117
116
|
getIntensity: () => light.intensity,
|
|
118
117
|
isOn: () => useInteractive.getState().procedural[item.pascalId]?.lightsOn ??
|
|
@@ -134,6 +133,14 @@ export function buildGlbLightRegs(items, identity) {
|
|
|
134
133
|
object,
|
|
135
134
|
color: effect.color,
|
|
136
135
|
distance: effect.distance ?? 0,
|
|
136
|
+
spot: effect.spot,
|
|
137
|
+
shadows: effect.shadows,
|
|
138
|
+
glow: effect.glow,
|
|
139
|
+
peak: effect.intensityRange[1],
|
|
140
|
+
getWorldDirection: (out) => {
|
|
141
|
+
out.set(...(effect.spot?.direction ?? [0, -1, 0])).normalize();
|
|
142
|
+
out.applyQuaternion(object.getWorldQuaternion(_quaternion));
|
|
143
|
+
},
|
|
137
144
|
getWorldPosition: (out) => {
|
|
138
145
|
object.updateWorldMatrix(true, false);
|
|
139
146
|
if (item.scripted) {
|
|
@@ -141,10 +148,11 @@ export function buildGlbLightRegs(items, identity) {
|
|
|
141
148
|
out
|
|
142
149
|
.set(effect.offset[0] * scale[0], effect.offset[1] * scale[1], effect.offset[2] * scale[2])
|
|
143
150
|
.applyMatrix4(object.matrixWorld);
|
|
144
|
-
return;
|
|
151
|
+
return true;
|
|
145
152
|
}
|
|
146
153
|
object.getWorldPosition(out);
|
|
147
154
|
out.set(out.x + effect.offset[0], out.y + effect.offset[1], out.z + effect.offset[2]);
|
|
155
|
+
return true;
|
|
148
156
|
},
|
|
149
157
|
getIntensity: () => {
|
|
150
158
|
const values = useInteractive.getState().items[item.pascalId]?.controlValues;
|
|
@@ -162,7 +170,6 @@ export function buildGlbLightRegs(items, identity) {
|
|
|
162
170
|
}
|
|
163
171
|
return regs;
|
|
164
172
|
}
|
|
165
|
-
const _itemPos = new Vector3();
|
|
166
173
|
/**
|
|
167
174
|
* Re-creates the item-driven interactivity the parametric viewer has — pooled
|
|
168
175
|
* lights, ambient animation, and the controls overlay — on top of a baked GLB.
|
|
@@ -170,7 +177,7 @@ const _itemPos = new Vector3();
|
|
|
170
177
|
* baked Object3Ds (`identity`), joined on `pascalId`. Nothing is stamped into
|
|
171
178
|
* the GLB itself, so the artifact stays integrator-clean.
|
|
172
179
|
*/
|
|
173
|
-
export function GlbInteractive({ items, identity,
|
|
180
|
+
export function GlbInteractive({ items, identity, actions, }) {
|
|
174
181
|
const scene = useThree((state) => state.scene);
|
|
175
182
|
useLayoutEffect(() => {
|
|
176
183
|
useItemLightPool.getState().setBakedCanvas(scene, true);
|
|
@@ -201,34 +208,11 @@ export function GlbInteractive({ items, identity, zones, actions, levelOrder, })
|
|
|
201
208
|
}, [items]);
|
|
202
209
|
const animationItems = useMemo(() => items.filter((item) => item.interactive.effects.some((e) => e.kind === 'animation')), [items]);
|
|
203
210
|
const lightRegs = useMemo(() => buildGlbLightRegs(items, identity), [items, identity]);
|
|
204
|
-
|
|
205
|
-
// Project the zone's baked-local polygon into world space so focused zone
|
|
206
|
-
// membership still works after level stacking moves its parent.
|
|
207
|
-
const focusedZoneId = useViewer((s) => s.selection.zoneId);
|
|
208
|
-
const selectedIds = useViewer((s) => s.selection.selectedIds);
|
|
209
|
-
const worldPolygon = useMemo(() => {
|
|
210
|
-
if (!focusedZoneId)
|
|
211
|
-
return null;
|
|
212
|
-
const zone = zones.find((z) => z.id === focusedZoneId);
|
|
213
|
-
if (!zone)
|
|
214
|
-
return null;
|
|
215
|
-
zone.node.updateWorldMatrix(true, false);
|
|
216
|
-
const project = (ring) => ring.map(([x, z]) => {
|
|
217
|
-
const v = new Vector3(x, 0, z).applyMatrix4(zone.node.matrixWorld);
|
|
218
|
-
return [v.x, v.z];
|
|
219
|
-
});
|
|
220
|
-
return { outer: project(zone.polygon), holes: (zone.holes ?? []).map(project) };
|
|
221
|
-
}, [focusedZoneId, zones]);
|
|
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
|
|
211
|
+
return (_jsxs(_Fragment, { children: [_jsx(GlbItemLights, { 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
212
|
.filter((item) => item.procedural?.lights.length)
|
|
224
213
|
.map((item) => {
|
|
225
214
|
const object = identity.get(item.pascalId);
|
|
226
215
|
return object ? (_jsx(GlbProceduralEmission, { item: item, object: object }, item.pascalId)) : null;
|
|
227
|
-
}), items
|
|
228
|
-
.filter((item) => worldPolygon || selectedIds.includes(item.pascalId))
|
|
229
|
-
.map((item) => {
|
|
230
|
-
const object = identity.get(item.pascalId);
|
|
231
|
-
return object ? (_jsx(GlbItemControls, { isSelected: selectedIds.includes(item.pascalId), item: item, object: object, worldPolygon: worldPolygon }, item.pascalId)) : null;
|
|
232
216
|
})] }));
|
|
233
217
|
}
|
|
234
218
|
function GlbProceduralEmission({ item, object }) {
|
|
@@ -258,22 +242,7 @@ function GlbProceduralEmission({ item, object }) {
|
|
|
258
242
|
}, [item, object]);
|
|
259
243
|
return null;
|
|
260
244
|
}
|
|
261
|
-
|
|
262
|
-
//
|
|
263
|
-
// Mirrors the parametric `ItemLightSystem`: a fixed pool of point lights is
|
|
264
|
-
// assigned to the nearest/most-visible lit items each tick (camera-proximity
|
|
265
|
-
// scored, with hysteresis), snapped to the item's world position + offset, and
|
|
266
|
-
// faded in/out on reassignment. Mounting a light per item instead would blow
|
|
267
|
-
// the renderer's light budget on a large house.
|
|
268
|
-
const POOL_SIZE = 12;
|
|
269
|
-
const REASSIGN_INTERVAL = 0.2;
|
|
270
|
-
const HYSTERESIS = 0.15;
|
|
271
|
-
const CAM_MOVE_DIST = 0.5;
|
|
272
|
-
const CAM_ROT_DOT = 0.995;
|
|
273
|
-
const _camPos = new Vector3();
|
|
274
|
-
const _camFwd = new Vector3();
|
|
275
|
-
const _dir = new Vector3();
|
|
276
|
-
const _lightWorld = new Vector3();
|
|
245
|
+
const _quaternion = new Quaternion();
|
|
277
246
|
/** The nearest level-identity ancestor's pascalId, for the level factor. */
|
|
278
247
|
function findLevelId(object) {
|
|
279
248
|
let cur = object;
|
|
@@ -294,168 +263,21 @@ function isRendered(object) {
|
|
|
294
263
|
}
|
|
295
264
|
return true;
|
|
296
265
|
}
|
|
297
|
-
|
|
266
|
+
// A light on another storey than the focused one counts for this much of what it adds.
|
|
267
|
+
const OTHER_LEVEL_WEIGHT = 0.25;
|
|
268
|
+
/** 0 while the light cannot be seen lit (off, hidden, on a hidden level), else its level factor. */
|
|
269
|
+
function regWeight(reg, selectedLevelId, levelMode) {
|
|
298
270
|
if (!reg.isOn() || !isRendered(reg.object))
|
|
299
|
-
return
|
|
300
|
-
if (selectedLevelId
|
|
301
|
-
return
|
|
302
|
-
|
|
303
|
-
_dir.copy(_lightWorld).sub(_camPos).normalize();
|
|
304
|
-
const angular = 1 - _camFwd.dot(_dir);
|
|
305
|
-
const dist = _camPos.distanceTo(_lightWorld) / 200;
|
|
306
|
-
let levelPenalty = 0;
|
|
307
|
-
if (selectedLevelId) {
|
|
308
|
-
if (reg.levelId !== selectedLevelId)
|
|
309
|
-
levelPenalty = levelMode === 'solo' ? 100 : 0.8;
|
|
310
|
-
}
|
|
311
|
-
else if (reg.levelId && (levelIndexById.get(reg.levelId) ?? 0) !== 0) {
|
|
312
|
-
levelPenalty = 0.3;
|
|
313
|
-
}
|
|
314
|
-
return angular * 0.7 + dist * 0.3 + levelPenalty;
|
|
271
|
+
return 0;
|
|
272
|
+
if (!selectedLevelId || reg.levelId === selectedLevelId)
|
|
273
|
+
return 1;
|
|
274
|
+
return levelMode === 'solo' ? 0 : OTHER_LEVEL_WEIGHT;
|
|
315
275
|
}
|
|
316
|
-
function GlbItemLights({ regs
|
|
317
|
-
|
|
318
|
-
const slots = useRef(Array.from({ length: POOL_SIZE }, () => ({ key: null, pendingKey: null, isFadingOut: false })));
|
|
319
|
-
const reassignTimer = useRef(0);
|
|
320
|
-
const prevCamPos = useRef(new Vector3());
|
|
321
|
-
const prevCamFwd = useRef(new Vector3(0, 0, -1));
|
|
322
|
-
const regByKey = useMemo(() => new Map(regs.map((r) => [r.key, r])), [regs]);
|
|
323
|
-
useFrame(({ camera }, delta) => {
|
|
324
|
-
const dt = Math.min(delta, 0.1);
|
|
325
|
-
camera.getWorldPosition(_camPos);
|
|
326
|
-
camera.getWorldDirection(_camFwd);
|
|
327
|
-
const camMoved = _camPos.distanceTo(prevCamPos.current) > CAM_MOVE_DIST ||
|
|
328
|
-
_camFwd.dot(prevCamFwd.current) < CAM_ROT_DOT;
|
|
329
|
-
reassignTimer.current -= delta;
|
|
330
|
-
if (reassignTimer.current <= 0 || camMoved) {
|
|
331
|
-
reassignTimer.current = REASSIGN_INTERVAL;
|
|
332
|
-
prevCamPos.current.copy(_camPos);
|
|
333
|
-
prevCamFwd.current.copy(_camFwd);
|
|
276
|
+
function GlbItemLights({ regs }) {
|
|
277
|
+
return (_jsx(PooledLights, { getLights: () => regs, intensity: (reg) => (reg.isOn() && isRendered(reg.object) ? reg.getIntensity() : 0), weight: (reg) => {
|
|
334
278
|
const viewer = useViewer.getState();
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const scored = regs.map((reg) => ({
|
|
338
|
-
key: reg.key,
|
|
339
|
-
score: scoreReg(reg, selectedLevelId, levelMode, levelIndexById),
|
|
340
|
-
}));
|
|
341
|
-
scored.sort((a, b) => a.score - b.score);
|
|
342
|
-
const scoreByKey = new Map(scored.map((s) => [s.key, s.score]));
|
|
343
|
-
const desired = scored
|
|
344
|
-
.filter((s) => Number.isFinite(s.score))
|
|
345
|
-
.slice(0, POOL_SIZE)
|
|
346
|
-
.map((s) => s.key);
|
|
347
|
-
const currentlyAssigned = new Map();
|
|
348
|
-
for (let i = 0; i < POOL_SIZE; i++) {
|
|
349
|
-
const s = slots.current[i];
|
|
350
|
-
const k = s?.key ?? s?.pendingKey;
|
|
351
|
-
if (k)
|
|
352
|
-
currentlyAssigned.set(k, i);
|
|
353
|
-
}
|
|
354
|
-
const usedSlots = new Set();
|
|
355
|
-
const assignedKeys = new Set();
|
|
356
|
-
// Pass 1: keep existing slots whose key is still wanted.
|
|
357
|
-
for (const key of desired) {
|
|
358
|
-
const existingSlot = currentlyAssigned.get(key);
|
|
359
|
-
if (existingSlot !== undefined && !usedSlots.has(existingSlot)) {
|
|
360
|
-
usedSlots.add(existingSlot);
|
|
361
|
-
assignedKeys.add(key);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
// Pass 2: assign the rest to free slots, evicting only on a clear win.
|
|
365
|
-
let freeSlot = 0;
|
|
366
|
-
for (const key of desired) {
|
|
367
|
-
if (assignedKeys.has(key))
|
|
368
|
-
continue;
|
|
369
|
-
while (freeSlot < POOL_SIZE && usedSlots.has(freeSlot))
|
|
370
|
-
freeSlot++;
|
|
371
|
-
if (freeSlot >= POOL_SIZE)
|
|
372
|
-
break;
|
|
373
|
-
const freeSlotData = slots.current[freeSlot];
|
|
374
|
-
const currentKey = freeSlotData ? (freeSlotData.key ?? freeSlotData.pendingKey) : null;
|
|
375
|
-
if (currentKey && !desired.includes(currentKey)) {
|
|
376
|
-
const currentScore = scoreByKey.get(currentKey) ?? Number.POSITIVE_INFINITY;
|
|
377
|
-
const newScore = scoreByKey.get(key) ?? 0;
|
|
378
|
-
if (currentScore - newScore < HYSTERESIS) {
|
|
379
|
-
freeSlot++;
|
|
380
|
-
continue;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
usedSlots.add(freeSlot);
|
|
384
|
-
assignedKeys.add(key);
|
|
385
|
-
const slot = slots.current[freeSlot];
|
|
386
|
-
if (slot && slot.key !== key) {
|
|
387
|
-
slot.pendingKey = key;
|
|
388
|
-
slot.isFadingOut = slot.key !== null;
|
|
389
|
-
if (!slot.isFadingOut) {
|
|
390
|
-
slot.key = key;
|
|
391
|
-
slot.pendingKey = null;
|
|
392
|
-
const light = lightRefs.current[freeSlot];
|
|
393
|
-
const reg = regByKey.get(key);
|
|
394
|
-
if (light && reg) {
|
|
395
|
-
light.color.set(reg.color);
|
|
396
|
-
light.distance = reg.distance;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
freeSlot++;
|
|
401
|
-
}
|
|
402
|
-
// Retire slots whose key is no longer wanted.
|
|
403
|
-
for (let i = 0; i < POOL_SIZE; i++) {
|
|
404
|
-
if (!usedSlots.has(i)) {
|
|
405
|
-
const slot = slots.current[i];
|
|
406
|
-
if (slot?.key && !desired.includes(slot.key)) {
|
|
407
|
-
slot.pendingKey = null;
|
|
408
|
-
slot.isFadingOut = true;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
// Per-frame: fade, snap position, and track intensity from control state.
|
|
414
|
-
// The pool lights stay permanently `visible` — only `intensity` is animated
|
|
415
|
-
// (an idle light just lerps to 0). Toggling `visible` would change the
|
|
416
|
-
// active-light count, which forces the WebGPU renderer to recompile every
|
|
417
|
-
// material's lighting node — a hard frame-time spike on every reassignment
|
|
418
|
-
// (i.e. on every camera move). Keeping the count fixed avoids that entirely.
|
|
419
|
-
for (let i = 0; i < POOL_SIZE; i++) {
|
|
420
|
-
const light = lightRefs.current[i];
|
|
421
|
-
const slot = slots.current[i];
|
|
422
|
-
if (!(light && slot))
|
|
423
|
-
continue;
|
|
424
|
-
if (slot.isFadingOut) {
|
|
425
|
-
light.intensity = MathUtils.lerp(light.intensity, 0, dt * 12);
|
|
426
|
-
if (light.intensity < 0.01) {
|
|
427
|
-
light.intensity = 0;
|
|
428
|
-
slot.isFadingOut = false;
|
|
429
|
-
slot.key = slot.pendingKey;
|
|
430
|
-
slot.pendingKey = null;
|
|
431
|
-
if (slot.key) {
|
|
432
|
-
const reg = regByKey.get(slot.key);
|
|
433
|
-
if (reg) {
|
|
434
|
-
light.color.set(reg.color);
|
|
435
|
-
light.distance = reg.distance;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
continue;
|
|
440
|
-
}
|
|
441
|
-
if (!slot.key) {
|
|
442
|
-
light.intensity = MathUtils.lerp(light.intensity, 0, dt * 12);
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
const reg = regByKey.get(slot.key);
|
|
446
|
-
if (!reg) {
|
|
447
|
-
slot.key = null;
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
reg.getWorldPosition(_lightWorld);
|
|
451
|
-
light.position.copy(_lightWorld);
|
|
452
|
-
const targetIntensity = reg.isOn() && isRendered(reg.object) ? reg.getIntensity() : 0;
|
|
453
|
-
light.intensity = MathUtils.lerp(light.intensity, targetIntensity, dt * 12);
|
|
454
|
-
}
|
|
455
|
-
}, 6);
|
|
456
|
-
return (_jsx(_Fragment, { children: Array.from({ length: POOL_SIZE }, (_, i) => (_jsx("pointLight", { castShadow: false, intensity: 0, ref: (el) => {
|
|
457
|
-
lightRefs.current[i] = el;
|
|
458
|
-
} }, i))) }));
|
|
279
|
+
return regWeight(reg, viewer.selection.levelId, viewer.levelMode);
|
|
280
|
+
} }));
|
|
459
281
|
}
|
|
460
282
|
/** Plays an item's baked ambient loop (a fan's spin), gated on its toggle.
|
|
461
283
|
* The clip and its targets are already in the GLB; we only start/stop it. */
|
|
@@ -501,61 +323,3 @@ function GlbScriptedClips({ item, actions, }) {
|
|
|
501
323
|
}, [actions, item.pascalId]);
|
|
502
324
|
return (_jsx(ScriptedClips, { actions: scriptActions, interactive: item.interactive, nodeId: item.pascalId }));
|
|
503
325
|
}
|
|
504
|
-
const FADE_MS = 300;
|
|
505
|
-
/** Controls overlay for a selected item or one inside the focused zone. */
|
|
506
|
-
function GlbItemControls({ item, object, worldPolygon, isSelected, }) {
|
|
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);
|
|
510
|
-
const setControlValue = useInteractive((s) => s.setControlValue);
|
|
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) {
|
|
523
|
-
object.getWorldPosition(_itemPos);
|
|
524
|
-
visible = visible || containsPoint([worldPolygon], [_itemPos.x, _itemPos.z]);
|
|
525
|
-
}
|
|
526
|
-
// Fade in on mount and fade out before unmounting the <Html>.
|
|
527
|
-
const [mounted, setMounted] = useState(false);
|
|
528
|
-
const [shown, setShown] = useState(false);
|
|
529
|
-
useEffect(() => {
|
|
530
|
-
if (visible) {
|
|
531
|
-
setMounted(true);
|
|
532
|
-
let raf2 = 0;
|
|
533
|
-
const raf1 = requestAnimationFrame(() => {
|
|
534
|
-
raf2 = requestAnimationFrame(() => setShown(true));
|
|
535
|
-
});
|
|
536
|
-
return () => {
|
|
537
|
-
cancelAnimationFrame(raf1);
|
|
538
|
-
cancelAnimationFrame(raf2);
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
setShown(false);
|
|
542
|
-
const timeout = setTimeout(() => setMounted(false), FADE_MS);
|
|
543
|
-
return () => clearTimeout(timeout);
|
|
544
|
-
}, [visible]);
|
|
545
|
-
if (!(mounted && descriptors.length))
|
|
546
|
-
return null;
|
|
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: {
|
|
548
|
-
display: 'flex',
|
|
549
|
-
flexDirection: 'column',
|
|
550
|
-
gap: 6,
|
|
551
|
-
background: 'rgba(0,0,0,0.75)',
|
|
552
|
-
backdropFilter: 'blur(8px)',
|
|
553
|
-
borderRadius: 8,
|
|
554
|
-
padding: '8px 12px',
|
|
555
|
-
minWidth: 120,
|
|
556
|
-
pointerEvents: visible ? 'auto' : 'none',
|
|
557
|
-
userSelect: 'none',
|
|
558
|
-
opacity: shown ? 1 : 0,
|
|
559
|
-
transition: `opacity ${FADE_MS}ms ease`,
|
|
560
|
-
}, children: descriptors.map((descriptor) => (_jsx(ControlWidget, { control: descriptor.control, onChange: descriptor.onChange, value: descriptor.value }, descriptor.key))) }) }), object);
|
|
561
|
-
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyNode } from '@pascal-app/core';
|
|
1
|
+
import { type AnyNode, type GeometryContext } from '@pascal-app/core';
|
|
2
2
|
import type { Object3D } from 'three';
|
|
3
3
|
/**
|
|
4
4
|
* Re-renders `bake: 'replace'` kinds (e.g. plugin trees) live over the baked GLB.
|
|
@@ -9,10 +9,13 @@ import type { Object3D } from 'three';
|
|
|
9
9
|
* free, and a forest stays a few instanced draw calls.
|
|
10
10
|
*
|
|
11
11
|
* Memoized: `GlbScene` re-renders each frame on camera move; `nodes` and
|
|
12
|
-
* `identity` are stable refs, so this whole subtree short-circuits
|
|
12
|
+
* `identity` are stable refs, so this whole subtree short-circuits; `resolve`
|
|
13
|
+
* must be stable too.
|
|
13
14
|
*/
|
|
14
|
-
export declare const GlbReplaceInstances: import("react").MemoExoticComponent<({ nodes, identity, }: {
|
|
15
|
+
export declare const GlbReplaceInstances: import("react").MemoExoticComponent<({ nodes, identity, resolve, }: {
|
|
15
16
|
nodes: AnyNode[];
|
|
16
17
|
identity: Map<string, Object3D>;
|
|
18
|
+
/** Looks up nodes of the published scene graph; handed to every renderer. */
|
|
19
|
+
resolve?: GeometryContext['resolve'];
|
|
17
20
|
}) => import("react").JSX.Element>;
|
|
18
21
|
//# sourceMappingURL=glb-replace-instances.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glb-replace-instances.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-replace-instances.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"glb-replace-instances.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-replace-instances.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAEZ,KAAK,eAAe,EAErB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAiBrC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,sEAI7B;IACD,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAC/B,6EAA6E;IAC7E,OAAO,CAAC,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;CACrC,iCAyCC,CAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { nodeRegistry } from '@pascal-app/core';
|
|
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';
|
|
6
6
|
// Lazy components cached by their source so React.lazy isn't re-invoked per render.
|
|
@@ -22,9 +22,10 @@ function getReplaceRenderer(source) {
|
|
|
22
22
|
* free, and a forest stays a few instanced draw calls.
|
|
23
23
|
*
|
|
24
24
|
* Memoized: `GlbScene` re-renders each frame on camera move; `nodes` and
|
|
25
|
-
* `identity` are stable refs, so this whole subtree short-circuits
|
|
25
|
+
* `identity` are stable refs, so this whole subtree short-circuits; `resolve`
|
|
26
|
+
* must be stable too.
|
|
26
27
|
*/
|
|
27
|
-
export const GlbReplaceInstances = memo(function GlbReplaceInstances({ nodes, identity, }) {
|
|
28
|
+
export const GlbReplaceInstances = memo(function GlbReplaceInstances({ nodes, identity, resolve, }) {
|
|
28
29
|
const byLevel = useMemo(() => {
|
|
29
30
|
const levels = new Map();
|
|
30
31
|
for (const node of nodes) {
|
|
@@ -54,7 +55,7 @@ export const GlbReplaceInstances = memo(function GlbReplaceInstances({ nodes, id
|
|
|
54
55
|
if (!source)
|
|
55
56
|
return;
|
|
56
57
|
const Renderer = getReplaceRenderer(source);
|
|
57
|
-
portals.push(_jsx(Fragment, { children: createPortal(_jsx(Suspense, { fallback: null, children: _jsx(Renderer, { nodes: kindNodes }) }), anchor) }, `${parentId}:${kind}`));
|
|
58
|
+
portals.push(_jsx(Fragment, { children: createPortal(_jsx(Suspense, { fallback: null, children: _jsx(Renderer, { nodes: kindNodes, resolve: resolve }) }), anchor) }, `${parentId}:${kind}`));
|
|
58
59
|
});
|
|
59
60
|
});
|
|
60
61
|
return _jsx(_Fragment, { children: portals });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type AnimationAction, type Object3D } from 'three';
|
|
2
|
+
import type { ScriptedClipActions } from '../../systems/interactive/scripted-clips';
|
|
3
|
+
export declare function isGlbShutterHit(hit: Object3D, identity: Object3D): boolean;
|
|
4
|
+
export declare function playGlbShutters(action: AnimationAction, open: boolean): void;
|
|
5
|
+
/** Own only the shutters part; the sash and other mechanisms keep their own state. */
|
|
6
|
+
export declare function bindGlbShutters(identity: ReadonlyMap<string, Object3D>, actions: ScriptedClipActions): () => void;
|
|
7
|
+
export declare function toggleGlbShutters(node: Object3D): boolean;
|
|
8
|
+
//# sourceMappingURL=glb-scene-motion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glb-scene-motion.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-scene-motion.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,eAAe,EAAY,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AACrE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAA;AAEnF,wBAAgB,eAAe,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAK1E;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAO5E;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,EACvC,OAAO,EAAE,mBAAmB,GAC3B,MAAM,IAAI,CAgCZ;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAMzD"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useInteractive } from '@pascal-app/core';
|
|
2
|
+
import { LoopOnce } from 'three';
|
|
3
|
+
export function isGlbShutterHit(hit, identity) {
|
|
4
|
+
for (let part = hit; part && part !== identity; part = part.parent) {
|
|
5
|
+
if (part.name.startsWith('shutter-') || part.name.startsWith('window-roller-'))
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
export function playGlbShutters(action, open) {
|
|
11
|
+
action.enabled = true;
|
|
12
|
+
action.paused = false;
|
|
13
|
+
action.setLoop(LoopOnce, 1);
|
|
14
|
+
action.clampWhenFinished = true;
|
|
15
|
+
action.timeScale = open ? 1 : -1;
|
|
16
|
+
action.play();
|
|
17
|
+
}
|
|
18
|
+
/** Own only the shutters part; the sash and other mechanisms keep their own state. */
|
|
19
|
+
export function bindGlbShutters(identity, actions) {
|
|
20
|
+
const parts = new Map();
|
|
21
|
+
for (const [id, object] of identity) {
|
|
22
|
+
const extras = object.userData;
|
|
23
|
+
const name = extras.clips?.find((clip) => clip === `${id}: shutters`);
|
|
24
|
+
const action = name ? actions[name] : undefined;
|
|
25
|
+
if (!extras.shutters || !action)
|
|
26
|
+
continue;
|
|
27
|
+
const fraction = Math.max(0, Math.min(1, extras.shuttersOpen ?? 1));
|
|
28
|
+
action.time = fraction * action.getClip().duration;
|
|
29
|
+
// Keep the baked rest pose until the first toggle, including separately saved roller poses.
|
|
30
|
+
parts.set(id, action);
|
|
31
|
+
useInteractive.getState().setMechanismPart(id, 'shutters', fraction > 0.5);
|
|
32
|
+
}
|
|
33
|
+
const unsubscribe = useInteractive.subscribe((state, previous) => {
|
|
34
|
+
if (state.mechanismParts === previous.mechanismParts)
|
|
35
|
+
return;
|
|
36
|
+
for (const [id, action] of parts) {
|
|
37
|
+
const key = `${id}:shutters`;
|
|
38
|
+
if (state.mechanismParts[key] !== previous.mechanismParts[key])
|
|
39
|
+
playGlbShutters(action, Boolean(state.mechanismParts[key]));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return () => {
|
|
43
|
+
unsubscribe();
|
|
44
|
+
for (const [id, action] of parts) {
|
|
45
|
+
action.stop();
|
|
46
|
+
useInteractive.getState().removeMechanismPart(id, 'shutters');
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function toggleGlbShutters(node) {
|
|
51
|
+
const { shutters, pascalId } = node.userData;
|
|
52
|
+
if (!shutters || !pascalId)
|
|
53
|
+
return false;
|
|
54
|
+
const state = useInteractive.getState();
|
|
55
|
+
state.setMechanismPart(pascalId, 'shutters', !state.mechanismParts[`${pascalId}:shutters`]);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
@@ -51,7 +51,7 @@ export declare function resolveGlbPick(hits: readonly HitCandidate[], identity:
|
|
|
51
51
|
* when clicked. The host disables the parametric `SelectionManager`
|
|
52
52
|
* (`selectionManager="custom"`).
|
|
53
53
|
*/
|
|
54
|
-
export declare function GlbScene({ url, interactiveItems, referenceNodes, replaceNodes, onObjectsChange, onPick, onWalkthroughChange, }: {
|
|
54
|
+
export declare function GlbScene({ url, interactiveItems, referenceNodes, replaceNodes, onObjectsChange, onPick, onWalkthroughChange, sceneNodes, }: {
|
|
55
55
|
url: string;
|
|
56
56
|
/** Light / animation effects + controls recovered from the DB scene graph,
|
|
57
57
|
* joined to the baked nodes by `pascalId` to re-light + re-animate the GLB. */
|
|
@@ -67,6 +67,10 @@ export declare function GlbScene({ url, interactiveItems, referenceNodes, replac
|
|
|
67
67
|
/** Clicks and hovers on the building; without it the scene is look-only. */
|
|
68
68
|
onPick?: (event: GlbPickEvent) => void;
|
|
69
69
|
onWalkthroughChange?: (state: GlbWalkthrough) => void;
|
|
70
|
+
/** Every node of the scene graph the artifact was baked from: the selection
|
|
71
|
+
* callout says what each element is, and `replace` renderers resolve their
|
|
72
|
+
* parent and siblings. A stable reference. */
|
|
73
|
+
sceneNodes?: Readonly<Record<string, AnyNode>>;
|
|
70
74
|
}): import("react").JSX.Element;
|
|
71
75
|
export {};
|
|
72
76
|
//# 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,EACL,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"glb-scene.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-scene.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAcb,MAAM,kBAAkB,CAAA;AASzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAoB9B,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAuB3E;;;;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;AAqDR,KAAK,YAAY,GAAG;IAAE,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAA;CAAE,CAAA;AA4CpE;;;;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,EACnB,UAAU,GACX,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;IACrD;;mDAE+C;IAC/C,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAC/C,+BA47BA"}
|