@pascal-app/viewer 1.1.0 → 1.1.1
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 +157 -0
- package/dist/components/viewer/drone-controller.d.ts.map +1 -1
- package/dist/components/viewer/drone-controller.js +1 -5
- package/dist/components/viewer/first-person/build-collider-world.d.ts +14 -0
- package/dist/components/viewer/first-person/build-collider-world.d.ts.map +1 -0
- package/dist/components/viewer/first-person/build-collider-world.js +426 -0
- package/dist/components/viewer/first-person/ground-holes.d.ts +22 -0
- package/dist/components/viewer/first-person/ground-holes.d.ts.map +1 -0
- package/dist/components/viewer/first-person/ground-holes.js +116 -0
- package/dist/components/viewer/first-person/mechanism-targets.d.ts +27 -0
- package/dist/components/viewer/first-person/mechanism-targets.d.ts.map +1 -0
- package/dist/components/viewer/first-person/mechanism-targets.js +79 -0
- package/dist/components/viewer/first-person/terrain-collider.d.ts +32 -0
- package/dist/components/viewer/first-person/terrain-collider.d.ts.map +1 -0
- package/dist/components/viewer/first-person/terrain-collider.js +175 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.js +3 -4
- package/dist/components/viewer/live-walkthrough-controller.d.ts +17 -0
- package/dist/components/viewer/live-walkthrough-controller.d.ts.map +1 -0
- package/dist/components/viewer/live-walkthrough-controller.js +1449 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +22 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/lib/daylight/room-map.d.ts +29 -4
- package/dist/lib/daylight/room-map.d.ts.map +1 -1
- package/dist/lib/daylight/room-map.js +219 -36
- package/dist/lib/daylight/room-scene.d.ts.map +1 -1
- package/dist/lib/daylight/room-scene.js +9 -3
- package/dist/lib/daylight/rooms.d.ts +15 -6
- package/dist/lib/daylight/rooms.d.ts.map +1 -1
- package/dist/lib/daylight/rooms.js +49 -9
- package/dist/lib/measurement-presentation.d.ts +2 -0
- package/dist/lib/measurement-presentation.d.ts.map +1 -1
- package/dist/lib/measurement-presentation.js +6 -0
- package/dist/lib/typing-target.d.ts +2 -0
- package/dist/lib/typing-target.d.ts.map +1 -0
- package/dist/lib/typing-target.js +7 -0
- package/dist/lib/viewer-config.d.ts +6 -0
- package/dist/lib/viewer-config.d.ts.map +1 -1
- package/dist/lib/viewer-config.js +2 -0
- package/dist/sfx/audio-settings.d.ts +81 -0
- package/dist/sfx/audio-settings.d.ts.map +1 -0
- package/dist/sfx/audio-settings.js +166 -0
- package/dist/sfx/index.d.ts +6 -0
- package/dist/sfx/index.d.ts.map +1 -0
- package/dist/sfx/index.js +5 -0
- package/dist/sfx/interaction-sounds.d.ts +7 -0
- package/dist/sfx/interaction-sounds.d.ts.map +1 -0
- package/dist/sfx/interaction-sounds.js +39 -0
- package/dist/sfx/player.d.ts +31 -0
- package/dist/sfx/player.d.ts.map +1 -0
- package/dist/sfx/player.js +255 -0
- package/dist/sfx/sounds.d.ts +60 -0
- package/dist/sfx/sounds.d.ts.map +1 -0
- package/dist/sfx/sounds.js +25 -0
- package/dist/sfx/use-viewer-ui-sounds.d.ts +8 -0
- package/dist/sfx/use-viewer-ui-sounds.d.ts.map +1 -0
- package/dist/sfx/use-viewer-ui-sounds.js +42 -0
- package/dist/store/use-first-person-hud.d.ts +13 -0
- package/dist/store/use-first-person-hud.d.ts.map +1 -0
- package/dist/store/use-first-person-hud.js +21 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +27 -13
- package/dist/ui/components/action-button.d.ts +14 -0
- package/dist/ui/components/action-button.d.ts.map +1 -0
- package/dist/ui/components/action-button.js +21 -0
- package/dist/ui/components/action-menu-placement.d.ts +7 -0
- package/dist/ui/components/action-menu-placement.d.ts.map +1 -0
- package/dist/ui/components/action-menu-placement.js +10 -0
- package/dist/ui/components/floorplan-compass-button.d.ts +10 -0
- package/dist/ui/components/floorplan-compass-button.d.ts.map +1 -0
- package/dist/ui/components/floorplan-compass-button.js +22 -0
- package/dist/ui/components/follow-pascal-toggle.d.ts +6 -0
- package/dist/ui/components/follow-pascal-toggle.d.ts.map +1 -0
- package/dist/ui/components/follow-pascal-toggle.js +21 -0
- package/dist/ui/components/localized-html.d.ts +9 -0
- package/dist/ui/components/localized-html.d.ts.map +1 -0
- package/dist/ui/components/localized-html.js +16 -0
- package/dist/ui/components/render-settings-menu.d.ts +9 -0
- package/dist/ui/components/render-settings-menu.d.ts.map +1 -0
- package/dist/ui/components/render-settings-menu.js +145 -0
- package/dist/ui/components/rooms/room-highlight.d.ts +10 -0
- package/dist/ui/components/rooms/room-highlight.d.ts.map +1 -0
- package/dist/ui/components/rooms/room-highlight.js +56 -0
- package/dist/ui/components/rooms/room-labels.d.ts +278 -0
- package/dist/ui/components/rooms/room-labels.d.ts.map +1 -0
- package/dist/ui/components/rooms/room-labels.js +1295 -0
- package/dist/ui/components/rooms/viewer-rooms.d.ts +14 -0
- package/dist/ui/components/rooms/viewer-rooms.d.ts.map +1 -0
- package/dist/ui/components/rooms/viewer-rooms.js +123 -0
- package/dist/ui/components/scene-loader.d.ts +18 -0
- package/dist/ui/components/scene-loader.d.ts.map +1 -0
- package/dist/ui/components/scene-loader.js +29 -0
- package/dist/ui/components/sky-control.d.ts +37 -0
- package/dist/ui/components/sky-control.d.ts.map +1 -0
- package/dist/ui/components/sky-control.js +382 -0
- package/dist/ui/components/use-viewer-camera-navigation-sync.d.ts +4 -0
- package/dist/ui/components/use-viewer-camera-navigation-sync.d.ts.map +1 -0
- package/dist/ui/components/use-viewer-camera-navigation-sync.js +117 -0
- package/dist/ui/components/viewer-controls-bar.d.ts +20 -0
- package/dist/ui/components/viewer-controls-bar.d.ts.map +1 -0
- package/dist/ui/components/viewer-controls-bar.js +130 -0
- package/dist/ui/components/viewer-hover-label.d.ts +14 -0
- package/dist/ui/components/viewer-hover-label.d.ts.map +1 -0
- package/dist/ui/components/viewer-hover-label.js +28 -0
- package/dist/ui/components/viewer-scene-header.d.ts +34 -0
- package/dist/ui/components/viewer-scene-header.d.ts.map +1 -0
- package/dist/ui/components/viewer-scene-header.js +92 -0
- package/dist/ui/components/viewer-selection-manager.d.ts +7 -0
- package/dist/ui/components/viewer-selection-manager.d.ts.map +1 -0
- package/dist/ui/components/viewer-selection-manager.js +116 -0
- package/dist/ui/components/viewer-stage-modes.d.ts +7 -0
- package/dist/ui/components/viewer-stage-modes.d.ts.map +1 -0
- package/dist/ui/components/viewer-stage-modes.js +42 -0
- package/dist/ui/components/viewer-stage-switcher.d.ts +10 -0
- package/dist/ui/components/viewer-stage-switcher.d.ts.map +1 -0
- package/dist/ui/components/viewer-stage-switcher.js +16 -0
- package/dist/ui/components/viewer-stage.d.ts +25 -0
- package/dist/ui/components/viewer-stage.d.ts.map +1 -0
- package/dist/ui/components/viewer-stage.js +107 -0
- package/dist/ui/components/walkthrough-hud.d.ts +29 -0
- package/dist/ui/components/walkthrough-hud.d.ts.map +1 -0
- package/dist/ui/components/walkthrough-hud.js +55 -0
- package/dist/ui/floorplan/floorplan-camera-sync.d.ts +22 -0
- package/dist/ui/floorplan/floorplan-camera-sync.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-camera-sync.js +157 -0
- package/dist/ui/floorplan/floorplan-dimension-renderer.d.ts +61 -0
- package/dist/ui/floorplan/floorplan-dimension-renderer.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-dimension-renderer.js +280 -0
- package/dist/ui/floorplan/floorplan-geometry-renderer.d.ts +54 -0
- package/dist/ui/floorplan/floorplan-geometry-renderer.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-geometry-renderer.js +317 -0
- package/dist/ui/floorplan/floorplan-label-angle.d.ts +24 -0
- package/dist/ui/floorplan/floorplan-label-angle.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-label-angle.js +60 -0
- package/dist/ui/floorplan/floorplan-navigation-sync.d.ts +18 -0
- package/dist/ui/floorplan/floorplan-navigation-sync.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-navigation-sync.js +41 -0
- package/dist/ui/floorplan/floorplan-preview-geometry.d.ts +26 -0
- package/dist/ui/floorplan/floorplan-preview-geometry.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview-geometry.js +173 -0
- package/dist/ui/floorplan/floorplan-preview-navigation.d.ts +22 -0
- package/dist/ui/floorplan/floorplan-preview-navigation.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview-navigation.js +41 -0
- package/dist/ui/floorplan/floorplan-preview-visibility.d.ts +8 -0
- package/dist/ui/floorplan/floorplan-preview-visibility.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview-visibility.js +21 -0
- package/dist/ui/floorplan/floorplan-preview.d.ts +19 -0
- package/dist/ui/floorplan/floorplan-preview.d.ts.map +1 -0
- package/dist/ui/floorplan/floorplan-preview.js +700 -0
- package/dist/ui/hooks/use-mobile.d.ts +2 -0
- package/dist/ui/hooks/use-mobile.d.ts.map +1 -0
- package/dist/ui/hooks/use-mobile.js +14 -0
- package/dist/ui/index.d.ts +49 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +48 -0
- package/dist/ui/lib/below-grade.d.ts +38 -0
- package/dist/ui/lib/below-grade.d.ts.map +1 -0
- package/dist/ui/lib/below-grade.js +102 -0
- package/dist/ui/lib/camera-pose.d.ts +19 -0
- package/dist/ui/lib/camera-pose.d.ts.map +1 -0
- package/dist/ui/lib/camera-pose.js +113 -0
- package/dist/ui/lib/cn.d.ts +3 -0
- package/dist/ui/lib/cn.d.ts.map +1 -0
- package/dist/ui/lib/cn.js +5 -0
- package/dist/ui/lib/level-name.d.ts +10 -0
- package/dist/ui/lib/level-name.d.ts.map +1 -0
- package/dist/ui/lib/level-name.js +33 -0
- package/dist/ui/lib/node-display-label.d.ts +8 -0
- package/dist/ui/lib/node-display-label.d.ts.map +1 -0
- package/dist/ui/lib/node-display-label.js +36 -0
- package/dist/ui/lib/room-area.d.ts +3 -0
- package/dist/ui/lib/room-area.d.ts.map +1 -0
- package/dist/ui/lib/room-area.js +8 -0
- package/dist/ui/lib/room-assembly-overlay.d.ts +46 -0
- package/dist/ui/lib/room-assembly-overlay.d.ts.map +1 -0
- package/dist/ui/lib/room-assembly-overlay.js +222 -0
- package/dist/ui/lib/room-name-messages.d.ts +28 -0
- package/dist/ui/lib/room-name-messages.d.ts.map +1 -0
- package/dist/ui/lib/room-name-messages.js +112 -0
- package/dist/ui/lib/room-selection.d.ts +58 -0
- package/dist/ui/lib/room-selection.d.ts.map +1 -0
- package/dist/ui/lib/room-selection.js +297 -0
- package/dist/ui/lib/scene-theme-name.d.ts +4 -0
- package/dist/ui/lib/scene-theme-name.d.ts.map +1 -0
- package/dist/ui/lib/scene-theme-name.js +22 -0
- package/dist/ui/lib/selection-modifiers.d.ts +20 -0
- package/dist/ui/lib/selection-modifiers.d.ts.map +1 -0
- package/dist/ui/lib/selection-modifiers.js +15 -0
- package/dist/ui/lib/viewer-selection.d.ts +132 -0
- package/dist/ui/lib/viewer-selection.d.ts.map +1 -0
- package/dist/ui/lib/viewer-selection.js +462 -0
- package/dist/ui/lib/zone-content.d.ts +6 -0
- package/dist/ui/lib/zone-content.d.ts.map +1 -0
- package/dist/ui/lib/zone-content.js +96 -0
- package/dist/ui/primitives/button.d.ts +11 -0
- package/dist/ui/primitives/button.d.ts.map +1 -0
- package/dist/ui/primitives/button.js +35 -0
- package/dist/ui/primitives/dropdown-menu.d.ts +26 -0
- package/dist/ui/primitives/dropdown-menu.d.ts.map +1 -0
- package/dist/ui/primitives/dropdown-menu.js +53 -0
- package/dist/ui/primitives/popover.d.ts +8 -0
- package/dist/ui/primitives/popover.d.ts.map +1 -0
- package/dist/ui/primitives/popover.js +17 -0
- package/dist/ui/primitives/switch.d.ts +5 -0
- package/dist/ui/primitives/switch.d.ts.map +1 -0
- package/dist/ui/primitives/switch.js +8 -0
- package/dist/ui/primitives/tooltip.d.ts +8 -0
- package/dist/ui/primitives/tooltip.d.ts.map +1 -0
- package/dist/ui/primitives/tooltip.js +17 -0
- package/dist/ui/store/camera-pose-store.d.ts +17 -0
- package/dist/ui/store/camera-pose-store.d.ts.map +1 -0
- package/dist/ui/store/camera-pose-store.js +15 -0
- package/dist/ui/store/navigation-sync-pose-store.d.ts +26 -0
- package/dist/ui/store/navigation-sync-pose-store.d.ts.map +1 -0
- package/dist/ui/store/navigation-sync-pose-store.js +16 -0
- package/dist/ui/store/use-follow-pascal.d.ts +30 -0
- package/dist/ui/store/use-follow-pascal.d.ts.map +1 -0
- package/dist/ui/store/use-follow-pascal.js +28 -0
- package/dist/ui/theme.css +339 -0
- package/package.json +34 -7
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holes in the site ground's collision geometry.
|
|
3
|
+
*
|
|
4
|
+
* The walkthrough stands on one merged BVH mesh, and the site's ground (a flat slab, or the terrain)
|
|
5
|
+
* is a sheet across the whole lot. Where the house stands over a basement the ground is drawn open
|
|
6
|
+
* (`coveredBelowGradeFootprints`), but the sheet under the stairwell stayed: the player descending
|
|
7
|
+
* the stair met it at grade and could go no deeper. Cutting the collider by the same rings the
|
|
8
|
+
* ground is drawn open over lets the player follow the building's floors and stairs there.
|
|
9
|
+
*
|
|
10
|
+
* Every triangle that reaches a hole is cut against it in plan; the pieces keep the source
|
|
11
|
+
* triangle's plane (height and normal interpolated from its corners) and its facing, so the cut
|
|
12
|
+
* surface is exactly the one the uncut triangle was.
|
|
13
|
+
*/
|
|
14
|
+
import { difference, union } from '@pascal-app/core';
|
|
15
|
+
import { BufferAttribute, BufferGeometry, ShapeUtils, Vector2 } from 'three';
|
|
16
|
+
/** Triangles flatter than this in plan (m²) are vertical faces: they hold nothing up. */
|
|
17
|
+
const MIN_PLAN_AREA = 1e-9;
|
|
18
|
+
/** Twice the signed plan area of `a b c` (x right, z down). */
|
|
19
|
+
function orient(a, b, c) {
|
|
20
|
+
return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* `geometry` (non-indexed, `position` and `normal`, in the frame the rings are in) without the part
|
|
24
|
+
* of it that lies over `holes`. A triangle the clip fails on is kept whole: the ground closed is
|
|
25
|
+
* the safe side. The geometry is returned as it is when there are no holes.
|
|
26
|
+
*/
|
|
27
|
+
export function cutGroundCollider(geometry, holes) {
|
|
28
|
+
if (holes.length === 0)
|
|
29
|
+
return geometry;
|
|
30
|
+
const cutouts = union(holes);
|
|
31
|
+
if (cutouts.length === 0)
|
|
32
|
+
return geometry;
|
|
33
|
+
const boxes = cutouts.map(({ outer }) => ({
|
|
34
|
+
minX: Math.min(...outer.map(([x]) => x)),
|
|
35
|
+
maxX: Math.max(...outer.map(([x]) => x)),
|
|
36
|
+
minZ: Math.min(...outer.map(([, z]) => z)),
|
|
37
|
+
maxZ: Math.max(...outer.map(([, z]) => z)),
|
|
38
|
+
}));
|
|
39
|
+
const position = geometry.getAttribute('position');
|
|
40
|
+
const normal = geometry.getAttribute('normal');
|
|
41
|
+
const positions = [];
|
|
42
|
+
const normals = [];
|
|
43
|
+
const push = (corner) => {
|
|
44
|
+
positions.push(corner.x, corner.y, corner.z);
|
|
45
|
+
normals.push(corner.nx, corner.ny, corner.nz);
|
|
46
|
+
};
|
|
47
|
+
for (let i = 0; i + 2 < position.count; i += 3) {
|
|
48
|
+
const corners = [0, 1, 2].map((k) => ({
|
|
49
|
+
x: position.getX(i + k),
|
|
50
|
+
y: position.getY(i + k),
|
|
51
|
+
z: position.getZ(i + k),
|
|
52
|
+
nx: normal.getX(i + k),
|
|
53
|
+
ny: normal.getY(i + k),
|
|
54
|
+
nz: normal.getZ(i + k),
|
|
55
|
+
}));
|
|
56
|
+
const [a, b, c] = corners;
|
|
57
|
+
const minX = Math.min(a.x, b.x, c.x);
|
|
58
|
+
const maxX = Math.max(a.x, b.x, c.x);
|
|
59
|
+
const minZ = Math.min(a.z, b.z, c.z);
|
|
60
|
+
const maxZ = Math.max(a.z, b.z, c.z);
|
|
61
|
+
const reached = boxes.some((box) => box.maxX > minX && box.minX < maxX && box.maxZ > minZ && box.minZ < maxZ);
|
|
62
|
+
const plan = [
|
|
63
|
+
[a.x, a.z],
|
|
64
|
+
[b.x, b.z],
|
|
65
|
+
[c.x, c.z],
|
|
66
|
+
];
|
|
67
|
+
const determinant = orient(plan[0], plan[1], plan[2]);
|
|
68
|
+
if (!reached || Math.abs(determinant) < MIN_PLAN_AREA) {
|
|
69
|
+
corners.forEach(push);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
let pieces;
|
|
73
|
+
try {
|
|
74
|
+
pieces = difference(plan, cutouts, { throwOnError: true });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
corners.forEach(push);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const at = (x, z) => {
|
|
81
|
+
const wa = ((b.z - c.z) * (x - c.x) + (c.x - b.x) * (z - c.z)) / determinant;
|
|
82
|
+
const wb = ((c.z - a.z) * (x - c.x) + (a.x - c.x) * (z - c.z)) / determinant;
|
|
83
|
+
const wc = 1 - wa - wb;
|
|
84
|
+
const nx = a.nx * wa + b.nx * wb + c.nx * wc;
|
|
85
|
+
const ny = a.ny * wa + b.ny * wb + c.ny * wc;
|
|
86
|
+
const nz = a.nz * wa + b.nz * wb + c.nz * wc;
|
|
87
|
+
const length = Math.hypot(nx, ny, nz) || 1;
|
|
88
|
+
return {
|
|
89
|
+
x,
|
|
90
|
+
y: a.y * wa + b.y * wb + c.y * wc,
|
|
91
|
+
z,
|
|
92
|
+
nx: nx / length,
|
|
93
|
+
ny: ny / length,
|
|
94
|
+
nz: nz / length,
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
for (const piece of pieces) {
|
|
98
|
+
const rings = [piece.outer, ...piece.holes];
|
|
99
|
+
const points = rings.flat();
|
|
100
|
+
const contour = piece.outer.map(([x, z]) => new Vector2(x, z));
|
|
101
|
+
const voids = piece.holes.map((ring) => ring.map(([x, z]) => new Vector2(x, z)));
|
|
102
|
+
for (const [p, q, r] of ShapeUtils.triangulateShape(contour, voids)) {
|
|
103
|
+
const corner = [p, q, r].map((index) => points[index]);
|
|
104
|
+
const swap = Math.sign(orient(corner[0], corner[1], corner[2])) !== Math.sign(determinant);
|
|
105
|
+
const ordered = swap ? [corner[0], corner[2], corner[1]] : corner;
|
|
106
|
+
for (const [x, z] of ordered)
|
|
107
|
+
push(at(x, z));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const cut = new BufferGeometry();
|
|
112
|
+
cut.setAttribute('position', new BufferAttribute(new Float32Array(positions), 3));
|
|
113
|
+
cut.setAttribute('normal', new BufferAttribute(new Float32Array(normals), 3));
|
|
114
|
+
geometry.dispose();
|
|
115
|
+
return cut;
|
|
116
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type AnyNode, type AnyNodeId } from '@pascal-app/core';
|
|
2
|
+
import type { WalkthroughInteract } from '../../../store/use-first-person-hud';
|
|
3
|
+
/**
|
|
4
|
+
* Whether the walkthrough swings this door or window itself, through the native `operationState`
|
|
5
|
+
* its door and window systems draw. One built from a script has no native leaf or sash: a swing
|
|
6
|
+
* would draw nothing, so its clips run through its mechanism, as an authored object's do.
|
|
7
|
+
*/
|
|
8
|
+
export declare function hasNativeLeaf(node: AnyNode | undefined): boolean;
|
|
9
|
+
/** Mounted doors and windows the walkthrough's own door and window targets cover. */
|
|
10
|
+
export declare function nativeOpeningIds(): AnyNodeId[];
|
|
11
|
+
/**
|
|
12
|
+
* Mounted nodes the walkthrough runs through `capabilities.mechanism`, any kind
|
|
13
|
+
* included. `covered` holds the nodes a finer target already tested (procedural
|
|
14
|
+
* parts, catalog item toggles), so no node is targeted twice.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mechanismTargetIds(covered: ReadonlySet<string>): AnyNodeId[];
|
|
17
|
+
/** The walkthrough prompt for a mechanism target: its name and what E does next. */
|
|
18
|
+
export declare function mechanismHudInteract(node: AnyNode): WalkthroughInteract;
|
|
19
|
+
/**
|
|
20
|
+
* Calls `onChange` when a mounted door built from a script opens or closes. The walkthrough's
|
|
21
|
+
* collider follows the door's mechanism, and its model's clip is moved by no tween system that
|
|
22
|
+
* would report the end, as a native door's does: without this the world keeps the leaf that was
|
|
23
|
+
* there when it was last built. It fires at the toggle: the doorway is passable as the leaves
|
|
24
|
+
* start to move, and solid again as they start to close.
|
|
25
|
+
*/
|
|
26
|
+
export declare function watchScriptedDoors(onChange: () => void): () => void;
|
|
27
|
+
//# sourceMappingURL=mechanism-targets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mechanism-targets.d.ts","sourceRoot":"","sources":["../../../../src/components/viewer/first-person/mechanism-targets.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EAOf,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAA;AAE9E;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAEhE;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,IAAI,SAAS,EAAE,CAS9C;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,EAAE,CAU5E;AAED,oFAAoF;AACpF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB,CAMvE;AAcD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CASnE"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { isScriptedNode, nodeMechanism, nodeRegistry, sceneRegistry, useInteractive, useScene, } from '@pascal-app/core';
|
|
2
|
+
/**
|
|
3
|
+
* Whether the walkthrough swings this door or window itself, through the native `operationState`
|
|
4
|
+
* its door and window systems draw. One built from a script has no native leaf or sash: a swing
|
|
5
|
+
* would draw nothing, so its clips run through its mechanism, as an authored object's do.
|
|
6
|
+
*/
|
|
7
|
+
export function hasNativeLeaf(node) {
|
|
8
|
+
return !isScriptedNode(node);
|
|
9
|
+
}
|
|
10
|
+
/** Mounted doors and windows the walkthrough's own door and window targets cover. */
|
|
11
|
+
export function nativeOpeningIds() {
|
|
12
|
+
const nodes = useScene.getState().nodes;
|
|
13
|
+
const ids = [];
|
|
14
|
+
for (const kind of ['door', 'window']) {
|
|
15
|
+
for (const id of sceneRegistry.byType[kind] ?? []) {
|
|
16
|
+
if (hasNativeLeaf(nodes[id]))
|
|
17
|
+
ids.push(id);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return ids;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Mounted nodes the walkthrough runs through `capabilities.mechanism`, any kind
|
|
24
|
+
* included. `covered` holds the nodes a finer target already tested (procedural
|
|
25
|
+
* parts, catalog item toggles), so no node is targeted twice.
|
|
26
|
+
*/
|
|
27
|
+
export function mechanismTargetIds(covered) {
|
|
28
|
+
const nodes = useScene.getState().nodes;
|
|
29
|
+
const ids = [];
|
|
30
|
+
for (const [kind, kindIds] of Object.entries(sceneRegistry.byType)) {
|
|
31
|
+
if (!nodeRegistry.get(kind)?.capabilities.mechanism)
|
|
32
|
+
continue;
|
|
33
|
+
for (const id of kindIds) {
|
|
34
|
+
if (!covered.has(id) && nodeMechanism(nodes[id]))
|
|
35
|
+
ids.push(id);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return ids;
|
|
39
|
+
}
|
|
40
|
+
/** The walkthrough prompt for a mechanism target: its name and what E does next. */
|
|
41
|
+
export function mechanismHudInteract(node) {
|
|
42
|
+
const mechanism = nodeMechanism(node);
|
|
43
|
+
if (!mechanism)
|
|
44
|
+
return null;
|
|
45
|
+
const on = mechanism.isOn(node, useInteractive.getState());
|
|
46
|
+
const verb = mechanism.verb === 'open' ? (on ? 'close' : 'open') : on ? 'turn off' : 'turn on';
|
|
47
|
+
return { label: node.name ?? 'item', verb };
|
|
48
|
+
}
|
|
49
|
+
/** Ids of the mounted doors built from a script that are open, by their mechanism. */
|
|
50
|
+
function openScriptedDoors() {
|
|
51
|
+
const nodes = useScene.getState().nodes;
|
|
52
|
+
const state = useInteractive.getState();
|
|
53
|
+
const open = [];
|
|
54
|
+
for (const id of sceneRegistry.byType.door ?? []) {
|
|
55
|
+
const node = nodes[id];
|
|
56
|
+
if (isScriptedNode(node) && nodeMechanism(node)?.isOn(node, state))
|
|
57
|
+
open.push(id);
|
|
58
|
+
}
|
|
59
|
+
return open.sort().join(',');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Calls `onChange` when a mounted door built from a script opens or closes. The walkthrough's
|
|
63
|
+
* collider follows the door's mechanism, and its model's clip is moved by no tween system that
|
|
64
|
+
* would report the end, as a native door's does: without this the world keeps the leaf that was
|
|
65
|
+
* there when it was last built. It fires at the toggle: the doorway is passable as the leaves
|
|
66
|
+
* start to move, and solid again as they start to close.
|
|
67
|
+
*/
|
|
68
|
+
export function watchScriptedDoors(onChange) {
|
|
69
|
+
let known = openScriptedDoors();
|
|
70
|
+
return useInteractive.subscribe((state, previous) => {
|
|
71
|
+
if (state.items === previous.items)
|
|
72
|
+
return;
|
|
73
|
+
const next = openScriptedDoors();
|
|
74
|
+
if (next === known)
|
|
75
|
+
return;
|
|
76
|
+
known = next;
|
|
77
|
+
onChange();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sculpted ground as walkthrough collision geometry.
|
|
3
|
+
*
|
|
4
|
+
* The first-person collider world is a single merged BVH mesh, and before terrain
|
|
5
|
+
* the ground contributed one flat 2 km box to it. That box is wrong in both
|
|
6
|
+
* directions on a sculpted site: it holds the player up at the datum inside an
|
|
7
|
+
* excavation, and buries them inside a hill. This module replaces it.
|
|
8
|
+
*
|
|
9
|
+
* Two properties it has to hold, because the whole point is that walking agrees
|
|
10
|
+
* with every other consumer of the field:
|
|
11
|
+
*
|
|
12
|
+
* - **The surface is the field**, sampled at every vertex — the same
|
|
13
|
+
* `heightAtSample`/`normalAt` the rendered mesh uses (`terrain-geometry.ts`),
|
|
14
|
+
* so what you see is what you stand on. It is deliberately not a decimated
|
|
15
|
+
* proxy at realistic field sizes.
|
|
16
|
+
* - **It extends past the field**, because `heightAt` clamps to the border
|
|
17
|
+
* outside it. A collider that stopped at the field edge would drop the player
|
|
18
|
+
* into the void one step past the site, which is exactly the bug the flat box
|
|
19
|
+
* existed to prevent. The skirt extrudes each border sample outward at its own
|
|
20
|
+
* height, so the collider and `heightAt` agree everywhere, not just inside.
|
|
21
|
+
*
|
|
22
|
+
* Non-indexed triangles with position + normal, matching what the other collider
|
|
23
|
+
* contributors emit — `mergeGeometries` requires every input to agree on both.
|
|
24
|
+
*/
|
|
25
|
+
import { type TerrainField } from '@pascal-app/core';
|
|
26
|
+
import * as THREE from 'three';
|
|
27
|
+
/**
|
|
28
|
+
* Terrain collision geometry in the site's own frame — the caller applies the
|
|
29
|
+
* site's world matrix, exactly as it did for the flat box it replaces.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createTerrainColliderGeometry(field: TerrainField): THREE.BufferGeometry | null;
|
|
32
|
+
//# sourceMappingURL=terrain-collider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terrain-collider.d.ts","sourceRoot":"","sources":["../../../../src/components/viewer/first-person/terrain-collider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAA4B,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC9E,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA2E9B;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,YAAY,GAAG,KAAK,CAAC,cAAc,GAAG,IAAI,CA0G9F"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sculpted ground as walkthrough collision geometry.
|
|
3
|
+
*
|
|
4
|
+
* The first-person collider world is a single merged BVH mesh, and before terrain
|
|
5
|
+
* the ground contributed one flat 2 km box to it. That box is wrong in both
|
|
6
|
+
* directions on a sculpted site: it holds the player up at the datum inside an
|
|
7
|
+
* excavation, and buries them inside a hill. This module replaces it.
|
|
8
|
+
*
|
|
9
|
+
* Two properties it has to hold, because the whole point is that walking agrees
|
|
10
|
+
* with every other consumer of the field:
|
|
11
|
+
*
|
|
12
|
+
* - **The surface is the field**, sampled at every vertex — the same
|
|
13
|
+
* `heightAtSample`/`normalAt` the rendered mesh uses (`terrain-geometry.ts`),
|
|
14
|
+
* so what you see is what you stand on. It is deliberately not a decimated
|
|
15
|
+
* proxy at realistic field sizes.
|
|
16
|
+
* - **It extends past the field**, because `heightAt` clamps to the border
|
|
17
|
+
* outside it. A collider that stopped at the field edge would drop the player
|
|
18
|
+
* into the void one step past the site, which is exactly the bug the flat box
|
|
19
|
+
* existed to prevent. The skirt extrudes each border sample outward at its own
|
|
20
|
+
* height, so the collider and `heightAt` agree everywhere, not just inside.
|
|
21
|
+
*
|
|
22
|
+
* Non-indexed triangles with position + normal, matching what the other collider
|
|
23
|
+
* contributors emit — `mergeGeometries` requires every input to agree on both.
|
|
24
|
+
*/
|
|
25
|
+
import { heightAtSample, normalAt } from '@pascal-app/core';
|
|
26
|
+
import * as THREE from 'three';
|
|
27
|
+
/**
|
|
28
|
+
* How far past the field the ground keeps holding the player, in metres. Matches
|
|
29
|
+
* the flat box's half-extent so a site's collider bounds do not change size when
|
|
30
|
+
* it gains terrain.
|
|
31
|
+
*/
|
|
32
|
+
const SKIRT_REACH = 1000;
|
|
33
|
+
/**
|
|
34
|
+
* Cell budget before the collider decimates.
|
|
35
|
+
*
|
|
36
|
+
* 66k cells is a 257² field — the largest anything realistic produces, and ~131k
|
|
37
|
+
* triangles, which the BVH builds in well under a frame's worth of budget. The
|
|
38
|
+
* schema allows up to 4097² (a decode-bomb guard, not a real terrain); at that
|
|
39
|
+
* size an exact collider would be 100M triangles, so past the budget this steps
|
|
40
|
+
* the sample grid and the collider becomes a faithful-but-coarser version of the
|
|
41
|
+
* same surface rather than an out-of-memory crash.
|
|
42
|
+
*/
|
|
43
|
+
const MAX_COLLIDER_CELLS = 66_000;
|
|
44
|
+
function colliderStride(field) {
|
|
45
|
+
const cells = Math.max(1, (field.cols - 1) * (field.rows - 1));
|
|
46
|
+
if (cells <= MAX_COLLIDER_CELLS)
|
|
47
|
+
return 1;
|
|
48
|
+
return Math.ceil(Math.sqrt(cells / MAX_COLLIDER_CELLS));
|
|
49
|
+
}
|
|
50
|
+
/** The sample indices the collider walks: every `stride`-th, always including the border. */
|
|
51
|
+
function sampleLine(count, stride) {
|
|
52
|
+
const line = [];
|
|
53
|
+
for (let i = 0; i < count - 1; i += stride)
|
|
54
|
+
line.push(i);
|
|
55
|
+
line.push(count - 1);
|
|
56
|
+
return line;
|
|
57
|
+
}
|
|
58
|
+
class TriangleSink {
|
|
59
|
+
field;
|
|
60
|
+
positions = [];
|
|
61
|
+
normals = [];
|
|
62
|
+
constructor(field) {
|
|
63
|
+
this.field = field;
|
|
64
|
+
}
|
|
65
|
+
push(v) {
|
|
66
|
+
this.positions.push(v.x, v.y, v.z);
|
|
67
|
+
const [nx, ny, nz] = normalAt(this.field, v.x, v.z);
|
|
68
|
+
this.normals.push(nx, ny, nz);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* One cell, given its four corners named by axis order. Winding matches
|
|
72
|
+
* `buildTerrainMesh`: `(p00, p01, p10)` and `(p10, p01, p11)` faces +Y, which is
|
|
73
|
+
* what the spawn resolver's `normal.dot(UP) > 0.2` floor test needs.
|
|
74
|
+
*/
|
|
75
|
+
quad(p00, p10, p01, p11) {
|
|
76
|
+
this.push(p00);
|
|
77
|
+
this.push(p01);
|
|
78
|
+
this.push(p10);
|
|
79
|
+
this.push(p10);
|
|
80
|
+
this.push(p01);
|
|
81
|
+
this.push(p11);
|
|
82
|
+
}
|
|
83
|
+
build() {
|
|
84
|
+
if (this.positions.length === 0)
|
|
85
|
+
return null;
|
|
86
|
+
const geometry = new THREE.BufferGeometry();
|
|
87
|
+
geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(this.positions), 3));
|
|
88
|
+
geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(this.normals), 3));
|
|
89
|
+
return geometry;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Terrain collision geometry in the site's own frame — the caller applies the
|
|
94
|
+
* site's world matrix, exactly as it did for the flat box it replaces.
|
|
95
|
+
*/
|
|
96
|
+
export function createTerrainColliderGeometry(field) {
|
|
97
|
+
if (field.cols < 2 || field.rows < 2)
|
|
98
|
+
return null;
|
|
99
|
+
const stride = colliderStride(field);
|
|
100
|
+
const cols = sampleLine(field.cols, stride);
|
|
101
|
+
const rows = sampleLine(field.rows, stride);
|
|
102
|
+
const sink = new TriangleSink(field);
|
|
103
|
+
const xAt = (col) => field.origin[0] + col * field.spacing;
|
|
104
|
+
const zAt = (row) => field.origin[1] + row * field.spacing;
|
|
105
|
+
const at = (col, row) => ({
|
|
106
|
+
x: xAt(col),
|
|
107
|
+
y: heightAtSample(field, col, row),
|
|
108
|
+
z: zAt(row),
|
|
109
|
+
});
|
|
110
|
+
for (let ri = 0; ri < rows.length - 1; ri++) {
|
|
111
|
+
const r0 = rows[ri];
|
|
112
|
+
const r1 = rows[ri + 1];
|
|
113
|
+
for (let ci = 0; ci < cols.length - 1; ci++) {
|
|
114
|
+
const c0 = cols[ci];
|
|
115
|
+
const c1 = cols[ci + 1];
|
|
116
|
+
sink.quad(at(c0, r0), at(c1, r0), at(c0, r1), at(c1, r1));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// The skirt: each border sample extruded outward at its own height, so stepping
|
|
120
|
+
// off the field lands on the border height rather than on nothing. This is the
|
|
121
|
+
// collider spelling of `heightAt`'s edge clamp.
|
|
122
|
+
const firstCol = 0;
|
|
123
|
+
const lastCol = field.cols - 1;
|
|
124
|
+
const firstRow = 0;
|
|
125
|
+
const lastRow = field.rows - 1;
|
|
126
|
+
const westX = xAt(firstCol) - SKIRT_REACH;
|
|
127
|
+
const eastX = xAt(lastCol) + SKIRT_REACH;
|
|
128
|
+
const northZ = zAt(firstRow) - SKIRT_REACH;
|
|
129
|
+
const southZ = zAt(lastRow) + SKIRT_REACH;
|
|
130
|
+
for (let ci = 0; ci < cols.length - 1; ci++) {
|
|
131
|
+
const c0 = cols[ci];
|
|
132
|
+
const c1 = cols[ci + 1];
|
|
133
|
+
const north0 = at(c0, firstRow);
|
|
134
|
+
const north1 = at(c1, firstRow);
|
|
135
|
+
sink.quad({ x: north0.x, y: north0.y, z: northZ }, { x: north1.x, y: north1.y, z: northZ }, north0, north1);
|
|
136
|
+
const south0 = at(c0, lastRow);
|
|
137
|
+
const south1 = at(c1, lastRow);
|
|
138
|
+
sink.quad(south0, south1, { x: south0.x, y: south0.y, z: southZ }, {
|
|
139
|
+
x: south1.x,
|
|
140
|
+
y: south1.y,
|
|
141
|
+
z: southZ,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
for (let ri = 0; ri < rows.length - 1; ri++) {
|
|
145
|
+
const r0 = rows[ri];
|
|
146
|
+
const r1 = rows[ri + 1];
|
|
147
|
+
const west0 = at(firstCol, r0);
|
|
148
|
+
const west1 = at(firstCol, r1);
|
|
149
|
+
sink.quad({ x: westX, y: west0.y, z: west0.z }, west0, { x: westX, y: west1.y, z: west1.z }, west1);
|
|
150
|
+
const east0 = at(lastCol, r0);
|
|
151
|
+
const east1 = at(lastCol, r1);
|
|
152
|
+
sink.quad(east0, { x: eastX, y: east0.y, z: east0.z }, east1, {
|
|
153
|
+
x: eastX,
|
|
154
|
+
y: east1.y,
|
|
155
|
+
z: east1.z,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// Corner quads, flat at the corner sample's height — the diagonal region no edge
|
|
159
|
+
// strip covers, and where `heightAt` also reports the corner value.
|
|
160
|
+
const corners = [
|
|
161
|
+
[at(firstCol, firstRow), westX, northZ],
|
|
162
|
+
[at(lastCol, firstRow), eastX, northZ],
|
|
163
|
+
[at(firstCol, lastRow), westX, southZ],
|
|
164
|
+
[at(lastCol, lastRow), eastX, southZ],
|
|
165
|
+
];
|
|
166
|
+
for (const [corner, farX, farZ] of corners) {
|
|
167
|
+
const minX = Math.min(corner.x, farX);
|
|
168
|
+
const maxX = Math.max(corner.x, farX);
|
|
169
|
+
const minZ = Math.min(corner.z, farZ);
|
|
170
|
+
const maxZ = Math.max(corner.z, farZ);
|
|
171
|
+
const y = corner.y;
|
|
172
|
+
sink.quad({ x: minX, y, z: minZ }, { x: maxX, y, z: minZ }, { x: minX, y, z: maxZ }, { x: maxX, y, z: maxZ });
|
|
173
|
+
}
|
|
174
|
+
return sink.build();
|
|
175
|
+
}
|
|
@@ -9,7 +9,7 @@ export declare const CROUCH_RUN_SPEED = 1.4;
|
|
|
9
9
|
export declare const STAND_CLEARANCE = 1.25;
|
|
10
10
|
export declare const EYE_LERP_SPEED = 12;
|
|
11
11
|
/**
|
|
12
|
-
* First-person walkthrough controller for the baked GLB.
|
|
12
|
+
* First-person walkthrough controller for the baked GLB. Uses the shared
|
|
13
13
|
* `BVHEcctrl` capsule character controller (gravity, jump, sprint, ground-float,
|
|
14
14
|
* mesh collision) fed a collider built from the artifact's own geometry — so the
|
|
15
15
|
* baked viewer walks the building with the same physics as the editor, without
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glb-walkthrough-controller.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-walkthrough-controller.tsx"],"names":[],"mappings":"AAkCA,eAAO,MAAM,eAAe,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"glb-walkthrough-controller.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/glb-walkthrough-controller.tsx"],"names":[],"mappings":"AAkCA,eAAO,MAAM,eAAe,KAAK,CAAA;AAiBjC,eAAO,MAAM,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAqB,CAAA;AAChF,eAAO,MAAM,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAqB,CAAA;AACjF,eAAO,MAAM,kBAAkB,MAAM,CAAA;AACrC,eAAO,MAAM,mBAAmB,OAAO,CAAA;AACvC,eAAO,MAAM,iBAAiB,MAAM,CAAA;AACpC,eAAO,MAAM,iBAAiB,IAAI,CAAA;AAClC,eAAO,MAAM,gBAAgB,MAAM,CAAA;AAInC,eAAO,MAAM,eAAe,OAAO,CAAA;AACnC,eAAO,MAAM,cAAc,KAAK,CAAA;AAmMhC;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,GAAG,EAAE,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,sCA0PhE"}
|
|
@@ -16,8 +16,7 @@ import { applyWalkthroughCameraClipping } from './viewer-camera';
|
|
|
16
16
|
// awareness without wide-angle distortion. Applied only while walking — both
|
|
17
17
|
// walkthrough controllers read this and restore the orbit FOV on exit.
|
|
18
18
|
export const WALKTHROUGH_FOV = 60;
|
|
19
|
-
//
|
|
20
|
-
// baked walkthrough feels identical. The capsule centre sits below the eye; the
|
|
19
|
+
// Shared with the live walkthrough so baked and parametric scenes feel identical. The capsule centre sits below the eye; the
|
|
21
20
|
// camera rides the capsule with a small offset and the controller floats it to
|
|
22
21
|
// the ground.
|
|
23
22
|
const CAMERA_EYE_OFFSET = 0.45;
|
|
@@ -109,7 +108,7 @@ function boxFloorGeometry(cx, topY, cz, width, depth) {
|
|
|
109
108
|
// Fallback ground so the player never falls into the void: a single large
|
|
110
109
|
// ground plane at the lowest level (level 0). Upper levels rely on their own
|
|
111
110
|
// baked slabs — a slab-less upper floor lets you fall down to the ground, which
|
|
112
|
-
// the ground plane catches. (Mirrors the
|
|
111
|
+
// the ground plane catches. (Mirrors the live walkthrough's site ground.)
|
|
113
112
|
function addFallbackFloors(scene, geometries) {
|
|
114
113
|
const sceneBounds = new Box3();
|
|
115
114
|
for (const geometry of geometries) {
|
|
@@ -224,7 +223,7 @@ function resolveGlbSpawn(scene) {
|
|
|
224
223
|
};
|
|
225
224
|
}
|
|
226
225
|
/**
|
|
227
|
-
* First-person walkthrough controller for the baked GLB.
|
|
226
|
+
* First-person walkthrough controller for the baked GLB. Uses the shared
|
|
228
227
|
* `BVHEcctrl` capsule character controller (gravity, jump, sprint, ground-float,
|
|
229
228
|
* mesh collision) fed a collider built from the artifact's own geometry — so the
|
|
230
229
|
* baked viewer walks the building with the same physics as the editor, without
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type LiveWalkthroughControllerProps = {
|
|
2
|
+
/** Freeze movement and look while a host renders a still. */
|
|
3
|
+
paused?: boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Whether the host owns camera FOV right now (e.g. a capture rig). Read when walkthrough
|
|
6
|
+
* starts and when it ends, so the answer at teardown decides whether the orbit FOV returns.
|
|
7
|
+
*/
|
|
8
|
+
externalFov?: () => boolean;
|
|
9
|
+
/** Allow locked-pointer clicks to operate the targeted object (E/R still work). */
|
|
10
|
+
pointerInteraction?: boolean;
|
|
11
|
+
/** Keep walkthrough active when pointer lock is lost; Escape with a free cursor exits. */
|
|
12
|
+
suspendOnUnlock?: boolean;
|
|
13
|
+
/** Called after walkthroughMode is cleared, including on Escape or pointer unlock. */
|
|
14
|
+
onExit?: () => void;
|
|
15
|
+
};
|
|
16
|
+
export declare function LiveWalkthroughController(props: LiveWalkthroughControllerProps): import("react").JSX.Element | null;
|
|
17
|
+
//# sourceMappingURL=live-walkthrough-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-walkthrough-controller.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/live-walkthrough-controller.tsx"],"names":[],"mappings":"AA8uBA,MAAM,MAAM,8BAA8B,GAAG;IAC3C,6DAA6D;IAC7D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,OAAO,CAAA;IAC3B,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,0FAA0F;IAC1F,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAKD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,8BAA8B,sCAG9E"}
|