@pascal-app/viewer 1.0.0-beta.4 → 1.0.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/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/capture/asset-url.d.ts +2 -0
- package/dist/capture/asset-url.d.ts.map +1 -0
- package/dist/capture/asset-url.js +13 -0
- package/dist/capture/capture-runtime.d.ts +43 -0
- package/dist/capture/capture-runtime.d.ts.map +1 -0
- package/dist/capture/capture-runtime.js +228 -0
- package/dist/capture/frame.d.ts +4 -0
- package/dist/capture/frame.d.ts.map +1 -0
- package/dist/capture/frame.js +21 -0
- package/dist/capture/index.d.ts +12 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +11 -0
- package/dist/capture/layer-visibility.d.ts +5 -0
- package/dist/capture/layer-visibility.d.ts.map +1 -0
- package/dist/capture/layer-visibility.js +11 -0
- package/dist/capture/layers/clay-matcap.d.ts +3 -0
- package/dist/capture/layers/clay-matcap.d.ts.map +1 -0
- package/dist/capture/layers/clay-matcap.js +26 -0
- package/dist/capture/layers/device-motion-layer.d.ts +7 -0
- package/dist/capture/layers/device-motion-layer.d.ts.map +1 -0
- package/dist/capture/layers/device-motion-layer.js +78 -0
- package/dist/capture/layers/point-cloud-layer.d.ts +15 -0
- package/dist/capture/layers/point-cloud-layer.d.ts.map +1 -0
- package/dist/capture/layers/point-cloud-layer.js +106 -0
- package/dist/capture/layers/room-model-layer.d.ts +9 -0
- package/dist/capture/layers/room-model-layer.d.ts.map +1 -0
- package/dist/capture/layers/room-model-layer.js +71 -0
- package/dist/capture/layers/surface-mesh-data.d.ts +9 -0
- package/dist/capture/layers/surface-mesh-data.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-data.js +74 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts +7 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-layer.js +16 -0
- package/dist/capture/preview.d.ts +3 -0
- package/dist/capture/preview.d.ts.map +1 -0
- package/dist/capture/preview.js +2 -0
- package/dist/capture/source-state.d.ts +22 -0
- package/dist/capture/source-state.d.ts.map +1 -0
- package/dist/capture/source-state.js +182 -0
- package/dist/capture/stream-rendering.d.ts +14 -0
- package/dist/capture/stream-rendering.d.ts.map +1 -0
- package/dist/capture/stream-rendering.js +69 -0
- package/dist/capture/trajectory.d.ts +20 -0
- package/dist/capture/trajectory.d.ts.map +1 -0
- package/dist/capture/trajectory.js +77 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts +14 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts.map +1 -0
- package/dist/components/viewer/batched-mesh-spike.js +64 -0
- package/dist/components/viewer/frame-limiter.d.ts +11 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +59 -18
- package/dist/components/viewer/index.d.ts +15 -0
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +74 -82
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +123 -89
- package/dist/components/viewer/perf-monitor.d.ts +6 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +306 -115
- package/dist/components/viewer/perf-panel.d.ts +3 -0
- package/dist/components/viewer/perf-panel.d.ts.map +1 -0
- package/dist/components/viewer/perf-panel.js +178 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
- package/dist/components/viewer/pointer-raycast-layers.js +28 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +124 -83
- package/dist/components/viewer/registered-systems.d.ts +14 -5
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +22 -10
- package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
- package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
- package/dist/components/viewer/scene-atmosphere.js +89 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -1
- package/dist/components/viewer/scene-environment.js +19 -5
- package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
- package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
- package/dist/components/viewer/scene-ground-replacement.js +35 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +7 -2
- package/dist/components/viewer/unsupported-gpu-fallback.d.ts +2 -0
- package/dist/components/viewer/unsupported-gpu-fallback.d.ts.map +1 -0
- package/dist/components/viewer/unsupported-gpu-fallback.js +4 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +4 -1
- package/dist/components/viewer/viewer-presentations.d.ts +67 -0
- package/dist/components/viewer/viewer-presentations.d.ts.map +1 -0
- package/dist/components/viewer/viewer-presentations.js +120 -0
- package/dist/hooks/use-library-materials-version.d.ts +7 -0
- package/dist/hooks/use-library-materials-version.d.ts.map +1 -0
- package/dist/hooks/use-library-materials-version.js +10 -0
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +1 -0
- package/dist/index.d.ts +20 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -7
- package/dist/lib/csg-utils.d.ts +1 -0
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +6 -1
- package/dist/lib/dispose-object3d.d.ts +4 -0
- package/dist/lib/dispose-object3d.d.ts.map +1 -0
- package/dist/lib/dispose-object3d.js +27 -0
- package/dist/lib/geometry-groups.d.ts +17 -0
- package/dist/lib/geometry-groups.d.ts.map +1 -0
- package/dist/lib/geometry-groups.js +53 -0
- package/dist/lib/gpu-perf.d.ts +0 -2
- package/dist/lib/gpu-perf.d.ts.map +1 -1
- package/dist/lib/gpu-perf.js +7 -21
- package/dist/lib/isolation.d.ts +8 -8
- package/dist/lib/isolation.d.ts.map +1 -1
- package/dist/lib/isolation.js +18 -30
- package/dist/lib/layer-pass.d.ts +28 -0
- package/dist/lib/layer-pass.d.ts.map +1 -0
- package/dist/lib/layer-pass.js +254 -0
- package/dist/lib/layers.d.ts +16 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +18 -0
- package/dist/lib/materials.d.ts +12 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +51 -17
- package/dist/lib/merged-outline-node.d.ts +17 -8
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +194 -52
- package/dist/lib/perf-actions.d.ts +55 -0
- package/dist/lib/perf-actions.d.ts.map +1 -0
- package/dist/lib/perf-actions.js +211 -0
- package/dist/lib/perf-observers.d.ts +2 -0
- package/dist/lib/perf-observers.d.ts.map +1 -0
- package/dist/lib/perf-observers.js +17 -0
- package/dist/lib/perf-panel-store.d.ts +71 -0
- package/dist/lib/perf-panel-store.d.ts.map +1 -0
- package/dist/lib/perf-panel-store.js +29 -0
- package/dist/lib/perf-tracks.d.ts +43 -0
- package/dist/lib/perf-tracks.d.ts.map +1 -0
- package/dist/lib/perf-tracks.js +113 -0
- package/dist/lib/pointer-events.d.ts +7 -0
- package/dist/lib/pointer-events.d.ts.map +1 -0
- package/dist/lib/pointer-events.js +680 -0
- package/dist/lib/post-processing-resources.d.ts +12 -0
- package/dist/lib/post-processing-resources.d.ts.map +1 -0
- package/dist/lib/post-processing-resources.js +19 -0
- package/dist/lib/renderer-capability.d.ts +56 -0
- package/dist/lib/renderer-capability.d.ts.map +1 -0
- package/dist/lib/renderer-capability.js +116 -0
- package/dist/lib/scene-visibility.d.ts +15 -0
- package/dist/lib/scene-visibility.d.ts.map +1 -0
- package/dist/lib/scene-visibility.js +69 -0
- package/dist/lib/shadow-only.d.ts +16 -0
- package/dist/lib/shadow-only.d.ts.map +1 -1
- package/dist/lib/shadow-only.js +8 -17
- package/dist/lib/snapshot-pipeline.d.ts +9 -0
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +33 -9
- package/dist/store/use-viewer.d.ts +7 -6
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +1 -1
- package/dist/systems/door/door-animation-system.d.ts.map +1 -1
- package/dist/systems/door/door-animation-system.js +6 -9
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +13 -2
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +9 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +5 -28
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +8 -2
- package/dist/systems/level/level-utils.d.ts +9 -0
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +14 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1196 -58
- package/dist/systems/scan/scan-system.js +6 -5
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +1 -2
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +9 -8
- package/dist/systems/surface-hole-geometry.d.ts +1 -1
- package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
- package/dist/systems/surface-hole-geometry.js +1 -1
- package/dist/systems/wall/level-miter-cache.d.ts +10 -0
- package/dist/systems/wall/level-miter-cache.d.ts.map +1 -0
- package/dist/systems/wall/level-miter-cache.js +40 -0
- package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
- package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
- package/dist/systems/wall/wall-build-lifecycle.js +74 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
- package/dist/systems/wall/wall-cutout-cache.js +304 -0
- package/dist/systems/wall/wall-cutout.d.ts +24 -1
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +33 -140
- package/dist/systems/wall/wall-material-variant.d.ts +31 -0
- package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
- package/dist/systems/wall/wall-material-variant.js +10 -0
- package/dist/systems/wall/wall-materials.d.ts +6 -0
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +29 -13
- package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
- package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
- package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
- package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
- package/dist/systems/wall/wall-system.d.ts +13 -2
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +413 -148
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +9 -8
- package/dist/systems/window/window-system.d.ts +1 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +16 -2
- package/package.json +18 -3
- package/dist/components/viewer/scene-bvh.test.d.ts +0 -2
- package/dist/components/viewer/scene-bvh.test.d.ts.map +0 -1
- package/dist/components/viewer/scene-bvh.test.js +0 -13
- package/dist/lib/csg-utils.test.d.ts +0 -2
- package/dist/lib/csg-utils.test.d.ts.map +0 -1
- package/dist/lib/csg-utils.test.js +0 -45
- package/dist/lib/materials.test.d.ts +0 -2
- package/dist/lib/materials.test.d.ts.map +0 -1
- package/dist/lib/materials.test.js +0 -25
- package/dist/lib/merged-outline-node.test.d.ts +0 -2
- package/dist/lib/merged-outline-node.test.d.ts.map +0 -1
- package/dist/lib/merged-outline-node.test.js +0 -22
- package/dist/lib/polygon-union.d.ts +0 -4
- package/dist/lib/polygon-union.d.ts.map +0 -1
- package/dist/lib/polygon-union.js +0 -289
- package/dist/lib/polygon-union.test.d.ts +0 -2
- package/dist/lib/polygon-union.test.d.ts.map +0 -1
- package/dist/lib/polygon-union.test.js +0 -105
- package/dist/lib/texture-reference.test.d.ts +0 -2
- package/dist/lib/texture-reference.test.d.ts.map +0 -1
- package/dist/lib/texture-reference.test.js +0 -86
- package/dist/store/use-viewer.test.d.ts +0 -2
- package/dist/store/use-viewer.test.d.ts.map +0 -1
- package/dist/store/use-viewer.test.js +0 -61
- package/dist/systems/door/door-animation.test.d.ts +0 -2
- package/dist/systems/door/door-animation.test.d.ts.map +0 -1
- package/dist/systems/door/door-animation.test.js +0 -51
- package/dist/systems/door/door-floor-alignment.test.d.ts +0 -2
- package/dist/systems/door/door-floor-alignment.test.d.ts.map +0 -1
- package/dist/systems/door/door-floor-alignment.test.js +0 -50
- package/dist/systems/geometry/geometry-system.test.d.ts +0 -2
- package/dist/systems/geometry/geometry-system.test.d.ts.map +0 -1
- package/dist/systems/geometry/geometry-system.test.js +0 -30
- package/dist/systems/slab/slab-system.test.d.ts +0 -2
- package/dist/systems/slab/slab-system.test.d.ts.map +0 -1
- package/dist/systems/slab/slab-system.test.js +0 -102
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts +0 -2
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +0 -1
- package/dist/systems/wall/opening-cutout-geometry.test.js +0 -172
- package/dist/systems/wall/wall-opening-cutout.test.d.ts +0 -2
- package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +0 -1
- package/dist/systems/wall/wall-opening-cutout.test.js +0 -59
- package/dist/systems/wall/wall-support-extension.test.d.ts +0 -2
- package/dist/systems/wall/wall-support-extension.test.d.ts.map +0 -1
- package/dist/systems/wall/wall-support-extension.test.js +0 -116
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts +0 -2
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts.map +0 -1
- package/dist/systems/wall/wall-terrain-bottom.test.js +0 -34
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type DeviceMotionTrajectoryPayload } from '@pascal-app/core/capture';
|
|
2
|
+
export type DeviceTrajectoryPose = {
|
|
3
|
+
position: [number, number, number];
|
|
4
|
+
quaternion: [number, number, number, number];
|
|
5
|
+
segment: number;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
};
|
|
8
|
+
export type DeviceTrajectory = {
|
|
9
|
+
duration: number;
|
|
10
|
+
poses: DeviceTrajectoryPose[];
|
|
11
|
+
};
|
|
12
|
+
export type DeviceTrajectoryFrame = {
|
|
13
|
+
alpha: number;
|
|
14
|
+
from: DeviceTrajectoryPose;
|
|
15
|
+
to: DeviceTrajectoryPose;
|
|
16
|
+
};
|
|
17
|
+
export declare function parseDeviceTrajectoryPayload(trajectory: DeviceMotionTrajectoryPayload | null | undefined): DeviceTrajectory | null;
|
|
18
|
+
export declare function parseDeviceTrajectoryPackets(payloads: readonly unknown[]): DeviceTrajectory | null;
|
|
19
|
+
export declare function sampleDeviceTrajectory(trajectory: DeviceTrajectory, elapsed: number): DeviceTrajectoryFrame;
|
|
20
|
+
//# sourceMappingURL=trajectory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trajectory.d.ts","sourceRoot":"","sources":["../../src/capture/trajectory.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,6BAA6B,EAEnC,MAAM,0BAA0B,CAAA;AAGjC,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAClC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5C,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,oBAAoB,EAAE,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,oBAAoB,CAAA;IAC1B,EAAE,EAAE,oBAAoB,CAAA;CACzB,CAAA;AAED,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,6BAA6B,GAAG,IAAI,GAAG,SAAS,GAC3D,gBAAgB,GAAG,IAAI,CAezB;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,SAAS,OAAO,EAAE,GAC3B,gBAAgB,GAAG,IAAI,CAezB;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,gBAAgB,EAC5B,OAAO,EAAE,MAAM,GACd,qBAAqB,CAqBvB"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { DeviceMotionSampleSchema, DeviceMotionTrajectorySchema, } from '@pascal-app/core/capture';
|
|
2
|
+
import { Matrix4, Quaternion, Vector3 } from 'three';
|
|
3
|
+
export function parseDeviceTrajectoryPayload(trajectory) {
|
|
4
|
+
if (!trajectory)
|
|
5
|
+
return null;
|
|
6
|
+
const parsed = DeviceMotionTrajectorySchema.safeParse(trajectory);
|
|
7
|
+
if (!parsed.success)
|
|
8
|
+
return null;
|
|
9
|
+
const poses = parsed.data.samples
|
|
10
|
+
.map(parsePose)
|
|
11
|
+
.sort((left, right) => left.timestamp - right.timestamp);
|
|
12
|
+
if (poses.length < 2)
|
|
13
|
+
return null;
|
|
14
|
+
const firstTimestamp = poses[0]?.timestamp ?? 0;
|
|
15
|
+
for (const pose of poses)
|
|
16
|
+
pose.timestamp -= firstTimestamp;
|
|
17
|
+
const duration = poses.at(-1)?.timestamp ?? 0;
|
|
18
|
+
if (!(duration > 0))
|
|
19
|
+
return null;
|
|
20
|
+
return { duration, poses };
|
|
21
|
+
}
|
|
22
|
+
export function parseDeviceTrajectoryPackets(payloads) {
|
|
23
|
+
let coordinateSystem = 'source';
|
|
24
|
+
let samples = [];
|
|
25
|
+
for (const payload of payloads) {
|
|
26
|
+
const trajectory = DeviceMotionTrajectorySchema.safeParse(payload);
|
|
27
|
+
if (trajectory.success) {
|
|
28
|
+
coordinateSystem = trajectory.data.coordinateSystem;
|
|
29
|
+
samples = [...trajectory.data.samples];
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const sample = DeviceMotionSampleSchema.safeParse(payload);
|
|
33
|
+
if (sample.success)
|
|
34
|
+
samples.push(sample.data);
|
|
35
|
+
}
|
|
36
|
+
if (samples.length < 2)
|
|
37
|
+
return null;
|
|
38
|
+
return parseDeviceTrajectoryPayload({ coordinateSystem, samples });
|
|
39
|
+
}
|
|
40
|
+
export function sampleDeviceTrajectory(trajectory, elapsed) {
|
|
41
|
+
const time = positiveModulo(elapsed, trajectory.duration);
|
|
42
|
+
const poses = trajectory.poses;
|
|
43
|
+
const first = poses[0];
|
|
44
|
+
const last = poses.at(-1);
|
|
45
|
+
if (!(first && last))
|
|
46
|
+
throw new Error('Device trajectory requires at least two poses.');
|
|
47
|
+
let upperIndex = poses.findIndex((pose) => pose.timestamp > time);
|
|
48
|
+
if (upperIndex < 0)
|
|
49
|
+
upperIndex = poses.length - 1;
|
|
50
|
+
if (upperIndex === 0)
|
|
51
|
+
return { alpha: 0, from: first, to: first };
|
|
52
|
+
const from = poses[upperIndex - 1] ?? first;
|
|
53
|
+
const to = poses[upperIndex] ?? last;
|
|
54
|
+
if (from.segment !== to.segment)
|
|
55
|
+
return { alpha: 0, from, to: from };
|
|
56
|
+
const interval = to.timestamp - from.timestamp;
|
|
57
|
+
return {
|
|
58
|
+
alpha: interval > 0 ? Math.min(1, Math.max(0, (time - from.timestamp) / interval)) : 0,
|
|
59
|
+
from,
|
|
60
|
+
to,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function parsePose(value) {
|
|
64
|
+
const matrix = new Matrix4().fromArray(value.transform);
|
|
65
|
+
const position = new Vector3();
|
|
66
|
+
const quaternion = new Quaternion();
|
|
67
|
+
matrix.decompose(position, quaternion, new Vector3());
|
|
68
|
+
return {
|
|
69
|
+
position: [position.x, position.y, position.z],
|
|
70
|
+
quaternion: [quaternion.x, quaternion.y, quaternion.z, quaternion.w],
|
|
71
|
+
segment: value.segment,
|
|
72
|
+
timestamp: value.timestamp,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function positiveModulo(value, divisor) {
|
|
76
|
+
return ((value % divisor) + divisor) % divisor;
|
|
77
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* De-risk spike for item instancing (charter backlog #3a): mounts one
|
|
3
|
+
* BatchedMesh — two geometries, a few hundred instances, one material —
|
|
4
|
+
* through the real render pipeline (post-FX, shadow pass, WebGPU backend).
|
|
5
|
+
* `?spike=batch` only; never mounted in normal sessions.
|
|
6
|
+
*
|
|
7
|
+
* What it proves, read via the ?perf panel + `window.__batchSpike`:
|
|
8
|
+
* - DRAW rises by ~1 per pass, not by the instance count.
|
|
9
|
+
* - TRI drops as instances leave the frustum (perObjectFrustumCulled works).
|
|
10
|
+
* - Shadows cast/receive; no pipeline crash.
|
|
11
|
+
*/
|
|
12
|
+
export declare const BATCH_SPIKE_ENABLED: boolean;
|
|
13
|
+
export declare const BatchedMeshSpike: () => null;
|
|
14
|
+
//# sourceMappingURL=batched-mesh-spike.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batched-mesh-spike.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/batched-mesh-spike.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,SAEsC,CAAA;AAStE,eAAO,MAAM,gBAAgB,YAoD5B,CAAA"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useThree } from '@react-three/fiber';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import * as THREE from 'three';
|
|
4
|
+
/**
|
|
5
|
+
* De-risk spike for item instancing (charter backlog #3a): mounts one
|
|
6
|
+
* BatchedMesh — two geometries, a few hundred instances, one material —
|
|
7
|
+
* through the real render pipeline (post-FX, shadow pass, WebGPU backend).
|
|
8
|
+
* `?spike=batch` only; never mounted in normal sessions.
|
|
9
|
+
*
|
|
10
|
+
* What it proves, read via the ?perf panel + `window.__batchSpike`:
|
|
11
|
+
* - DRAW rises by ~1 per pass, not by the instance count.
|
|
12
|
+
* - TRI drops as instances leave the frustum (perObjectFrustumCulled works).
|
|
13
|
+
* - Shadows cast/receive; no pipeline crash.
|
|
14
|
+
*/
|
|
15
|
+
export const BATCH_SPIKE_ENABLED = typeof window !== 'undefined' &&
|
|
16
|
+
new URLSearchParams(window.location.search).get('spike') === 'batch';
|
|
17
|
+
// `?spike=batch&spikeGrid=70` → 4,900 instances; default 20×20 = 400.
|
|
18
|
+
const GRID = typeof window !== 'undefined'
|
|
19
|
+
? Math.min(120, Number(new URLSearchParams(window.location.search).get('spikeGrid')) || 20)
|
|
20
|
+
: 20;
|
|
21
|
+
const SPACING = 1.2;
|
|
22
|
+
export const BatchedMeshSpike = () => {
|
|
23
|
+
const scene = useThree((s) => s.scene);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const box = new THREE.BoxGeometry(0.4, 0.4, 0.4);
|
|
26
|
+
const sphere = new THREE.SphereGeometry(0.25, 16, 12);
|
|
27
|
+
const material = new THREE.MeshStandardMaterial({ color: '#c2410c', roughness: 0.6 });
|
|
28
|
+
const maxVertices = (box.attributes.position?.count ?? 0) + (sphere.attributes.position?.count ?? 0);
|
|
29
|
+
const maxIndices = (box.index?.count ?? 0) + (sphere.index?.count ?? 0);
|
|
30
|
+
const batch = new THREE.BatchedMesh(GRID * GRID, maxVertices, maxIndices, material);
|
|
31
|
+
batch.castShadow = true;
|
|
32
|
+
batch.receiveShadow = true;
|
|
33
|
+
// Default is true — asserted explicitly because per-instance frustum
|
|
34
|
+
// culling is the property the whole plan depends on.
|
|
35
|
+
batch.perObjectFrustumCulled = true;
|
|
36
|
+
const boxGeomId = batch.addGeometry(box);
|
|
37
|
+
const sphereGeomId = batch.addGeometry(sphere);
|
|
38
|
+
const m = new THREE.Matrix4();
|
|
39
|
+
let count = 0;
|
|
40
|
+
for (let x = 0; x < GRID; x++) {
|
|
41
|
+
for (let z = 0; z < GRID; z++) {
|
|
42
|
+
const geomId = (x + z) % 2 === 0 ? boxGeomId : sphereGeomId;
|
|
43
|
+
const id = batch.addInstance(geomId);
|
|
44
|
+
m.setPosition((x - GRID / 2) * SPACING, 6 + Math.sin(x * 0.7) * 0.5 + Math.cos(z * 0.5) * 0.5, (z - GRID / 2) * SPACING);
|
|
45
|
+
batch.setMatrixAt(id, m);
|
|
46
|
+
count++;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
scene.add(batch);
|
|
50
|
+
window.__batchSpike = {
|
|
51
|
+
instances: count,
|
|
52
|
+
geometries: 2,
|
|
53
|
+
};
|
|
54
|
+
return () => {
|
|
55
|
+
scene.remove(batch);
|
|
56
|
+
batch.dispose();
|
|
57
|
+
box.dispose();
|
|
58
|
+
sphere.dispose();
|
|
59
|
+
material.dispose();
|
|
60
|
+
delete window.__batchSpike;
|
|
61
|
+
};
|
|
62
|
+
}, [scene]);
|
|
63
|
+
return null;
|
|
64
|
+
};
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
type FrameLimiterProps = {
|
|
2
2
|
fps?: number;
|
|
3
|
+
paused?: boolean;
|
|
3
4
|
};
|
|
5
|
+
export type FrameClock = {
|
|
6
|
+
sample: (wallTimeMs: number, intervalMs: number) => number | null;
|
|
7
|
+
step: (seconds: number) => number;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Keeps R3F's manual clock monotonic while each limiter effect owns a fresh
|
|
11
|
+
* wall-time baseline. The first rAF sample establishes that baseline instead
|
|
12
|
+
* of treating the browser's process uptime as elapsed frame time.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createFrameClock(initialTime?: number): FrameClock;
|
|
4
15
|
declare const FrameLimiter: React.FC<FrameLimiterProps>;
|
|
5
16
|
export default FrameLimiter;
|
|
6
17
|
//# sourceMappingURL=frame-limiter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frame-limiter.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/frame-limiter.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"frame-limiter.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/frame-limiter.tsx"],"names":[],"mappings":"AAKA,KAAK,iBAAiB,GAAG;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;IACjE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;CAClC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,SAAI,GAAG,UAAU,CAwB5D;AAgBD,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAoF7C,CAAA;AAED,eAAe,YAAY,CAAA"}
|
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
import { useThree } from '@react-three/fiber';
|
|
2
|
-
import { useLayoutEffect } from 'react';
|
|
2
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
3
|
+
import { timeSpan } from '../../lib/perf-tracks';
|
|
3
4
|
import useViewer from '../../store/use-viewer';
|
|
5
|
+
/**
|
|
6
|
+
* Keeps R3F's manual clock monotonic while each limiter effect owns a fresh
|
|
7
|
+
* wall-time baseline. The first rAF sample establishes that baseline instead
|
|
8
|
+
* of treating the browser's process uptime as elapsed frame time.
|
|
9
|
+
*/
|
|
10
|
+
export function createFrameClock(initialTime = 0) {
|
|
11
|
+
let frameTime = initialTime;
|
|
12
|
+
let previousWallTime = null;
|
|
13
|
+
return {
|
|
14
|
+
sample(wallTimeMs, intervalMs) {
|
|
15
|
+
if (previousWallTime === null) {
|
|
16
|
+
previousWallTime = wallTimeMs;
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const elapsedMs = wallTimeMs - previousWallTime;
|
|
20
|
+
if (elapsedMs < intervalMs)
|
|
21
|
+
return null;
|
|
22
|
+
const remainderMs = elapsedMs % intervalMs;
|
|
23
|
+
frameTime += (elapsedMs - remainderMs) / 1000;
|
|
24
|
+
previousWallTime = wallTimeMs - remainderMs;
|
|
25
|
+
return frameTime;
|
|
26
|
+
},
|
|
27
|
+
step(seconds) {
|
|
28
|
+
frameTime += seconds;
|
|
29
|
+
return frameTime;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
4
33
|
// `?disable=draw` (see post-processing.tsx): the page renders no real frames,
|
|
5
34
|
// and Chromium's no-damage scheduler then throttles requestAnimationFrame to
|
|
6
35
|
// 1Hz on Linux (measured in the headless bake worker — every useFrame system
|
|
@@ -11,23 +40,23 @@ const DRAW_DISABLED = typeof window !== 'undefined' &&
|
|
|
11
40
|
new Set((new URLSearchParams(window.location.search).get('disable') ?? '')
|
|
12
41
|
.split(',')
|
|
13
42
|
.map((s) => s.trim())).has('draw');
|
|
14
|
-
const FrameLimiter = ({ fps = 50 }) => {
|
|
43
|
+
const FrameLimiter = ({ fps = 50, paused = false }) => {
|
|
15
44
|
const { advance, set, frameloop: initFrameloop } = useThree();
|
|
45
|
+
const nextFrameTimeRef = useRef(0);
|
|
16
46
|
const renderer = useThree((state) => state.gl);
|
|
17
47
|
const size = useThree((state) => state.size);
|
|
18
48
|
const dpr = useThree((state) => state.viewport.dpr);
|
|
19
49
|
// Fully covered canvas (e.g. studio gallery) → stop advancing frames
|
|
20
50
|
const renderPaused = useViewer((s) => s.renderPaused);
|
|
21
51
|
useLayoutEffect(() => {
|
|
22
|
-
if (renderPaused)
|
|
52
|
+
if (renderPaused || paused)
|
|
23
53
|
return;
|
|
24
|
-
|
|
25
|
-
let then = 0;
|
|
26
|
-
let i = 0;
|
|
54
|
+
const clock = createFrameClock(nextFrameTimeRef.current);
|
|
27
55
|
let raf = null;
|
|
28
56
|
let timer = null;
|
|
29
57
|
let sizeSynced = false;
|
|
30
|
-
const
|
|
58
|
+
const effectiveFps = Number.isFinite(fps) && fps > 0 ? fps : 50;
|
|
59
|
+
const interval = 1000 / effectiveFps;
|
|
31
60
|
function syncSize() {
|
|
32
61
|
if (sizeSynced)
|
|
33
62
|
return;
|
|
@@ -38,17 +67,17 @@ const FrameLimiter = ({ fps = 50 }) => {
|
|
|
38
67
|
function tick(t) {
|
|
39
68
|
raf = requestAnimationFrame(tick);
|
|
40
69
|
syncSize();
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
70
|
+
const frameTime = clock.sample(t, interval);
|
|
71
|
+
if (frameTime === null)
|
|
72
|
+
return;
|
|
73
|
+
nextFrameTimeRef.current = frameTime;
|
|
74
|
+
timeSpan('frame-cpu', () => advance(frameTime));
|
|
47
75
|
}
|
|
48
76
|
function kick() {
|
|
49
77
|
syncSize();
|
|
50
|
-
|
|
51
|
-
|
|
78
|
+
const frameTime = clock.step(1 / 1000);
|
|
79
|
+
nextFrameTimeRef.current = frameTime;
|
|
80
|
+
timeSpan('frame-cpu', () => advance(frameTime));
|
|
52
81
|
}
|
|
53
82
|
function onVisibilityChange() {
|
|
54
83
|
if (document.visibilityState === 'visible')
|
|
@@ -58,8 +87,9 @@ const FrameLimiter = ({ fps = 50 }) => {
|
|
|
58
87
|
set({ frameloop: 'never' });
|
|
59
88
|
if (DRAW_DISABLED) {
|
|
60
89
|
timer = setInterval(() => {
|
|
61
|
-
|
|
62
|
-
|
|
90
|
+
const frameTime = clock.step(interval / 1000);
|
|
91
|
+
nextFrameTimeRef.current = frameTime;
|
|
92
|
+
timeSpan('frame-cpu', () => advance(frameTime));
|
|
63
93
|
}, interval);
|
|
64
94
|
}
|
|
65
95
|
else {
|
|
@@ -84,7 +114,18 @@ const FrameLimiter = ({ fps = 50 }) => {
|
|
|
84
114
|
window.removeEventListener('pageshow', kick);
|
|
85
115
|
set({ frameloop: initFrameloop });
|
|
86
116
|
};
|
|
87
|
-
}, [
|
|
117
|
+
}, [
|
|
118
|
+
advance,
|
|
119
|
+
dpr,
|
|
120
|
+
fps,
|
|
121
|
+
initFrameloop,
|
|
122
|
+
paused,
|
|
123
|
+
renderPaused,
|
|
124
|
+
renderer,
|
|
125
|
+
set,
|
|
126
|
+
size.height,
|
|
127
|
+
size.width,
|
|
128
|
+
]);
|
|
88
129
|
return null;
|
|
89
130
|
};
|
|
90
131
|
export default FrameLimiter;
|
|
@@ -46,6 +46,19 @@ interface ViewerProps {
|
|
|
46
46
|
* 180-frame cap in ~3.6s — shorter than a cold item-model download).
|
|
47
47
|
*/
|
|
48
48
|
sceneReadyMaxWaitMs?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Frame cap for the render loop, in frames per second. Defaults to 50, the
|
|
51
|
+
* value the viewer has always used.
|
|
52
|
+
*
|
|
53
|
+
* The viewer runs `frameloop="never"` and advances frames itself through
|
|
54
|
+
* `<FrameLimiter>`, so this cap is the only thing setting the cadence and a
|
|
55
|
+
* host cannot raise it from the outside. Hosts that animate the scene on
|
|
56
|
+
* their own clock — a timeline scrubbing node transforms, a walkthrough
|
|
57
|
+
* camera — are pinned to it and cannot reach display refresh, which reads as
|
|
58
|
+
* judder against a 60Hz+ monitor. Raise it for those; lower it to spare the
|
|
59
|
+
* GPU on a passive or background canvas.
|
|
60
|
+
*/
|
|
61
|
+
maxFps?: number;
|
|
49
62
|
/**
|
|
50
63
|
* Skip the TSL post-processing pipeline (SSGI/denoise/ink/outline) and render
|
|
51
64
|
* the scene directly. For headless/capture surfaces (the bake page) where
|
|
@@ -54,6 +67,8 @@ interface ViewerProps {
|
|
|
54
67
|
* `?disable=postFx` diagnostic URL flag, but host-controlled.
|
|
55
68
|
*/
|
|
56
69
|
disablePostFx?: boolean;
|
|
70
|
+
/** Keep the mounted renderer/context warm without advancing scene frames. */
|
|
71
|
+
renderPaused?: boolean;
|
|
57
72
|
}
|
|
58
73
|
/** Imperative handle exposed via `ref` on `<Viewer>`. */
|
|
59
74
|
export type ViewerHandle = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,SAAS,EAMf,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAkB,KAAK,YAAY,EAAsB,MAAM,oBAAoB,CAAA;AAS1F,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAKrC,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAKrE,OAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAatE,OAAuB,EAAwB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAY1F,OAAO,QAAQ,oBAAoB,CAAC;IAOlC,UAAU,aAAa;QACrB,qBAAqB,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,qBAAqB,CAAC,CAAA;KACxE;CACF;AA8QD,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,gBAAgB,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IACvC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,aAAa,CAAC,EAAE;QACd,OAAO,CAAC,EAAE,aAAa,CAAA;QACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;QAClB,WAAW,CAAC,EAAE,WAAW,CAAA;KAC1B,CAAA;IACD;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACtC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,yDAAyD;AACzD,MAAM,MAAM,YAAY,GAAG;IACzB;;;;;OAKG;IACH,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;CAC3C,CAAA;AAED,QAAA,MAAM,MAAM,sGA0PV,CAAA;AAEF,eAAe,MAAM,CAAA"}
|
|
@@ -1,27 +1,36 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { nodeRegistry, StairOpeningSystem, sceneRegistry, useScene, } from '@pascal-app/core';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { nodeRegistry, RoofElevationSystem, StairOpeningSystem, sceneRegistry, useScene, } from '@pascal-app/core';
|
|
4
4
|
import { Canvas, extend, useFrame, useThree } from '@react-three/fiber';
|
|
5
5
|
import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react';
|
|
6
6
|
import * as THREE from 'three/webgpu';
|
|
7
7
|
import { hasDrawableGeometry } from '../../lib/drawable-geometry';
|
|
8
|
-
import { PERF_OVERLAY_ENABLED
|
|
8
|
+
import { PERF_OVERLAY_ENABLED } from '../../lib/gpu-perf';
|
|
9
9
|
import { applyIsolation, clearIsolation } from '../../lib/isolation';
|
|
10
10
|
import { ensureKtx2Support } from '../../lib/ktx2-loader';
|
|
11
|
+
import { choosePointerEvents } from '../../lib/pointer-events';
|
|
12
|
+
import { initializeGpuRenderer } from '../../lib/renderer-capability';
|
|
11
13
|
import { getSceneTheme } from '../../lib/scene-themes';
|
|
12
14
|
import { installTextureNodeNullGuard } from '../../lib/texture-node-guard';
|
|
13
15
|
import useViewer, {} from '../../store/use-viewer';
|
|
14
16
|
import { FloorElevationSystem } from '../../systems/floor-elevation/floor-elevation-system';
|
|
15
17
|
import { GeometrySystem } from '../../systems/geometry/geometry-system';
|
|
18
|
+
import { PerfActionSettleSystem } from '../../systems/perf-action-settle/perf-action-settle-system';
|
|
19
|
+
import { subscribeWallBuildInteractions } from '../../systems/wall/wall-build-lifecycle';
|
|
16
20
|
import { ErrorBoundary } from '../error-boundary';
|
|
17
21
|
import { SceneRenderer } from '../renderers/scene-renderer';
|
|
22
|
+
import { BATCH_SPIKE_ENABLED, BatchedMeshSpike } from './batched-mesh-spike';
|
|
18
23
|
import FrameLimiter from './frame-limiter';
|
|
19
24
|
import { Lights } from './lights';
|
|
20
25
|
import { PerfMonitor } from './perf-monitor';
|
|
26
|
+
import { PerfPanel } from './perf-panel';
|
|
27
|
+
import { PointerRaycastLayers } from './pointer-raycast-layers';
|
|
21
28
|
import PostProcessing, { DEFAULT_HOVER_STYLES } from './post-processing';
|
|
22
29
|
import { RegisteredSystems } from './registered-systems';
|
|
30
|
+
import { useSceneAtmosphere } from './scene-atmosphere';
|
|
23
31
|
import { SceneBvh } from './scene-bvh';
|
|
24
32
|
import { SelectionManager } from './selection-manager';
|
|
33
|
+
import { UnsupportedGpuViewerFallback } from './unsupported-gpu-fallback';
|
|
25
34
|
import { ViewerCamera } from './viewer-camera';
|
|
26
35
|
// Must be in place before any node material builds — a null texture pulled by
|
|
27
36
|
// a shared override-material pass otherwise kills the render pass outright.
|
|
@@ -56,27 +65,6 @@ const DIRTY_BUILD_KINDS = new Set([
|
|
|
56
65
|
'window',
|
|
57
66
|
]);
|
|
58
67
|
const warnedEmptyDraw = process.env.NODE_ENV === 'production' ? null : new WeakSet();
|
|
59
|
-
function canCreateWebGLContext() {
|
|
60
|
-
if (typeof document === 'undefined')
|
|
61
|
-
return false;
|
|
62
|
-
const canvas = document.createElement('canvas');
|
|
63
|
-
try {
|
|
64
|
-
return Boolean(canvas.getContext('webgl2') ?? canvas.getContext('webgl'));
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
function canMountGpuViewer() {
|
|
71
|
-
if (typeof window === 'undefined')
|
|
72
|
-
return false;
|
|
73
|
-
if (!('gpu' in navigator) && !canCreateWebGLContext())
|
|
74
|
-
return false;
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
function UnsupportedGpuViewerFallback() {
|
|
78
|
-
return (_jsx("div", { className: "flex h-full min-h-64 w-full items-center justify-center bg-[#fafafa] p-6 text-center text-neutral-900", children: _jsxs("div", { className: "max-w-md rounded-2xl border border-neutral-200 bg-white p-6 shadow-sm", children: [_jsx("h2", { className: "font-semibold text-lg", children: "3D viewer unavailable" }), _jsx("p", { className: "mt-2 text-neutral-600 text-sm", children: "This browser or environment does not expose WebGPU or WebGL, so Pascal cannot render the 3D scene here. Try opening the editor in a browser with hardware acceleration enabled." })] }) }));
|
|
79
|
-
}
|
|
80
68
|
/**
|
|
81
69
|
* Renderer-level safety net against the empty-vertex-buffer crash.
|
|
82
70
|
*
|
|
@@ -144,12 +132,20 @@ function GPUDeviceWatcher() {
|
|
|
144
132
|
}
|
|
145
133
|
function ToneMappingExposure() {
|
|
146
134
|
const sceneTheme = useViewer((state) => state.sceneTheme);
|
|
135
|
+
const atmosphere = useSceneAtmosphere();
|
|
147
136
|
const gl = useThree((state) => state.gl);
|
|
148
137
|
const invalidate = useThree((state) => state.invalidate);
|
|
149
138
|
useEffect(() => {
|
|
139
|
+
if (atmosphere)
|
|
140
|
+
return;
|
|
150
141
|
gl.toneMappingExposure = getSceneTheme(sceneTheme).toneMappingExposure;
|
|
151
142
|
invalidate();
|
|
152
|
-
}, [gl, invalidate, sceneTheme]);
|
|
143
|
+
}, [atmosphere, gl, invalidate, sceneTheme]);
|
|
144
|
+
useFrame(() => {
|
|
145
|
+
if (!atmosphere)
|
|
146
|
+
return;
|
|
147
|
+
gl.toneMappingExposure = atmosphere.exposure;
|
|
148
|
+
}, -1);
|
|
153
149
|
return null;
|
|
154
150
|
}
|
|
155
151
|
function hasPendingSceneBuildWork() {
|
|
@@ -230,7 +226,7 @@ function SceneReadyTracker({ onSceneReadyChange, sceneReadyKey, sceneReadyMaxWai
|
|
|
230
226
|
}, 10);
|
|
231
227
|
return null;
|
|
232
228
|
}
|
|
233
|
-
const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVER_STYLES, selectionManager = 'default', perf = false, useBvh = true, renderContext = 'editor', transparent, defaultRender, isolate, sceneReadyKey, onSceneReadyChange, sceneReadyMaxWaitMs, disablePostFx = false, }, ref) {
|
|
229
|
+
const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVER_STYLES, selectionManager = 'default', perf = false, useBvh = true, renderContext = 'editor', transparent, defaultRender, isolate, sceneReadyKey, onSceneReadyChange, sceneReadyMaxWaitMs, maxFps = 50, disablePostFx = false, renderPaused = false, }, ref) {
|
|
234
230
|
useEffect(() => {
|
|
235
231
|
if (nodeRegistry.size === 0) {
|
|
236
232
|
console.warn('[viewer] Node registry is empty. Install @pascal-app/nodes and call await loadPlugin(builtinPlugin) before mounting <Viewer>.');
|
|
@@ -253,16 +249,8 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
|
|
|
253
249
|
clearIsolation();
|
|
254
250
|
};
|
|
255
251
|
}, [isolate]);
|
|
252
|
+
const [pointerEvents] = useState(() => choosePointerEvents());
|
|
256
253
|
const [rendererInitFailed, setRendererInitFailed] = useState(false);
|
|
257
|
-
// Capability detection runs after mount. We start optimistic (true) so the
|
|
258
|
-
// server-rendered markup and the first client render agree (no hydration
|
|
259
|
-
// mismatch); the effect flips it to false only on environments that expose
|
|
260
|
-
// neither WebGPU nor WebGL.
|
|
261
|
-
const [canMountViewer, setCanMountViewer] = useState(true);
|
|
262
|
-
useEffect(() => {
|
|
263
|
-
if (!canMountGpuViewer())
|
|
264
|
-
setCanMountViewer(false);
|
|
265
|
-
}, []);
|
|
266
254
|
const isDark = useViewer((state) => getSceneTheme(state.sceneTheme).appearance === 'dark');
|
|
267
255
|
const transparentBackground = useViewer((state) => state.transparentBackground);
|
|
268
256
|
// The shadows toggle drives `renderer.shadowMap.enabled` (via the Canvas
|
|
@@ -317,7 +305,7 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
|
|
|
317
305
|
// fragment-shader cost down — saves another ~30% over 1.5x on high-DPI mobile.
|
|
318
306
|
// Desktops (fine pointer) keep the original 1.5 cap.
|
|
319
307
|
const maxDpr = typeof window !== 'undefined' && window.matchMedia('(pointer: coarse)').matches ? 1.25 : 1.5;
|
|
320
|
-
const showGpuFallback =
|
|
308
|
+
const showGpuFallback = rendererInitFailed;
|
|
321
309
|
// When we can't mount the GPU canvas, the SceneReadyTracker never mounts and
|
|
322
310
|
// the host editor would otherwise wait on its scene-readiness timeout. Signal
|
|
323
311
|
// readiness explicitly so the host can drop its loader immediately.
|
|
@@ -328,52 +316,56 @@ const Viewer = forwardRef(function Viewer({ children, hoverStyles = DEFAULT_HOVE
|
|
|
328
316
|
if (showGpuFallback) {
|
|
329
317
|
return _jsx(UnsupportedGpuViewerFallback, {});
|
|
330
318
|
}
|
|
331
|
-
return (_jsxs(Canvas, { camera: { position: [50, 50, 50], fov: 50 }, className: `transition-colors duration-700 ${transparentBackground ? 'bg-transparent' : isDark ? 'bg-[#1f2433]' : 'bg-[#fafafa]'}`, dpr: [1, maxDpr], frameloop: "never", gl: ((props) => {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
319
|
+
return (_jsxs(_Fragment, { children: [(perf || PERF_OVERLAY_ENABLED) && _jsx(PerfPanel, {}), _jsxs(Canvas, { ref: subscribeWallBuildInteractions, camera: { position: [50, 50, 50], fov: 50 }, className: `transition-colors duration-700 ${transparentBackground ? 'bg-transparent' : isDark ? 'bg-[#1f2433]' : 'bg-[#fafafa]'}`, dpr: [1, maxDpr], events: pointerEvents, frameloop: "never", gl: ((props) => {
|
|
320
|
+
const canvas = props.canvas;
|
|
321
|
+
const cached = canvas ? WEBGPU_RENDERER_CACHE.get(canvas) : undefined;
|
|
322
|
+
if (cached)
|
|
323
|
+
return cached;
|
|
324
|
+
const promise = (async () => {
|
|
325
|
+
const result = await initializeGpuRenderer({
|
|
326
|
+
// Supplying `device` makes three skip its own `requestAdapter`,
|
|
327
|
+
// so R3F's `powerPreference` only reaches the GPU if we forward it.
|
|
328
|
+
powerPreference: props.powerPreference,
|
|
329
|
+
createRenderer: (backendParameters) => {
|
|
330
|
+
const renderer = new THREE.WebGPURenderer({
|
|
331
|
+
...props,
|
|
332
|
+
...backendParameters,
|
|
333
|
+
alpha: true,
|
|
334
|
+
// Allocates the backend's timestamp query pool so
|
|
335
|
+
// `resolveTimestampsAsync()` can report real GPU render-pass
|
|
336
|
+
// time (post-processing.tsx). The backend self-disables it
|
|
337
|
+
// when the device lacks 'timestamp-query'.
|
|
338
|
+
trackTimestamp: PERF_OVERLAY_ENABLED,
|
|
339
|
+
});
|
|
340
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
341
|
+
renderer.toneMappingExposure = getSceneTheme(useViewer.getState().sceneTheme).toneMappingExposure;
|
|
342
|
+
return renderer;
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
if (result.status === 'ready') {
|
|
346
|
+
installEmptyDrawGuard(result.renderer);
|
|
347
|
+
return result.renderer;
|
|
348
|
+
}
|
|
349
|
+
if (canvas)
|
|
350
|
+
WEBGPU_RENDERER_CACHE.delete(canvas);
|
|
351
|
+
console.error('[viewer] WebGPURenderer init failed', result.error);
|
|
352
|
+
setRendererInitFailed(true);
|
|
353
|
+
// Never settles on purpose. Rejecting is what produced
|
|
354
|
+
// MONOREPO-EDITOR-59: R3F awaits this inside its own configure()
|
|
355
|
+
// with no catch, so a rejection surfaces as an unhandled rejection.
|
|
356
|
+
// Resolving is worse still — R3F would call render() on a renderer
|
|
357
|
+
// that has no context. The state update above unmounts this Canvas,
|
|
358
|
+
// which is what releases the pending configure().
|
|
359
|
+
return new Promise(() => undefined);
|
|
360
|
+
})();
|
|
349
361
|
if (canvas)
|
|
350
|
-
WEBGPU_RENDERER_CACHE.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
return promise;
|
|
359
|
-
}), resize: {
|
|
360
|
-
debounce: 100,
|
|
361
|
-
}, shadows: {
|
|
362
|
-
type: THREE.PCFShadowMap,
|
|
363
|
-
enabled: shadowsEnabled,
|
|
364
|
-
}, children: [_jsx(FrameLimiter, { fps: 50 }), _jsx(ViewerCamera, {}), _jsx(GPUDeviceWatcher, {}), _jsx(ToneMappingExposure, {}), _jsx(SceneReadyTracker, { onSceneReadyChange: onSceneReadyChange, sceneReadyKey: sceneReadyKey, sceneReadyMaxWaitMs: sceneReadyMaxWaitMs }), _jsxs(ErrorBoundary, { fallback: null, scope: "viewer-scene", children: [_jsx(Lights, {}), useBvh ? (_jsx(SceneBvh, { children: _jsx(SceneRenderer, {}) })) : (_jsx(SceneRenderer, {})), _jsx(FloorElevationSystem, {}), _jsx(GeometrySystem, {}), _jsx(StairOpeningSystem, {}), _jsx(RegisteredSystems, {}), _jsx(PostProcessing, { disablePostFx: disablePostFx, hoverStyles: hoverStyles }), selectionManager === 'default' && _jsx(SelectionManager, {}), (perf || PERF_OVERLAY_ENABLED) && _jsx(PerfMonitor, {}), children] })] }));
|
|
362
|
+
WEBGPU_RENDERER_CACHE.set(canvas, promise);
|
|
363
|
+
return promise;
|
|
364
|
+
}), resize: {
|
|
365
|
+
debounce: 100,
|
|
366
|
+
}, shadows: {
|
|
367
|
+
type: THREE.PCFShadowMap,
|
|
368
|
+
enabled: shadowsEnabled,
|
|
369
|
+
}, children: [_jsx(FrameLimiter, { fps: maxFps, paused: renderPaused }), _jsx(ViewerCamera, {}), _jsx(PointerRaycastLayers, {}), _jsx(GPUDeviceWatcher, {}), _jsx(ToneMappingExposure, {}), _jsx(SceneReadyTracker, { onSceneReadyChange: onSceneReadyChange, sceneReadyKey: sceneReadyKey, sceneReadyMaxWaitMs: sceneReadyMaxWaitMs }), _jsxs(ErrorBoundary, { fallback: null, scope: "viewer-scene", children: [_jsx(Lights, {}), useBvh ? (_jsx(SceneBvh, { children: _jsx(SceneRenderer, {}) })) : (_jsx(SceneRenderer, {})), _jsx(FloorElevationSystem, {}), _jsx(GeometrySystem, {}), _jsx(StairOpeningSystem, {}), _jsx(RoofElevationSystem, {}), _jsx(RegisteredSystems, {}), _jsx(PostProcessing, { disablePostFx: disablePostFx, hoverStyles: hoverStyles }), selectionManager === 'default' && _jsx(SelectionManager, {}), (perf || PERF_OVERLAY_ENABLED) && _jsx(PerfMonitor, {}), (perf || PERF_OVERLAY_ENABLED) && _jsx(PerfActionSettleSystem, {}), BATCH_SPIKE_ENABLED && _jsx(BatchedMeshSpike, {}), children] })] })] }));
|
|
365
370
|
});
|
|
366
|
-
const DebugRenderer = () => {
|
|
367
|
-
useFrame(({ gl, scene, camera }) => {
|
|
368
|
-
const submittedAt = PERF_OVERLAY_ENABLED ? performance.now() : 0;
|
|
369
|
-
gl.render(scene, camera);
|
|
370
|
-
if (PERF_OVERLAY_ENABLED) {
|
|
371
|
-
const queue = gl.backend?.device?.queue;
|
|
372
|
-
queue?.onSubmittedWorkDone?.().then(() => {
|
|
373
|
-
pushGpuSample(performance.now() - submittedAt);
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
return null;
|
|
378
|
-
};
|
|
379
371
|
export default Viewer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/lights.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lights.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/lights.tsx"],"names":[],"mappings":"AA6EA,wBAAgB,MAAM,gCAqQrB"}
|