@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
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BufferGeometry, Vector2 } from 'three';
|
|
2
|
+
export declare function panelProfile(leafDepth: number, panelDepth: number, panelInset: number, width: number, height: number): {
|
|
3
|
+
inset: number;
|
|
4
|
+
depth: number;
|
|
5
|
+
}[];
|
|
6
|
+
/** One closed panel: inward sticking, a recessed field and an optional raised centre, on both faces. */
|
|
7
|
+
export declare function buildPanelField(contour: Vector2[], leafDepth: number, panelDepth: number, panelInset: number, flushPullAt?: {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
}): BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
|
|
11
|
+
//# sourceMappingURL=leaf-profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaf-profile.d.ts","sourceRoot":"","sources":["../../../src/systems/door/leaf-profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAsC,OAAO,EAAW,MAAM,OAAO,CAAA;AAE5F,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM;;;IAyBf;AAED,wGAAwG;AACxG,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAAE,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,kGAyGvC"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { BufferGeometry, Float32BufferAttribute, ShapeUtils, Vector2, Vector3 } from 'three';
|
|
2
|
+
export function panelProfile(leafDepth, panelDepth, panelInset, width, height) {
|
|
3
|
+
const fieldDepth = Math.min(panelDepth > 0 ? Math.max(0.006, Math.min(0.012, panelDepth)) : Math.min(-panelDepth, 0.012), Math.max(0, (leafDepth - 0.002) / 2));
|
|
4
|
+
// Narrow custom openings must leave room for a centre instead of crossing their offset rings.
|
|
5
|
+
const stickingWidth = Math.min(Math.max(0.012, Math.min(0.02, panelInset / 2)), width / 4, height / 4);
|
|
6
|
+
const profile = [
|
|
7
|
+
{ inset: 0, depth: 0 },
|
|
8
|
+
{ inset: stickingWidth / 2, depth: -fieldDepth * 0.35 },
|
|
9
|
+
{ inset: stickingWidth, depth: -fieldDepth },
|
|
10
|
+
];
|
|
11
|
+
if (panelDepth > 0) {
|
|
12
|
+
const plateauDepth = Math.min(0.002, fieldDepth);
|
|
13
|
+
profile.push({
|
|
14
|
+
inset: stickingWidth + Math.min(fieldDepth - plateauDepth, stickingWidth * 0.9),
|
|
15
|
+
depth: -plateauDepth,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return profile;
|
|
19
|
+
}
|
|
20
|
+
/** One closed panel: inward sticking, a recessed field and an optional raised centre, on both faces. */
|
|
21
|
+
export function buildPanelField(contour, leafDepth, panelDepth, panelInset, flushPullAt) {
|
|
22
|
+
if (contour.length > 1 && contour[0].equals(contour[contour.length - 1]))
|
|
23
|
+
contour = contour.slice(0, -1);
|
|
24
|
+
const outer = contour.filter((point, i) => {
|
|
25
|
+
const previous = contour[(i + contour.length - 1) % contour.length];
|
|
26
|
+
const next = contour[(i + 1) % contour.length];
|
|
27
|
+
return Math.abs(point.clone().sub(previous).cross(next.clone().sub(point))) > 1e-9;
|
|
28
|
+
});
|
|
29
|
+
if (ShapeUtils.isClockWise(outer))
|
|
30
|
+
outer.reverse();
|
|
31
|
+
const xs = outer.map((p) => p.x);
|
|
32
|
+
const ys = outer.map((p) => p.y);
|
|
33
|
+
const profile = panelProfile(leafDepth, panelDepth, panelInset, Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys));
|
|
34
|
+
const offsets = outer.map((point, i) => {
|
|
35
|
+
const incoming = point
|
|
36
|
+
.clone()
|
|
37
|
+
.sub(outer[(i + outer.length - 1) % outer.length])
|
|
38
|
+
.normalize();
|
|
39
|
+
const outgoing = outer[(i + 1) % outer.length].clone().sub(point).normalize();
|
|
40
|
+
const n1 = new Vector2(-incoming.y, incoming.x);
|
|
41
|
+
const n2 = new Vector2(-outgoing.y, outgoing.x);
|
|
42
|
+
const scale = 1 / (1 + n1.dot(n2));
|
|
43
|
+
return n1.add(n2).multiplyScalar(scale);
|
|
44
|
+
});
|
|
45
|
+
const rings = profile.map(({ inset, depth }) => ({
|
|
46
|
+
contour: outer.map((point, i) => point.clone().addScaledVector(offsets[i], inset)),
|
|
47
|
+
depth,
|
|
48
|
+
}));
|
|
49
|
+
const positions = [];
|
|
50
|
+
const uvs = [];
|
|
51
|
+
const triangle = (a, b, c, u = new Vector3(1, 0, 0), v = new Vector3(0, 1, 0)) => {
|
|
52
|
+
const polygons = flushPullAt ? outsidePull([a, b, c], flushPullAt) : [[a, b, c]];
|
|
53
|
+
for (const polygon of polygons)
|
|
54
|
+
for (let i = 1; i < polygon.length - 1; i++) {
|
|
55
|
+
const points = [polygon[0], polygon[i], polygon[i + 1]];
|
|
56
|
+
if (points[1].clone().sub(points[0]).cross(points[2].clone().sub(points[0])).lengthSq() <
|
|
57
|
+
1e-20)
|
|
58
|
+
continue;
|
|
59
|
+
for (const p of points) {
|
|
60
|
+
positions.push(p.x, p.y, p.z);
|
|
61
|
+
uvs.push(p.dot(u), p.dot(v));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const point3 = (p, z) => new Vector3(p.x, p.y, z);
|
|
66
|
+
for (const face of [-1, 1]) {
|
|
67
|
+
const { contour: inner, depth } = rings[rings.length - 1];
|
|
68
|
+
const z = face * (leafDepth / 2 + depth);
|
|
69
|
+
for (const [a, b, c] of ShapeUtils.triangulateShape(inner, [])) {
|
|
70
|
+
const points = [point3(inner[a], z), point3(inner[b], z), point3(inner[c], z)];
|
|
71
|
+
if (face < 0)
|
|
72
|
+
points.reverse();
|
|
73
|
+
triangle(points[0], points[1], points[2]);
|
|
74
|
+
}
|
|
75
|
+
for (let ring = 1; ring < rings.length; ring++) {
|
|
76
|
+
const outside = rings[ring - 1];
|
|
77
|
+
const inside = rings[ring];
|
|
78
|
+
for (let i = 0; i < outer.length; i++) {
|
|
79
|
+
const j = (i + 1) % outer.length;
|
|
80
|
+
const a = point3(outside.contour[i], face * (leafDepth / 2 + outside.depth));
|
|
81
|
+
const b = point3(outside.contour[j], face * (leafDepth / 2 + outside.depth));
|
|
82
|
+
const c = point3(inside.contour[j], face * (leafDepth / 2 + inside.depth));
|
|
83
|
+
const d = point3(inside.contour[i], face * (leafDepth / 2 + inside.depth));
|
|
84
|
+
const u = b.clone().sub(a).normalize();
|
|
85
|
+
const normal = b.clone().sub(a).cross(d.clone().sub(a)).normalize();
|
|
86
|
+
const v = normal.clone().cross(u);
|
|
87
|
+
if (face > 0) {
|
|
88
|
+
triangle(a, b, c, u, v);
|
|
89
|
+
triangle(a, c, d, u, v);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
triangle(a, c, b, u, v);
|
|
93
|
+
triangle(a, d, c, u, v);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (let i = 0; i < outer.length; i++) {
|
|
99
|
+
const j = (i + 1) % outer.length;
|
|
100
|
+
const a = point3(outer[i], -leafDepth / 2);
|
|
101
|
+
const b = point3(outer[j], -leafDepth / 2);
|
|
102
|
+
const c = point3(outer[j], leafDepth / 2);
|
|
103
|
+
const d = point3(outer[i], leafDepth / 2);
|
|
104
|
+
const u = b.clone().sub(a).normalize();
|
|
105
|
+
const v = new Vector3(0, 0, 1);
|
|
106
|
+
triangle(a, b, c, u, v);
|
|
107
|
+
triangle(a, c, d, u, v);
|
|
108
|
+
}
|
|
109
|
+
const geometry = new BufferGeometry();
|
|
110
|
+
geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
|
|
111
|
+
geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2));
|
|
112
|
+
geometry.computeVertexNormals();
|
|
113
|
+
return geometry;
|
|
114
|
+
}
|
|
115
|
+
// Clip each field triangle around the same through-hole the stile boxes leave for the flush cup.
|
|
116
|
+
function outsidePull(triangle, pull) {
|
|
117
|
+
const clip = (polygon, axis, edge, sign) => {
|
|
118
|
+
const result = [];
|
|
119
|
+
for (let i = 0; i < polygon.length; i++) {
|
|
120
|
+
const a = polygon[i];
|
|
121
|
+
const b = polygon[(i + 1) % polygon.length];
|
|
122
|
+
const da = sign * (a[axis] - edge);
|
|
123
|
+
const db = sign * (b[axis] - edge);
|
|
124
|
+
if (da >= 0)
|
|
125
|
+
result.push(a);
|
|
126
|
+
if (da >= 0 !== db >= 0)
|
|
127
|
+
result.push(a.clone().lerp(b, da / (da - db)));
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
};
|
|
131
|
+
const left = pull.x - 0.02;
|
|
132
|
+
const right = pull.x + 0.02;
|
|
133
|
+
const middle = clip(clip(triangle, 'x', left, 1), 'x', right, -1);
|
|
134
|
+
return [
|
|
135
|
+
clip(triangle, 'x', left, -1),
|
|
136
|
+
clip(triangle, 'x', right, 1),
|
|
137
|
+
clip(middle, 'y', pull.y - 0.06, -1),
|
|
138
|
+
clip(middle, 'y', pull.y + 0.06, 1),
|
|
139
|
+
];
|
|
140
|
+
}
|
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type AnyNodeId } from '@pascal-app/core';
|
|
2
|
+
import { type Object3D, Raycaster } from 'three';
|
|
3
|
+
type ElevatorButtonAction = 'open-door' | 'request-level';
|
|
4
|
+
type ElevatorButtonUserData = {
|
|
5
|
+
action: ElevatorButtonAction;
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
elevatorId: AnyNodeId;
|
|
8
|
+
kind: 'cab' | 'landing';
|
|
9
|
+
levelId?: AnyNodeId;
|
|
10
|
+
};
|
|
2
11
|
/**
|
|
3
12
|
* The roots of every elevator button under `object`. Any button along the ray
|
|
4
13
|
* takes the click, so only they are raycast — never the rest of the scene,
|
|
5
14
|
* whose meshes this capture-phase listener must not trip over.
|
|
6
15
|
*/
|
|
7
16
|
export declare function collectElevatorButtons(object: Object3D, into?: Object3D[]): Object3D[];
|
|
8
|
-
|
|
17
|
+
/** Ignore malformed material groups during transient paint updates, rather than raycasting invalid faces. */
|
|
18
|
+
export declare function pickElevatorButton(scene: Object3D, raycaster: Raycaster): ElevatorButtonUserData | null;
|
|
19
|
+
export declare function ElevatorInteractionSystem({ enabled }: {
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
}): null;
|
|
22
|
+
export {};
|
|
9
23
|
//# sourceMappingURL=elevator-interaction-system.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elevator-interaction-system.d.ts","sourceRoot":"","sources":["../../../src/systems/elevator/elevator-interaction-system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"elevator-interaction-system.d.ts","sourceRoot":"","sources":["../../../src/systems/elevator/elevator-interaction-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EAMf,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAAQ,KAAK,QAAQ,EAAE,SAAS,EAAW,MAAM,OAAO,CAAA;AAE/D,KAAK,oBAAoB,GAAG,WAAW,GAAG,eAAe,CAAA;AAEzD,KAAK,sBAAsB,GAAG;IAC5B,MAAM,EAAE,oBAAoB,CAAA;IAC5B,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,SAAS,CAAA;IACrB,IAAI,EAAE,KAAK,GAAG,SAAS,CAAA;IACvB,OAAO,CAAC,EAAE,SAAS,CAAA;CACpB,CAAA;AAcD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAE,QAAQ,EAAO,GAAG,QAAQ,EAAE,CAQ1F;AAED,6GAA6G;AAC7G,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,QAAQ,EACf,SAAS,EAAE,SAAS,GACnB,sBAAsB,GAAG,IAAI,CAoC/B;AAED,wBAAgB,yBAAyB,CAAC,EAAE,OAAc,EAAE,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,QA0DlF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { openElevatorDoor, requestElevatorLevel, resolveElevatorDispatchTarget, useInteractive, useScene, } from '@pascal-app/core';
|
|
2
2
|
import { useThree } from '@react-three/fiber';
|
|
3
3
|
import { useEffect, useRef } from 'react';
|
|
4
|
-
import { Raycaster, Vector2 } from 'three';
|
|
4
|
+
import { Mesh, Raycaster, Vector2 } from 'three';
|
|
5
5
|
function getElevatorButtonData(object) {
|
|
6
6
|
let current = object;
|
|
7
7
|
while (current) {
|
|
@@ -18,6 +18,8 @@ function getElevatorButtonData(object) {
|
|
|
18
18
|
* whose meshes this capture-phase listener must not trip over.
|
|
19
19
|
*/
|
|
20
20
|
export function collectElevatorButtons(object, into = []) {
|
|
21
|
+
if (!object.visible)
|
|
22
|
+
return into;
|
|
21
23
|
if (object.userData.elevatorButton) {
|
|
22
24
|
into.push(object);
|
|
23
25
|
return into;
|
|
@@ -26,7 +28,41 @@ export function collectElevatorButtons(object, into = []) {
|
|
|
26
28
|
collectElevatorButtons(child, into);
|
|
27
29
|
return into;
|
|
28
30
|
}
|
|
29
|
-
|
|
31
|
+
/** Ignore malformed material groups during transient paint updates, rather than raycasting invalid faces. */
|
|
32
|
+
export function pickElevatorButton(scene, raycaster) {
|
|
33
|
+
const buttons = collectElevatorButtons(scene);
|
|
34
|
+
const faces = [];
|
|
35
|
+
for (const button of buttons)
|
|
36
|
+
button.traverseVisible((object) => {
|
|
37
|
+
if (object instanceof Mesh)
|
|
38
|
+
faces.push(object);
|
|
39
|
+
});
|
|
40
|
+
const hit = raycaster.intersectObjects(faces, false)[0];
|
|
41
|
+
if (!hit)
|
|
42
|
+
return null;
|
|
43
|
+
const blockers = [];
|
|
44
|
+
scene.traverseVisible((object) => {
|
|
45
|
+
if (!(object instanceof Mesh) ||
|
|
46
|
+
getElevatorButtonData(object) ||
|
|
47
|
+
object.userData.pascalExport === 'strip')
|
|
48
|
+
return;
|
|
49
|
+
if (Array.isArray(object.material) &&
|
|
50
|
+
object.geometry.groups.some((group) => !object.material[group.materialIndex ?? 0] || !Number.isFinite(group.count)))
|
|
51
|
+
return;
|
|
52
|
+
blockers.push(object);
|
|
53
|
+
});
|
|
54
|
+
const occluded = raycaster.intersectObjects(blockers, false).some((intersection) => {
|
|
55
|
+
if (intersection.distance >= hit.distance - 0.001)
|
|
56
|
+
return false;
|
|
57
|
+
const mesh = intersection.object;
|
|
58
|
+
const material = Array.isArray(mesh.material)
|
|
59
|
+
? mesh.material[intersection.face?.materialIndex ?? 0]
|
|
60
|
+
: mesh.material;
|
|
61
|
+
return material?.visible && (!material.transparent || material.opacity >= 0.95);
|
|
62
|
+
});
|
|
63
|
+
return occluded ? null : getElevatorButtonData(hit.object);
|
|
64
|
+
}
|
|
65
|
+
export function ElevatorInteractionSystem({ enabled = true }) {
|
|
30
66
|
const camera = useThree((state) => state.camera);
|
|
31
67
|
const gl = useThree((state) => state.gl);
|
|
32
68
|
const scene = useThree((state) => state.scene);
|
|
@@ -35,7 +71,7 @@ export function ElevatorInteractionSystem() {
|
|
|
35
71
|
useEffect(() => {
|
|
36
72
|
const canvas = gl.domElement;
|
|
37
73
|
const handlePointerDown = (event) => {
|
|
38
|
-
if (event.button !== 0)
|
|
74
|
+
if (event.button !== 0 || !enabled)
|
|
39
75
|
return;
|
|
40
76
|
const buttons = collectElevatorButtons(scene);
|
|
41
77
|
if (buttons.length === 0)
|
|
@@ -46,10 +82,7 @@ export function ElevatorInteractionSystem() {
|
|
|
46
82
|
pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
|
47
83
|
const raycaster = raycasterRef.current;
|
|
48
84
|
raycaster.setFromCamera(pointer, camera);
|
|
49
|
-
const button = raycaster
|
|
50
|
-
.intersectObjects(buttons, true)
|
|
51
|
-
.map((intersection) => getElevatorButtonData(intersection.object))
|
|
52
|
-
.find((data) => data !== null);
|
|
85
|
+
const button = pickElevatorButton(scene, raycaster);
|
|
53
86
|
if (!button || button.disabled)
|
|
54
87
|
return;
|
|
55
88
|
event.preventDefault();
|
|
@@ -74,6 +107,6 @@ export function ElevatorInteractionSystem() {
|
|
|
74
107
|
return () => {
|
|
75
108
|
canvas.removeEventListener('pointerdown', handlePointerDown, true);
|
|
76
109
|
};
|
|
77
|
-
}, [camera, gl, scene]);
|
|
110
|
+
}, [camera, gl, scene, enabled]);
|
|
78
111
|
return null;
|
|
79
112
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type AnyNodeId } from '@pascal-app/core';
|
|
2
|
+
export type GeometryFailure = 'build-failed' | 'invalid-geometry';
|
|
3
|
+
/** Geometry failures describe the current rendering, never saved building data. */
|
|
4
|
+
export declare const useGeometryStatus: import("zustand").UseBoundStore<import("zustand").StoreApi<{
|
|
5
|
+
failures: ReadonlyMap<AnyNodeId, GeometryFailure>;
|
|
6
|
+
}>>;
|
|
7
|
+
export declare function setGeometryFailure(id: AnyNodeId, message: GeometryFailure | null): void;
|
|
8
|
+
//# sourceMappingURL=geometry-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geometry-status.d.ts","sourceRoot":"","sources":["../../../src/systems/geometry/geometry-status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,kBAAkB,CAAA;AAG3D,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,kBAAkB,CAAA;AAEjE,mFAAmF;AACnF,eAAO,MAAM,iBAAiB;cAAsB,WAAW,CAAC,SAAS,EAAE,eAAe,CAAC;GAI1F,CAAA;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,QAOhF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useScene } from '@pascal-app/core';
|
|
2
|
+
import { create } from 'zustand';
|
|
3
|
+
/** Geometry failures describe the current rendering, never saved building data. */
|
|
4
|
+
export const useGeometryStatus = create(() => ({
|
|
5
|
+
failures: new Map(),
|
|
6
|
+
}));
|
|
7
|
+
export function setGeometryFailure(id, message) {
|
|
8
|
+
const current = useGeometryStatus.getState().failures;
|
|
9
|
+
if ((current.get(id) ?? null) === message)
|
|
10
|
+
return;
|
|
11
|
+
const failures = new Map(current);
|
|
12
|
+
if (message)
|
|
13
|
+
failures.set(id, message);
|
|
14
|
+
else
|
|
15
|
+
failures.delete(id);
|
|
16
|
+
useGeometryStatus.setState({ failures });
|
|
17
|
+
}
|
|
18
|
+
useScene.subscribe((scene, previous) => {
|
|
19
|
+
const failures = useGeometryStatus.getState().failures;
|
|
20
|
+
for (const id of failures.keys()) {
|
|
21
|
+
if (scene.hydrationId !== previous.hydrationId || !scene.nodes[id])
|
|
22
|
+
setGeometryFailure(id, null);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type AnyNode, type AnyNodeId, type ControlValue, type Interactive, type InteractiveState, type MessageDescriptor } from '@pascal-app/core';
|
|
2
|
+
import { type ProceduralItemNode } from '@pascal-app/core/procedural-items';
|
|
3
|
+
import type { CalloutAction, CalloutIcon, CalloutSound, CalloutText } from '../../components/viewer/selection-callout';
|
|
4
|
+
export declare function hasCallout(type: string | undefined): boolean;
|
|
5
|
+
export declare const SHUTTERS: MessageDescriptor;
|
|
6
|
+
/**
|
|
7
|
+
* The line under the callout's name: what kind of thing it is ("Single-hung
|
|
8
|
+
* window", "Sliding door"), or the catalog name when a person renamed the item.
|
|
9
|
+
*/
|
|
10
|
+
export declare function calloutSubtitle(node: AnyNode | undefined, title: string): CalloutText | null;
|
|
11
|
+
export declare const OPEN_SOUNDS: {
|
|
12
|
+
readonly door: {
|
|
13
|
+
readonly on: 'door-open';
|
|
14
|
+
readonly off: 'door-close';
|
|
15
|
+
readonly swing: 'door-swing';
|
|
16
|
+
};
|
|
17
|
+
readonly window: {
|
|
18
|
+
readonly on: 'window-open';
|
|
19
|
+
readonly off: 'window-close';
|
|
20
|
+
readonly swing: 'window-swing';
|
|
21
|
+
};
|
|
22
|
+
readonly garage: {
|
|
23
|
+
readonly on: 'garage';
|
|
24
|
+
readonly off: 'garage';
|
|
25
|
+
};
|
|
26
|
+
readonly shutters: {
|
|
27
|
+
readonly on: 'shutters-open';
|
|
28
|
+
readonly off: 'shutters-close';
|
|
29
|
+
};
|
|
30
|
+
readonly panel: {
|
|
31
|
+
readonly on: 'panel-open';
|
|
32
|
+
readonly off: 'panel-close';
|
|
33
|
+
readonly swing: 'panel-swing';
|
|
34
|
+
};
|
|
35
|
+
readonly drawer: {
|
|
36
|
+
readonly on: 'panel-open';
|
|
37
|
+
readonly off: 'panel-close';
|
|
38
|
+
};
|
|
39
|
+
readonly switch: {
|
|
40
|
+
readonly on: 'switch-on';
|
|
41
|
+
readonly off: 'switch-off';
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
/** The node fields that choose an opening's icon and sound. */
|
|
45
|
+
export type CalloutActionInput = {
|
|
46
|
+
type: string;
|
|
47
|
+
doorType?: string;
|
|
48
|
+
};
|
|
49
|
+
export declare function openingSound(node: CalloutActionInput): {
|
|
50
|
+
readonly on: 'door-open';
|
|
51
|
+
readonly off: 'door-close';
|
|
52
|
+
readonly swing: 'door-swing';
|
|
53
|
+
} | {
|
|
54
|
+
readonly on: 'window-open';
|
|
55
|
+
readonly off: 'window-close';
|
|
56
|
+
readonly swing: 'window-swing';
|
|
57
|
+
} | {
|
|
58
|
+
readonly on: 'garage';
|
|
59
|
+
readonly off: 'garage';
|
|
60
|
+
};
|
|
61
|
+
/** A pill that flips one thing; without its own text it shows its state word. */
|
|
62
|
+
export declare function calloutToggle(key: string, icon: CalloutIcon, on: boolean, name: string, sound: CalloutSound, run: () => void | Promise<void>, text?: CalloutText): CalloutAction;
|
|
63
|
+
/**
|
|
64
|
+
* A catalog or scripted object's own controls: its light switch, its `open`
|
|
65
|
+
* clip, any other named toggle, and its sliders.
|
|
66
|
+
*/
|
|
67
|
+
export declare function interactiveActions(name: string, interactive: Interactive, values: readonly ControlValue[] | undefined, setValue: (index: number, value: ControlValue) => void, opening?: CalloutActionInput): CalloutAction[];
|
|
68
|
+
/** A procedural item's moving parts, each by its own name, then its lights. */
|
|
69
|
+
export declare function proceduralActions(nodeId: AnyNodeId, name: string, recipe: Pick<ProceduralItemNode['recipe'], 'parts' | 'joints'>, state: InteractiveState): CalloutAction[];
|
|
70
|
+
/** Everything a live scene node can do, for the parametric viewer's callout. */
|
|
71
|
+
export declare function nodeCalloutActions(node: AnyNode, name: string, state: InteractiveState): CalloutAction[];
|
|
72
|
+
//# sourceMappingURL=callout-actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callout-actions.d.ts","sourceRoot":"","sources":["../../../src/systems/interactive/callout-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EAGrB,KAAK,iBAAiB,EAOvB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AAC1F,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACX,YAAY,EACZ,WAAW,EACZ,MAAM,2CAA2C,CAAA;AAiBlD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE5D;AA+CD,eAAO,MAAM,QAAQ,EAAE,iBAGtB,CAAA;AAOD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CA4B5F;AAED,eAAO,MAAM,WAAW;;iBACd,EAAE,EAAE,WAAW;iBAAE,GAAG,EAAE,YAAY;iBAAE,KAAK,EAAE,YAAY;;;iBACrD,EAAE,EAAE,aAAa;iBAAE,GAAG,EAAE,cAAc;iBAAE,KAAK,EAAE,cAAc;;;iBAC7D,EAAE,EAAE,QAAQ;iBAAE,GAAG,EAAE,QAAQ;;;iBACzB,EAAE,EAAE,eAAe;iBAAE,GAAG,EAAE,gBAAgB;;;iBAE7C,EAAE,EAAE,YAAY;iBAAE,GAAG,EAAE,aAAa;iBAAE,KAAK,EAAE,aAAa;;;iBACzD,EAAE,EAAE,YAAY;iBAAE,GAAG,EAAE,aAAa;;;iBACpC,EAAE,EAAE,WAAW;iBAAE,GAAG,EAAE,YAAY;;CACG,CAAA;AAEjD,+DAA+D;AAC/D,MAAM,MAAM,kBAAkB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpE,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB;iBAbvC,WAAW;kBAAO,YAAY;oBAAS,YAAY;;iBACjD,aAAa;kBAAO,cAAc;oBAAS,cAAc;;iBACzD,QAAQ;kBAAO,QAAQ;EActC;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,WAAW,EACjB,EAAE,EAAE,OAAO,EACX,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC/B,IAAI,CAAC,EAAE,WAAW,GACjB,aAAa,CAEf;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,SAAS,EAC3C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,KAAK,IAAI,EACtD,OAAO,GAAE,kBAAqC,GAC7C,aAAa,EAAE,CAwCjB;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC,EAC9D,KAAK,EAAE,gBAAgB,GACtB,aAAa,EAAE,CAiCjB;AAyCD,gFAAgF;AAChF,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,gBAAgB,GACtB,aAAa,EAAE,CAwBjB"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { isScriptedNode, itemInteraction, nodeMechanism, nodeMechanismParts, scriptedInteractive, toggleMechanism, toggleMechanismPart, useInteractive, } from '@pascal-app/core';
|
|
2
|
+
import { operableParts } from '@pascal-app/core/procedural-items';
|
|
3
|
+
import { openingSettled, proceduralSettled } from './opening-settled';
|
|
4
|
+
/** Kinds that are the building itself: the breadcrumb names them, no callout does. */
|
|
5
|
+
const STRUCTURE = new Set([
|
|
6
|
+
'site',
|
|
7
|
+
'building',
|
|
8
|
+
'level',
|
|
9
|
+
'zone',
|
|
10
|
+
'wall',
|
|
11
|
+
'slab',
|
|
12
|
+
'ceiling',
|
|
13
|
+
'roof',
|
|
14
|
+
'roof-segment',
|
|
15
|
+
'fence',
|
|
16
|
+
]);
|
|
17
|
+
export function hasCallout(type) {
|
|
18
|
+
return Boolean(type) && !STRUCTURE.has(type);
|
|
19
|
+
}
|
|
20
|
+
const DOOR_DETAIL = {
|
|
21
|
+
hinged: /* i18n */ { id: 'viewer.callout.door.hinged', message: 'Hinged door' },
|
|
22
|
+
double: /* i18n */ { id: 'viewer.callout.door.double', message: 'Double door' },
|
|
23
|
+
french: /* i18n */ { id: 'viewer.callout.door.french', message: 'French door' },
|
|
24
|
+
folding: /* i18n */ { id: 'viewer.callout.door.folding', message: 'Folding door' },
|
|
25
|
+
pocket: /* i18n */ { id: 'viewer.callout.door.pocket', message: 'Pocket door' },
|
|
26
|
+
barn: /* i18n */ { id: 'viewer.callout.door.barn', message: 'Barn door' },
|
|
27
|
+
sliding: /* i18n */ { id: 'viewer.callout.door.sliding', message: 'Sliding door' },
|
|
28
|
+
'garage-sectional': /* i18n */ {
|
|
29
|
+
id: 'viewer.callout.door.garageSectional',
|
|
30
|
+
message: 'Sectional garage door',
|
|
31
|
+
},
|
|
32
|
+
'garage-rollup': /* i18n */ {
|
|
33
|
+
id: 'viewer.callout.door.garageRollup',
|
|
34
|
+
message: 'Roll-up garage door',
|
|
35
|
+
},
|
|
36
|
+
'garage-tiltup': /* i18n */ {
|
|
37
|
+
id: 'viewer.callout.door.garageTiltup',
|
|
38
|
+
message: 'Tilt-up garage door',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
const WINDOW_DETAIL = {
|
|
42
|
+
fixed: /* i18n */ { id: 'viewer.callout.window.fixed', message: 'Fixed window' },
|
|
43
|
+
sliding: /* i18n */ { id: 'viewer.callout.window.sliding', message: 'Sliding window' },
|
|
44
|
+
casement: /* i18n */ { id: 'viewer.callout.window.casement', message: 'Casement window' },
|
|
45
|
+
awning: /* i18n */ { id: 'viewer.callout.window.awning', message: 'Awning window' },
|
|
46
|
+
hopper: /* i18n */ { id: 'viewer.callout.window.hopper', message: 'Hopper window' },
|
|
47
|
+
'tilt-turn': /* i18n */ { id: 'viewer.callout.window.tiltTurn', message: 'Tilt-and-turn window' },
|
|
48
|
+
'single-hung': /* i18n */ {
|
|
49
|
+
id: 'viewer.callout.window.singleHung',
|
|
50
|
+
message: 'Single-hung window',
|
|
51
|
+
},
|
|
52
|
+
'double-hung': /* i18n */ {
|
|
53
|
+
id: 'viewer.callout.window.doubleHung',
|
|
54
|
+
message: 'Double-hung window',
|
|
55
|
+
},
|
|
56
|
+
bay: /* i18n */ { id: 'viewer.callout.window.bay', message: 'Bay window' },
|
|
57
|
+
bow: /* i18n */ { id: 'viewer.callout.window.bow', message: 'Bow window' },
|
|
58
|
+
louvered: /* i18n */ { id: 'viewer.callout.window.louvered', message: 'Louvered window' },
|
|
59
|
+
};
|
|
60
|
+
const OPENING = /* i18n */ { id: 'viewer.callout.opening', message: 'Opening' };
|
|
61
|
+
const DOOR = /* i18n */ { id: 'viewer.callout.door', message: 'Door' };
|
|
62
|
+
const WINDOW = /* i18n */ { id: 'viewer.callout.window', message: 'Window' };
|
|
63
|
+
export const SHUTTERS = /* i18n */ {
|
|
64
|
+
id: 'viewer.walkthrough.shutters',
|
|
65
|
+
message: 'Shutters',
|
|
66
|
+
};
|
|
67
|
+
function sentence(word) {
|
|
68
|
+
const spaced = word.replaceAll(/[-_]/g, ' ');
|
|
69
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The line under the callout's name: what kind of thing it is ("Single-hung
|
|
73
|
+
* window", "Sliding door"), or the catalog name when a person renamed the item.
|
|
74
|
+
*/
|
|
75
|
+
export function calloutSubtitle(node, title) {
|
|
76
|
+
if (!node)
|
|
77
|
+
return null;
|
|
78
|
+
const raw = node;
|
|
79
|
+
let detail = null;
|
|
80
|
+
if (raw.type === 'door' || raw.type === 'window') {
|
|
81
|
+
const word = raw.type === 'door' ? DOOR : WINDOW;
|
|
82
|
+
if (raw.openingKind === 'opening')
|
|
83
|
+
detail = OPENING;
|
|
84
|
+
else if (raw.source)
|
|
85
|
+
detail = word;
|
|
86
|
+
else if (raw.type === 'door')
|
|
87
|
+
detail = DOOR_DETAIL[raw.doorType ?? 'hinged'] ?? word;
|
|
88
|
+
else
|
|
89
|
+
detail = WINDOW_DETAIL[raw.windowType ?? 'fixed'] ?? word;
|
|
90
|
+
}
|
|
91
|
+
else if (raw.type === 'item') {
|
|
92
|
+
detail =
|
|
93
|
+
raw.asset?.name && raw.asset.name !== title
|
|
94
|
+
? raw.asset.name
|
|
95
|
+
: raw.asset?.category
|
|
96
|
+
? sentence(raw.asset.category)
|
|
97
|
+
: null;
|
|
98
|
+
}
|
|
99
|
+
else if (raw.type !== 'procedural-item') {
|
|
100
|
+
detail = sentence(raw.type);
|
|
101
|
+
}
|
|
102
|
+
return detail && detail !== title ? detail : null;
|
|
103
|
+
}
|
|
104
|
+
export const OPEN_SOUNDS = {
|
|
105
|
+
door: { on: 'door-open', off: 'door-close', swing: 'door-swing' },
|
|
106
|
+
window: { on: 'window-open', off: 'window-close', swing: 'window-swing' },
|
|
107
|
+
garage: { on: 'garage', off: 'garage' },
|
|
108
|
+
shutters: { on: 'shutters-open', off: 'shutters-close' },
|
|
109
|
+
// A recipe's hinged part (a cabinet or wardrobe door, a lid) and its sliding one (a drawer).
|
|
110
|
+
panel: { on: 'panel-open', off: 'panel-close', swing: 'panel-swing' },
|
|
111
|
+
drawer: { on: 'panel-open', off: 'panel-close' },
|
|
112
|
+
switch: { on: 'switch-on', off: 'switch-off' },
|
|
113
|
+
};
|
|
114
|
+
export function openingSound(node) {
|
|
115
|
+
if (node.type === 'window')
|
|
116
|
+
return OPEN_SOUNDS.window;
|
|
117
|
+
return node.doorType?.startsWith('garage-') ? OPEN_SOUNDS.garage : OPEN_SOUNDS.door;
|
|
118
|
+
}
|
|
119
|
+
/** A pill that flips one thing; without its own text it shows its state word. */
|
|
120
|
+
export function calloutToggle(key, icon, on, name, sound, run, text) {
|
|
121
|
+
return { kind: 'toggle', key, icon, on, name, text, sound, toggle: run };
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A catalog or scripted object's own controls: its light switch, its `open`
|
|
125
|
+
* clip, any other named toggle, and its sliders.
|
|
126
|
+
*/
|
|
127
|
+
export function interactiveActions(name, interactive, values, setValue, opening = { type: 'item' }) {
|
|
128
|
+
const toggles = interactive.controls.flatMap((control, index) => control.kind === 'toggle' ? [index] : []);
|
|
129
|
+
const light = interactive.effects.some((effect) => effect.kind === 'light') ? toggles[0] : -1;
|
|
130
|
+
const interaction = itemInteraction(interactive);
|
|
131
|
+
const openControl = interaction.kind === 'open' ? interaction.control : -1;
|
|
132
|
+
const openIcon = opening.type === 'window' ? 'window' : 'door';
|
|
133
|
+
return interactive.controls.map((control, index) => {
|
|
134
|
+
const value = values?.[index];
|
|
135
|
+
if (control.kind === 'toggle') {
|
|
136
|
+
const on = Boolean(value);
|
|
137
|
+
const flip = () => setValue(index, !on);
|
|
138
|
+
if (index === light)
|
|
139
|
+
return calloutToggle(String(index), 'light', on, name, OPEN_SOUNDS.switch, flip);
|
|
140
|
+
if (index === openControl)
|
|
141
|
+
return calloutToggle(String(index), openIcon, on, name, openingSound(opening), flip);
|
|
142
|
+
const label = control.label;
|
|
143
|
+
return calloutToggle(String(index), label && /fan/i.test(label) ? 'fan' : 'power', on, label ?? name, OPEN_SOUNDS.switch, flip, label);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
kind: 'range',
|
|
147
|
+
key: String(index),
|
|
148
|
+
label: control.label,
|
|
149
|
+
value: typeof value === 'number' ? value : control.min,
|
|
150
|
+
min: control.min,
|
|
151
|
+
max: control.max,
|
|
152
|
+
step: control.kind === 'slider' ? control.step : 1,
|
|
153
|
+
unit: control.kind === 'temperature' ? `°${control.unit}` : control.unit,
|
|
154
|
+
onChange: (next) => setValue(index, next),
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/** A procedural item's moving parts, each by its own name, then its lights. */
|
|
159
|
+
export function proceduralActions(nodeId, name, recipe, state) {
|
|
160
|
+
const procedural = state.procedural[nodeId];
|
|
161
|
+
const actions = operableParts(recipe).map((part) => {
|
|
162
|
+
const on = procedural?.parts[part.id] ?? part.kind === 'spin';
|
|
163
|
+
return calloutToggle(part.id, part.kind === 'spin' ? 'fan' : 'door', on, part.label, part.kind === 'spin'
|
|
164
|
+
? OPEN_SOUNDS.switch
|
|
165
|
+
: part.kind === 'slide'
|
|
166
|
+
? OPEN_SOUNDS.drawer
|
|
167
|
+
: OPEN_SOUNDS.panel, () => {
|
|
168
|
+
useInteractive.getState().toggleProceduralPart(nodeId, part.id);
|
|
169
|
+
if (part.kind !== 'spin')
|
|
170
|
+
return proceduralSettled(nodeId);
|
|
171
|
+
}, part.label);
|
|
172
|
+
});
|
|
173
|
+
if (recipe.parts.some((part) => part.light))
|
|
174
|
+
actions.push(calloutToggle('lights', 'light', procedural?.lightsOn ?? state.lampDefault, name, OPEN_SOUNDS.switch, () => useInteractive.getState().toggleProceduralLights(nodeId)));
|
|
175
|
+
return actions;
|
|
176
|
+
}
|
|
177
|
+
/** A built-in door's or window's leaf, then its parts (a window's shutters). */
|
|
178
|
+
function mechanismActions(node, name, state) {
|
|
179
|
+
const actions = [];
|
|
180
|
+
const mechanism = nodeMechanism(node);
|
|
181
|
+
if (mechanism?.icon)
|
|
182
|
+
actions.push(calloutToggle('mechanism', mechanism.icon, mechanism.isOn(node, state), name, openingSound(node), () => {
|
|
183
|
+
toggleMechanism(mechanism, node);
|
|
184
|
+
// Built-in leaves and sashes report when they land; a scripted one plays a clip.
|
|
185
|
+
if (!node.source)
|
|
186
|
+
return openingSettled(node);
|
|
187
|
+
}));
|
|
188
|
+
for (const part of nodeMechanismParts(node)) {
|
|
189
|
+
const on = part.isOn(node, state);
|
|
190
|
+
actions.push(calloutToggle(part.id, part.icon === 'shutters' ? 'shutters' : 'power', on, name, part.icon === 'shutters' ? OPEN_SOUNDS.shutters : OPEN_SOUNDS.switch, () => {
|
|
191
|
+
toggleMechanismPart(part, node);
|
|
192
|
+
if (part.icon === 'shutters')
|
|
193
|
+
return openingSettled(node, 'shutters');
|
|
194
|
+
}, part.icon === 'shutters' ? SHUTTERS : part.label));
|
|
195
|
+
}
|
|
196
|
+
return actions;
|
|
197
|
+
}
|
|
198
|
+
/** Everything a live scene node can do, for the parametric viewer's callout. */
|
|
199
|
+
export function nodeCalloutActions(node, name, state) {
|
|
200
|
+
const setValue = (index, value) => useInteractive.getState().setControlValue(node.id, index, value);
|
|
201
|
+
if (isScriptedNode(node)) {
|
|
202
|
+
const interactive = scriptedInteractive(node);
|
|
203
|
+
return interactive
|
|
204
|
+
? interactiveActions(name, interactive, state.items[node.id]?.controlValues, setValue, node)
|
|
205
|
+
: [];
|
|
206
|
+
}
|
|
207
|
+
if (node.type === 'item' && node.asset.interactive)
|
|
208
|
+
return interactiveActions(name, node.asset.interactive, state.items[node.id]?.controlValues, setValue);
|
|
209
|
+
if (node.type === 'procedural-item')
|
|
210
|
+
return proceduralActions(node.id, name, node.recipe, state);
|
|
211
|
+
return mechanismActions(node, name, state);
|
|
212
|
+
}
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The parametric viewer's callout on the one selected element: its name, what
|
|
3
|
+
* it is, and what it can do (open a door, switch a lamp, close the shutters).
|
|
4
|
+
*/
|
|
5
|
+
export declare const InteractiveSystem: () => import("react").JSX.Element | null;
|
|
2
6
|
//# sourceMappingURL=interactive-system.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactive-system.d.ts","sourceRoot":"","sources":["../../../src/systems/interactive/interactive-system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"interactive-system.d.ts","sourceRoot":"","sources":["../../../src/systems/interactive/interactive-system.tsx"],"names":[],"mappings":"AAwBA;;;GAGG;AACH,eAAO,MAAM,iBAAiB,0CAU7B,CAAA"}
|