@pascal-app/viewer 1.0.6 → 1.0.7
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/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-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 +3 -1
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +289 -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 +30 -6
- 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/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/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 +236 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +12 -1
- package/dist/index.d.ts +21 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -7
- 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 +53 -0
- package/dist/lib/daylight/room-scene.d.ts +54 -0
- package/dist/lib/daylight/room-scene.d.ts.map +1 -0
- package/dist/lib/daylight/room-scene.js +356 -0
- package/dist/lib/daylight/rooms.d.ts +262 -0
- package/dist/lib/daylight/rooms.d.ts.map +1 -0
- package/dist/lib/daylight/rooms.js +1223 -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/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/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/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 +10 -8
- 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/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 +72 -15
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +103 -18
- 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/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 +477 -105
- 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.map +1 -1
- package/dist/systems/wall/wall-system.js +34 -19
- 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
|
+
}
|
|
@@ -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"}
|
|
@@ -1,129 +1,52 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
8
|
-
import { Vector3 } from 'three';
|
|
9
|
-
import { useShallow } from 'zustand/react/shallow';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { sceneRegistry, useInteractive, useScene, } from '@pascal-app/core';
|
|
4
|
+
import { useFrame } from '@react-three/fiber';
|
|
5
|
+
import { useCallback, useState } from 'react';
|
|
6
|
+
import { SelectionCallout } from '../../components/viewer/selection-callout';
|
|
10
7
|
import useViewer from '../../store/use-viewer';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
import { calloutSubtitle, hasCallout, nodeCalloutActions } from './callout-actions';
|
|
9
|
+
function calloutTitle(node) {
|
|
10
|
+
if (node.name)
|
|
11
|
+
return node.name;
|
|
12
|
+
const asset = node.asset;
|
|
13
|
+
if (asset?.name)
|
|
14
|
+
return asset.name;
|
|
15
|
+
const word = node.type.replaceAll('-', ' ');
|
|
16
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The parametric viewer's callout on the one selected element: its name, what
|
|
20
|
+
* it is, and what it can do (open a door, switch a lamp, close the shutters).
|
|
21
|
+
*/
|
|
22
22
|
export const InteractiveSystem = () => {
|
|
23
|
-
const zoneId = useViewer((s) => s.selection.zoneId);
|
|
24
23
|
const selectedIds = useViewer((s) => s.selection.selectedIds);
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
.filter((n) => (isScriptedNode(n) && scriptInteractive(n.source.manifest) != null) ||
|
|
33
|
-
(n.type === 'item' && n.asset.interactive != null) ||
|
|
34
|
-
(n.type === 'procedural-item' &&
|
|
35
|
-
(operableParts(n.recipe).length > 0 || n.recipe.parts.some((part) => part.light))))
|
|
36
|
-
.map((n) => n.id)));
|
|
37
|
-
return (_jsx(_Fragment, { children: interactiveNodeIds
|
|
38
|
-
.filter((id) => zonePolygon?.length || selectedIds.includes(id))
|
|
39
|
-
.map((id) => (_jsx(ItemControlsOverlay, { isSelected: selectedIds.includes(id), nodeId: id, zonePolygon: zonePolygon }, id))) }));
|
|
24
|
+
const walkthrough = useViewer((s) => s.walkthroughMode);
|
|
25
|
+
const node = useScene((s) => selectedIds.length === 1
|
|
26
|
+
? s.nodes[selectedIds[0]]
|
|
27
|
+
: undefined);
|
|
28
|
+
if (walkthrough || !node || !hasCallout(node.type))
|
|
29
|
+
return null;
|
|
30
|
+
return _jsx(NodeCallout, { node: node }, node.id);
|
|
40
31
|
};
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const [itemObj, setItemObj] = useState(null);
|
|
32
|
+
function NodeCallout({ node }) {
|
|
33
|
+
const [object, setObject] = useState(null);
|
|
34
|
+
// The renderer registers the object a frame or more after selection lands,
|
|
35
|
+
// and may swap it for a new one when the element rebuilds (a sash opening).
|
|
46
36
|
useFrame(() => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (obj)
|
|
51
|
-
setItemObj(obj);
|
|
37
|
+
const registered = sceneRegistry.nodes.get(node.id) ?? null;
|
|
38
|
+
if (registered !== object)
|
|
39
|
+
setObject(registered);
|
|
52
40
|
});
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
]);
|
|
63
|
-
let descriptors = [];
|
|
64
|
-
let height = 0;
|
|
65
|
-
const interactive = isScriptedNode(node)
|
|
66
|
-
? scriptInteractive(node.source.manifest, scriptedOrigin(node))
|
|
67
|
-
: node?.type === 'item'
|
|
68
|
-
? node.asset.interactive
|
|
69
|
-
: undefined;
|
|
70
|
-
if (interactive && controlValues) {
|
|
71
|
-
descriptors = interactive.controls.map((control, i) => ({
|
|
72
|
-
key: String(i),
|
|
73
|
-
control,
|
|
74
|
-
value: controlValues[i] ?? false,
|
|
75
|
-
onChange: (value) => setControlValue(nodeId, i, value),
|
|
76
|
-
}));
|
|
77
|
-
height = isScriptedNode(node)
|
|
78
|
-
? scriptedSize(node.source.manifest)[1] + scriptedOrigin(node)[1]
|
|
79
|
-
: node?.type === 'item'
|
|
80
|
-
? node.asset.dimensions[1]
|
|
81
|
-
: 0;
|
|
82
|
-
}
|
|
83
|
-
else if (node?.type === 'procedural-item') {
|
|
84
|
-
descriptors = proceduralControlDescriptors(node.recipe, proceduralState, (partId) => togglePart(nodeId, partId), () => toggleLights(nodeId), lampDefault);
|
|
85
|
-
height = proceduralHeight;
|
|
86
|
-
}
|
|
87
|
-
let visible = isSelected;
|
|
88
|
-
if (itemObj && zonePolygon?.length) {
|
|
89
|
-
itemObj.getWorldPosition(_tempVec);
|
|
90
|
-
visible = visible || pointInPolygon(_tempVec.x, _tempVec.z, zonePolygon);
|
|
91
|
-
}
|
|
92
|
-
// Fade in on mount and fade out before unmounting the <Html>.
|
|
93
|
-
const [mounted, setMounted] = useState(false);
|
|
94
|
-
const [shown, setShown] = useState(false);
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
if (visible) {
|
|
97
|
-
setMounted(true);
|
|
98
|
-
// Double rAF: the overlay has to paint once at opacity 0 before the
|
|
99
|
-
// opacity-1 style lands, otherwise the fade-in transition is skipped.
|
|
100
|
-
let raf2 = 0;
|
|
101
|
-
const raf1 = requestAnimationFrame(() => {
|
|
102
|
-
raf2 = requestAnimationFrame(() => setShown(true));
|
|
103
|
-
});
|
|
104
|
-
return () => {
|
|
105
|
-
cancelAnimationFrame(raf1);
|
|
106
|
-
cancelAnimationFrame(raf2);
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
setShown(false);
|
|
110
|
-
const timeout = setTimeout(() => setMounted(false), FADE_MS);
|
|
111
|
-
return () => clearTimeout(timeout);
|
|
112
|
-
}, [visible]);
|
|
113
|
-
if (!(mounted && itemObj && descriptors.length))
|
|
41
|
+
const title = calloutTitle(node);
|
|
42
|
+
const interactive = useInteractive();
|
|
43
|
+
const actions = nodeCalloutActions(node, title, interactive);
|
|
44
|
+
const dismiss = useCallback(() => {
|
|
45
|
+
const { selection, setSelection } = useViewer.getState();
|
|
46
|
+
if (selection.selectedIds.includes(node.id))
|
|
47
|
+
setSelection({ selectedIds: [] });
|
|
48
|
+
}, [node.id]);
|
|
49
|
+
if (!object)
|
|
114
50
|
return null;
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
flexDirection: 'column',
|
|
118
|
-
gap: 6,
|
|
119
|
-
background: 'rgba(0,0,0,0.75)',
|
|
120
|
-
backdropFilter: 'blur(8px)',
|
|
121
|
-
borderRadius: 8,
|
|
122
|
-
padding: '8px 12px',
|
|
123
|
-
minWidth: 120,
|
|
124
|
-
pointerEvents: visible ? 'auto' : 'none',
|
|
125
|
-
userSelect: 'none',
|
|
126
|
-
opacity: shown ? 1 : 0,
|
|
127
|
-
transition: `opacity ${FADE_MS}ms ease`,
|
|
128
|
-
}, children: descriptors.map((descriptor) => (_jsx(ControlWidget, { control: descriptor.control, onChange: descriptor.onChange, value: descriptor.value }, descriptor.key))) }) }), itemObj);
|
|
129
|
-
};
|
|
51
|
+
return (_jsx(SelectionCallout, { actions: actions, object: object, onDismiss: dismiss, subtitle: calloutSubtitle(node, title), title: title }));
|
|
52
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type AnyNodeId } from '@pascal-app/core';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves when a built-in door's leaf or a window's sash (or, with `shutters`,
|
|
4
|
+
* its shutters) finishes the run just started, so the latch can sound on the
|
|
5
|
+
* frame the leaf lands.
|
|
6
|
+
*/
|
|
7
|
+
export declare function openingSettled(node: {
|
|
8
|
+
id: string;
|
|
9
|
+
type: string;
|
|
10
|
+
}, part?: 'leaf' | 'shutters'): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Resolves when a procedural item's latest commanded run (a cabinet door, a
|
|
13
|
+
* drawer, a lid) has its moving parts at rest. Call it right after the toggle.
|
|
14
|
+
*/
|
|
15
|
+
export declare function proceduralSettled(nodeId: AnyNodeId): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=opening-settled.d.ts.map
|