@pascal-app/viewer 1.0.1 → 1.0.4
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/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +8 -3
- package/dist/components/viewer/frame-limiter.d.ts +1 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +22 -3
- package/dist/components/viewer/glb-interactive.d.ts +25 -1
- package/dist/components/viewer/glb-interactive.d.ts.map +1 -1
- package/dist/components/viewer/glb-interactive.js +252 -85
- package/dist/components/viewer/glb-mechanisms.d.ts +10 -0
- package/dist/components/viewer/glb-mechanisms.d.ts.map +1 -0
- package/dist/components/viewer/glb-mechanisms.js +19 -0
- package/dist/components/viewer/glb-scene.d.ts +45 -27
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +420 -506
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.js +12 -1
- package/dist/components/viewer/index.d.ts +26 -0
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +108 -29
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +6 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +6 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +8 -3
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +16 -2
- package/dist/components/viewer/render-error.d.ts +18 -0
- package/dist/components/viewer/render-error.d.ts.map +1 -0
- package/dist/components/viewer/render-error.js +29 -0
- package/dist/components/viewer/scene-ground-replacement.d.ts +6 -0
- package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -1
- package/dist/components/viewer/scene-ground-replacement.js +15 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +10 -0
- package/dist/components/viewer/viewer-camera.d.ts +19 -1
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +53 -5
- package/dist/index.d.ts +28 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -18
- package/dist/lib/asset-url.d.ts.map +1 -1
- package/dist/lib/asset-url.js +7 -1
- package/dist/lib/csg-utils.d.ts +1 -1
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +1 -1
- package/dist/lib/display-state.d.ts +25 -0
- package/dist/lib/display-state.d.ts.map +1 -0
- package/dist/lib/display-state.js +1 -0
- package/dist/lib/hero-pose.d.ts +12 -2
- package/dist/lib/hero-pose.d.ts.map +1 -1
- package/dist/lib/hero-pose.js +72 -5
- package/dist/lib/ink-edges.d.ts +4 -2
- package/dist/lib/ink-edges.d.ts.map +1 -1
- package/dist/lib/ink-edges.js +17 -7
- package/dist/lib/key-light-override.d.ts +11 -0
- package/dist/lib/key-light-override.d.ts.map +1 -0
- package/dist/lib/key-light-override.js +14 -0
- package/dist/lib/live-frame-hold.d.ts +14 -0
- package/dist/lib/live-frame-hold.d.ts.map +1 -0
- package/dist/lib/live-frame-hold.js +28 -0
- package/dist/lib/materials.d.ts +14 -7
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +31 -5
- package/dist/lib/node-top-surface-height.d.ts +6 -0
- package/dist/lib/node-top-surface-height.d.ts.map +1 -0
- package/dist/lib/node-top-surface-height.js +74 -0
- package/dist/lib/perf-probe-state.d.ts +39 -0
- package/dist/lib/perf-probe-state.d.ts.map +1 -0
- package/dist/lib/perf-probe-state.js +17 -0
- package/dist/lib/pointer-events.d.ts +8 -0
- package/dist/lib/pointer-events.d.ts.map +1 -1
- package/dist/lib/pointer-events.js +27 -4
- package/dist/lib/procedural-emission.d.ts +7 -0
- package/dist/lib/procedural-emission.d.ts.map +1 -0
- package/dist/lib/procedural-emission.js +54 -0
- package/dist/lib/renderer-capability.d.ts +2 -1
- package/dist/lib/renderer-capability.d.ts.map +1 -1
- package/dist/lib/renderer-capability.js +2 -2
- package/dist/lib/scene-support-height.d.ts +4 -0
- package/dist/lib/scene-support-height.d.ts.map +1 -0
- package/dist/lib/scene-support-height.js +34 -0
- package/dist/lib/scene-visibility.d.ts +6 -0
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +15 -3
- package/dist/lib/scripted-opening.d.ts +11 -0
- package/dist/lib/scripted-opening.d.ts.map +1 -0
- package/dist/lib/scripted-opening.js +20 -0
- package/dist/lib/snapshot-pipeline.d.ts +32 -2
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +215 -105
- package/dist/lib/terrain-perimeter-fill.d.ts.map +1 -1
- package/dist/lib/terrain-perimeter-fill.js +22 -14
- package/dist/lib/zone-geometry.d.ts +11 -0
- package/dist/lib/zone-geometry.d.ts.map +1 -0
- package/dist/lib/zone-geometry.js +46 -0
- package/dist/store/use-item-light-pool.d.ts +14 -10
- package/dist/store/use-item-light-pool.d.ts.map +1 -1
- package/dist/store/use-item-light-pool.js +52 -26
- package/dist/store/use-viewer.d.ts +11 -0
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +13 -6
- package/dist/systems/ceiling/ceiling-system.d.ts +15 -1
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +86 -6
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +73 -22
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts +3 -1
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +148 -5
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +71 -11
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +40 -15
- package/dist/systems/interactive/procedural-controls.d.ts +13 -0
- package/dist/systems/interactive/procedural-controls.d.ts.map +1 -0
- package/dist/systems/interactive/procedural-controls.js +17 -0
- package/dist/systems/interactive/scripted-clips.d.ts +19 -0
- package/dist/systems/interactive/scripted-clips.d.ts.map +1 -0
- package/dist/systems/interactive/scripted-clips.js +134 -0
- package/dist/systems/item/item-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.js +9 -3
- package/dist/systems/item/object-cut-invalidation.d.ts +3 -0
- package/dist/systems/item/object-cut-invalidation.d.ts.map +1 -0
- package/dist/systems/item/object-cut-invalidation.js +68 -0
- package/dist/systems/item-light/item-light-system.d.ts +1 -1
- package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
- package/dist/systems/item-light/item-light-system.js +47 -52
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +17 -19
- package/dist/systems/level/level-utils.d.ts +28 -1
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +36 -4
- package/dist/systems/level/shadow-only-levels.d.ts +9 -0
- package/dist/systems/level/shadow-only-levels.d.ts.map +1 -0
- package/dist/systems/level/shadow-only-levels.js +7 -0
- package/dist/systems/roof/roof-materials.d.ts +14 -2
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +54 -15
- package/dist/systems/roof/roof-system.d.ts +13 -3
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +188 -13
- package/dist/systems/slab/slab-system.d.ts +1 -1
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +49 -19
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +133 -354
- package/dist/systems/wall/wall-cutout-cache.d.ts +11 -1
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout-cache.js +58 -20
- package/dist/systems/wall/wall-cutout.d.ts +9 -1
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +46 -32
- package/dist/systems/wall/wall-finish-data.d.ts +22 -0
- package/dist/systems/wall/wall-finish-data.d.ts.map +1 -0
- package/dist/systems/wall/wall-finish-data.js +17 -0
- package/dist/systems/wall/wall-materials.d.ts +17 -3
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +109 -156
- package/dist/systems/wall/wall-system.d.ts +43 -4
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +601 -163
- package/dist/systems/window/window-system.d.ts +1 -1
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +44 -24
- package/dist/xr/presentation-context.d.ts +7 -0
- package/dist/xr/presentation-context.d.ts.map +1 -0
- package/dist/xr/presentation-context.js +10 -0
- package/package.json +6 -6
- package/dist/systems/fence/fence-system.d.ts +0 -22
- package/dist/systems/fence/fence-system.d.ts.map +0 -1
- package/dist/systems/fence/fence-system.js +0 -357
package/dist/lib/hero-pose.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { sceneRegistry, useScene } from '@pascal-app/core';
|
|
2
2
|
import { Box3, Vector3 } from 'three';
|
|
3
3
|
export const DEFAULT_FRAMING_EXCLUDED_TYPES = ['site', 'scan', 'guide', 'spawn'];
|
|
4
|
-
export function heroCameraPose({ boxes, aspect, aim, fovDeg = 60, azimuthRad = Math.PI / 4, elevationRad = (13 * Math.PI) / 180, padding = 1.03, minDistance = 4, frameShift = 0, }) {
|
|
4
|
+
export function heroCameraPose({ boxes, aspect, aim, fovDeg = 60, azimuthRad = Math.PI / 4, elevationRad = (13 * Math.PI) / 180, padding = 1.03, minDistance = 4, frameShift = 0, tightFit = false, }) {
|
|
5
5
|
const list = Array.isArray(boxes) ? boxes : [boxes];
|
|
6
6
|
const union = new Box3();
|
|
7
7
|
for (const box of list)
|
|
@@ -37,6 +37,62 @@ export function heroCameraPose({ boxes, aspect, aim, fovDeg = 60, azimuthRad = M
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
+
if (tightFit) {
|
|
41
|
+
// Every corner as (lateral, vertical, depth) around `center`. For a camera
|
|
42
|
+
// distance d, a shift (sx, sy) of aim+camera along right/up keeps a corner
|
|
43
|
+
// in frame iff |lateral − sx| ≤ tanH·(d + depth) (same vertically), so each
|
|
44
|
+
// corner bounds the feasible shift window; the tightest d whose windows
|
|
45
|
+
// still intersect is the fit, bisected from the symmetric distance.
|
|
46
|
+
const samples = [];
|
|
47
|
+
for (const box of list) {
|
|
48
|
+
for (const x of [box.min.x, box.max.x]) {
|
|
49
|
+
for (const y of [box.min.y, box.max.y]) {
|
|
50
|
+
for (const z of [box.min.z, box.max.z]) {
|
|
51
|
+
offset.subVectors(corner.set(x, y, z), center);
|
|
52
|
+
samples.push([offset.dot(right), offset.dot(up), offset.dot(forward)]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const tanH = tanHorizontal / padding;
|
|
58
|
+
const tanV = tanVertical / padding;
|
|
59
|
+
const shiftWindow = (d) => {
|
|
60
|
+
let loX = Number.NEGATIVE_INFINITY;
|
|
61
|
+
let hiX = Number.POSITIVE_INFINITY;
|
|
62
|
+
let loY = Number.NEGATIVE_INFINITY;
|
|
63
|
+
let hiY = Number.POSITIVE_INFINITY;
|
|
64
|
+
for (const [lateral, vertical, depth] of samples) {
|
|
65
|
+
const z = d + depth;
|
|
66
|
+
if (z <= 0.1)
|
|
67
|
+
return null;
|
|
68
|
+
loX = Math.max(loX, lateral - tanH * z);
|
|
69
|
+
hiX = Math.min(hiX, lateral + tanH * z);
|
|
70
|
+
loY = Math.max(loY, vertical - tanV * z);
|
|
71
|
+
hiY = Math.min(hiY, vertical + tanV * z);
|
|
72
|
+
}
|
|
73
|
+
return loX <= hiX && loY <= hiY ? { sx: (loX + hiX) / 2, sy: (loY + hiY) / 2 } : null;
|
|
74
|
+
};
|
|
75
|
+
let lo = minDistance;
|
|
76
|
+
let hi = distance;
|
|
77
|
+
while (!shiftWindow(hi))
|
|
78
|
+
hi *= 1.5;
|
|
79
|
+
for (let i = 0; i < 40; i++) {
|
|
80
|
+
const mid = (lo + hi) / 2;
|
|
81
|
+
if (shiftWindow(mid))
|
|
82
|
+
hi = mid;
|
|
83
|
+
else
|
|
84
|
+
lo = mid;
|
|
85
|
+
}
|
|
86
|
+
const shift = shiftWindow(hi);
|
|
87
|
+
const target = center
|
|
88
|
+
.clone()
|
|
89
|
+
.addScaledVector(right, shift.sx)
|
|
90
|
+
.addScaledVector(up, shift.sy - frameShift * hi * tanVertical);
|
|
91
|
+
return {
|
|
92
|
+
position: [target.x + dir.x * hi, target.y + dir.y * hi, target.z + dir.z * hi],
|
|
93
|
+
target: [target.x, target.y, target.z],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
40
96
|
// Reframe: slide aim and camera together along the view-up axis — pure
|
|
41
97
|
// composition shift, no perspective change.
|
|
42
98
|
const drop = up.clone().multiplyScalar(-frameShift * distance * tanVertical);
|
|
@@ -166,7 +222,7 @@ function dominantWallYaw() {
|
|
|
166
222
|
* structure (pure furniture arrangements) fall back to framing every
|
|
167
223
|
* non-helper node.
|
|
168
224
|
*/
|
|
169
|
-
export function computeHeroFraming() {
|
|
225
|
+
export function computeHeroFraming({ sitePlates = true, } = {}) {
|
|
170
226
|
const structural = perNodeBoxes([...DEFAULT_FRAMING_EXCLUDED_TYPES, 'item', 'building', 'level']);
|
|
171
227
|
if (structural.length === 0) {
|
|
172
228
|
const all = perNodeBoxes([...DEFAULT_FRAMING_EXCLUDED_TYPES, 'building', 'level']);
|
|
@@ -176,12 +232,17 @@ export function computeHeroFraming() {
|
|
|
176
232
|
for (const box of all)
|
|
177
233
|
union.union(box);
|
|
178
234
|
const center = union.getCenter(new Vector3());
|
|
179
|
-
return {
|
|
235
|
+
return {
|
|
236
|
+
boxes: all,
|
|
237
|
+
aim: [center.x, center.y, center.z],
|
|
238
|
+
azimuthRad: Math.PI / 4,
|
|
239
|
+
detachedItemIds: [],
|
|
240
|
+
};
|
|
180
241
|
}
|
|
181
242
|
const structuralUnion = new Box3();
|
|
182
243
|
for (const box of structural)
|
|
183
244
|
structuralUnion.union(box);
|
|
184
|
-
const plates = sitePlateBoxes();
|
|
245
|
+
const plates = sitePlates ? sitePlateBoxes() : [];
|
|
185
246
|
const groundUnion = structuralUnion.clone();
|
|
186
247
|
for (const box of plates)
|
|
187
248
|
groundUnion.union(box);
|
|
@@ -189,14 +250,19 @@ export function computeHeroFraming() {
|
|
|
189
250
|
.clone()
|
|
190
251
|
.expandByVector(groundUnion.getSize(new Vector3()).multiplyScalar(0.15));
|
|
191
252
|
const boxes = [...structural, ...plates];
|
|
253
|
+
const detachedItemIds = [];
|
|
192
254
|
const itemIds = sceneRegistry.byType.item;
|
|
193
255
|
for (const id of itemIds) {
|
|
194
256
|
const object = sceneRegistry.nodes.get(id);
|
|
195
257
|
if (!object)
|
|
196
258
|
continue;
|
|
197
259
|
const bounds = new Box3().setFromObject(object);
|
|
198
|
-
if (
|
|
260
|
+
if (bounds.isEmpty())
|
|
261
|
+
continue;
|
|
262
|
+
if (nearby.intersectsBox(bounds))
|
|
199
263
|
boxes.push(bounds);
|
|
264
|
+
else
|
|
265
|
+
detachedItemIds.push(id);
|
|
200
266
|
}
|
|
201
267
|
const groundCenter = groundUnion.getCenter(new Vector3());
|
|
202
268
|
const structuralCenter = structuralUnion.getCenter(new Vector3());
|
|
@@ -205,6 +271,7 @@ export function computeHeroFraming() {
|
|
|
205
271
|
boxes,
|
|
206
272
|
aim: [groundCenter.x, structuralCenter.y, groundCenter.z],
|
|
207
273
|
azimuthRad: Math.PI / 4 - (yaw ?? 0),
|
|
274
|
+
detachedItemIds,
|
|
208
275
|
};
|
|
209
276
|
}
|
|
210
277
|
export function temporarilyHideNodeTypes(types) {
|
package/dist/lib/ink-edges.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { Camera } from 'three';
|
|
2
|
+
export declare function createEdgeDepthSampler(depthTex: any, camera: Camera): (uv: any) => any;
|
|
3
|
+
export declare function inkedEdges({ sampleDepth, normalTex, inkColor, radius, opacity, sceneRgb, }: {
|
|
4
|
+
sampleDepth: (uv: any) => any;
|
|
3
5
|
normalTex: any;
|
|
4
6
|
inkColor: any;
|
|
5
7
|
radius: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ink-edges.d.ts","sourceRoot":"","sources":["../../src/lib/ink-edges.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ink-edges.d.ts","sourceRoot":"","sources":["../../src/lib/ink-edges.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAqB,MAAM,OAAO,CAAA;AAkBtD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,QACd,GAAG,SAYxD;AAkBD,wBAAgB,UAAU,CAAC,EACzB,WAAW,EACX,SAAS,EACT,QAAQ,EACR,MAAM,EACN,OAAO,EACP,QAAQ,GACT,EAAE;IACD,WAAW,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,GAAG,CAAA;IAC7B,SAAS,EAAE,GAAG,CAAA;IACd,QAAQ,EAAE,GAAG,CAAA;IAKb,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,GAAG,CAAA;IACZ,QAAQ,EAAE,GAAG,CAAA;CACd,OAsCA"}
|
package/dist/lib/ink-edges.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import { abs, float, max, min, mix, screenSize, screenUV, smoothstep, vec2 } from 'three/tsl';
|
|
1
|
+
import { abs, float, max, min, mix, perspectiveDepthToViewZ, screenSize, screenUV, smoothstep, uniform, vec2, viewZToPerspectiveDepth, } from 'three/tsl';
|
|
2
2
|
import { unpackRGBToNormal } from './tsl-compat';
|
|
3
|
+
export function createEdgeDepthSampler(depthTex, camera) {
|
|
4
|
+
if (!('isPerspectiveCamera' in camera))
|
|
5
|
+
return (uv) => depthTex.sample(uv).r;
|
|
6
|
+
const perspective = camera;
|
|
7
|
+
const near = uniform(perspective.near).onRenderUpdate(() => perspective.near);
|
|
8
|
+
const far = uniform(perspective.far).onRenderUpdate(() => perspective.far);
|
|
9
|
+
// Ink and AO thresholds were calibrated at near 0.1 / far 1000. Keep
|
|
10
|
+
// their world distances when orbit, walkthrough or capture clipping changes.
|
|
11
|
+
return (uv) => viewZToPerspectiveDepth(perspectiveDepthToViewZ(depthTex.sample(uv).r, near, far), float(0.1), float(1000));
|
|
12
|
+
}
|
|
3
13
|
// Screen-space ink outline (SketchUp / Moebius look). Reads the scene-pass
|
|
4
14
|
// depth + normal MRT and inks two signals:
|
|
5
15
|
//
|
|
@@ -16,14 +26,14 @@ import { unpackRGBToNormal } from './tsl-compat';
|
|
|
16
26
|
// Topology-agnostic: works on CSG triangle soup, organic GLBs, anything — it
|
|
17
27
|
// only sees the rendered buffers. `intensity` scales the final mask; `inkColor`
|
|
18
28
|
// should track the background luminance (dark lines on light scenes).
|
|
19
|
-
export function inkedEdges({
|
|
29
|
+
export function inkedEdges({ sampleDepth, normalTex, inkColor, radius, opacity, sceneRgb, }) {
|
|
20
30
|
const px = vec2(1, 1).div(screenSize).mul(radius);
|
|
21
31
|
const uvN = screenUV;
|
|
22
|
-
const dC =
|
|
23
|
-
const dR =
|
|
24
|
-
const dL =
|
|
25
|
-
const dU =
|
|
26
|
-
const dD =
|
|
32
|
+
const dC = sampleDepth(uvN);
|
|
33
|
+
const dR = sampleDepth(uvN.add(vec2(px.x, 0)));
|
|
34
|
+
const dL = sampleDepth(uvN.sub(vec2(px.x, 0)));
|
|
35
|
+
const dU = sampleDepth(uvN.add(vec2(0, px.y)));
|
|
36
|
+
const dD = sampleDepth(uvN.sub(vec2(0, px.y)));
|
|
27
37
|
const depthLap = abs(dR.add(dL).add(dU).add(dD).sub(dC.mul(4)));
|
|
28
38
|
const invDepth = float(1).sub(dC);
|
|
29
39
|
const depthMetric = depthLap.div(invDepth.mul(invDepth).add(float(0.00002)));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Vector3Tuple } from 'three';
|
|
2
|
+
/**
|
|
3
|
+
* Aims the key (shadow-casting) light along a world direction, ahead of the
|
|
4
|
+
* theme or atmosphere source. For captures that compose their own camera —
|
|
5
|
+
* the worker thumbnail sets the sun relative to its hero angle so shadows
|
|
6
|
+
* read instead of falling straight behind the building. `null` restores the
|
|
7
|
+
* theme/atmosphere direction.
|
|
8
|
+
*/
|
|
9
|
+
export declare function setKeyLightDirectionOverride(direction: Vector3Tuple | null): void;
|
|
10
|
+
export declare function getKeyLightDirectionOverride(): Vector3Tuple | null;
|
|
11
|
+
//# sourceMappingURL=key-light-override.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-light-override.d.ts","sourceRoot":"","sources":["../../src/lib/key-light-override.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAA;AAIzC;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,YAAY,GAAG,IAAI,QAE1E;AAED,wBAAgB,4BAA4B,IAAI,YAAY,GAAG,IAAI,CAElE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
let keyLightDirection = null;
|
|
2
|
+
/**
|
|
3
|
+
* Aims the key (shadow-casting) light along a world direction, ahead of the
|
|
4
|
+
* theme or atmosphere source. For captures that compose their own camera —
|
|
5
|
+
* the worker thumbnail sets the sun relative to its hero angle so shadows
|
|
6
|
+
* read instead of falling straight behind the building. `null` restores the
|
|
7
|
+
* theme/atmosphere direction.
|
|
8
|
+
*/
|
|
9
|
+
export function setKeyLightDirectionOverride(direction) {
|
|
10
|
+
keyLightDirection = direction;
|
|
11
|
+
}
|
|
12
|
+
export function getKeyLightDirectionOverride() {
|
|
13
|
+
return keyLightDirection;
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holds the viewport's LAST presented frame on screen while a capture works
|
|
3
|
+
* on the shared scene: the frame loop keeps ticking (useFrame systems still
|
|
4
|
+
* run, a capture can pump frames by hand) but the live draw is skipped, so
|
|
5
|
+
* whatever a capture swaps in for its own renders — a presentation flip, a
|
|
6
|
+
* hidden ground, an override material held across a GPU readback — never
|
|
7
|
+
* reaches the canvas.
|
|
8
|
+
*
|
|
9
|
+
* Holds nest; each one expires on its own after `maxMs` so a capture that
|
|
10
|
+
* never settles cannot freeze the viewport for the session.
|
|
11
|
+
*/
|
|
12
|
+
export declare function holdLiveFrame(maxMs?: number): () => void;
|
|
13
|
+
export declare function isLiveFrameHeld(): boolean;
|
|
14
|
+
//# sourceMappingURL=live-frame-hold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-frame-hold.d.ts","sourceRoot":"","sources":["../../src/lib/live-frame-hold.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,wBAAgB,aAAa,CAAC,KAAK,SAAS,GAAG,MAAM,IAAI,CAWxD;AAED,wBAAgB,eAAe,IAAI,OAAO,CAEzC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holds the viewport's LAST presented frame on screen while a capture works
|
|
3
|
+
* on the shared scene: the frame loop keeps ticking (useFrame systems still
|
|
4
|
+
* run, a capture can pump frames by hand) but the live draw is skipped, so
|
|
5
|
+
* whatever a capture swaps in for its own renders — a presentation flip, a
|
|
6
|
+
* hidden ground, an override material held across a GPU readback — never
|
|
7
|
+
* reaches the canvas.
|
|
8
|
+
*
|
|
9
|
+
* Holds nest; each one expires on its own after `maxMs` so a capture that
|
|
10
|
+
* never settles cannot freeze the viewport for the session.
|
|
11
|
+
*/
|
|
12
|
+
let holds = 0;
|
|
13
|
+
export function holdLiveFrame(maxMs = 60_000) {
|
|
14
|
+
holds += 1;
|
|
15
|
+
let released = false;
|
|
16
|
+
const release = () => {
|
|
17
|
+
if (released)
|
|
18
|
+
return;
|
|
19
|
+
released = true;
|
|
20
|
+
clearTimeout(expiry);
|
|
21
|
+
holds -= 1;
|
|
22
|
+
};
|
|
23
|
+
const expiry = setTimeout(release, maxMs);
|
|
24
|
+
return release;
|
|
25
|
+
}
|
|
26
|
+
export function isLiveFrameHeld() {
|
|
27
|
+
return holds > 0;
|
|
28
|
+
}
|
package/dist/lib/materials.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare const glassMaterial: MeshLambertNodeMaterial;
|
|
|
13
13
|
export declare function getMaterialTextureVersion(): number;
|
|
14
14
|
export declare function resolveTextureRepeat(repeat: unknown, scale: unknown): [number, number];
|
|
15
15
|
export declare function getTextureKey(material?: MaterialSchema): string;
|
|
16
|
+
/** Glass lets light through; shadow maps would otherwise treat it as a solid panel. */
|
|
17
|
+
export declare function materialCastsShadow(material: THREE.Material | THREE.Material[]): boolean;
|
|
16
18
|
export declare function applyMaterialPresetToMaterials(materialInput: THREE.Material | THREE.Material[], preset: MaterialPresetPayload | null | undefined): void;
|
|
17
19
|
export declare function createMaterialFromPreset(preset: MaterialPresetPayload, shading?: RenderShading): THREE.Material;
|
|
18
20
|
export declare function createMaterialFromPresetRef(materialPreset?: string, shading?: RenderShading): THREE.Material | null;
|
|
@@ -26,17 +28,22 @@ export declare function createMaterial(material?: MaterialSchema, shading?: Rend
|
|
|
26
28
|
*/
|
|
27
29
|
export declare function materialPresetRefSignature(ref: string): string;
|
|
28
30
|
/**
|
|
29
|
-
* Resolve a MaterialRef ('library:<id>' | 'scene:<id>')
|
|
30
|
-
*
|
|
31
|
-
*
|
|
31
|
+
* Resolve a MaterialRef ('library:<id>' | 'scene:<id>') or a plain '#rrggbb'
|
|
32
|
+
* colour to a three.js material; the colour paints like
|
|
33
|
+
* `material: { properties: { color } }`. Returns null for an unknown / dangling
|
|
34
|
+
* ref so callers fall back to the slot's default (authored material, then
|
|
35
|
+
* themed default). Never throws.
|
|
32
36
|
*/
|
|
33
37
|
export declare function resolveMaterialRef(ref: string | undefined, sceneMaterials: Record<SceneMaterialId, SceneMaterial> | undefined, shading?: RenderShading): THREE.Material | null;
|
|
34
38
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
+
* Swaps declared slot DEFAULTS (never painted refs) for another finish, e.g.
|
|
40
|
+
* the worker thumbnail renders unpainted walls as plaster instead of the
|
|
41
|
+
* prepared-drywall default. Must be set before the scene builds its
|
|
42
|
+
* materials; `null` restores the declared defaults.
|
|
39
43
|
*/
|
|
44
|
+
export declare function setSlotDefaultOverrides(overrides: Readonly<Record<string, string>> | null): void;
|
|
45
|
+
/** A declared slot default after `setSlotDefaultOverrides`. */
|
|
46
|
+
export declare function resolveSlotDefaultRef(slotDefault: string): string;
|
|
40
47
|
export declare function resolveSlotDefaultMaterial(slotDefault: string, shading?: RenderShading, roughness?: number): THREE.Material;
|
|
41
48
|
export declare function createDefaultMaterial(color?: string, roughness?: number, shading?: RenderShading, side?: THREE.Side): THREE.Material;
|
|
42
49
|
export declare function createSurfaceRoleMaterial(role: SurfaceRole, preset: ColorPreset, side?: THREE.Side, sceneThemeId?: string): THREE.Material;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../src/lib/materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,qBAAqB,EAE1B,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../src/lib/materials.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/materials.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getMaterialPresetByRef, parseMaterialRef, resolveMaterial, } from '@pascal-app/core';
|
|
1
|
+
import { getMaterialPresetByRef, materialColorPaint, parseMaterialColor, parseMaterialRef, resolveMaterial, } from '@pascal-app/core';
|
|
2
2
|
import { addAfterEffect, invalidate } from '@react-three/fiber';
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { float, mix, positionViewDirection, transformedNormalView } from 'three/tsl';
|
|
@@ -346,6 +346,12 @@ function applyMaterialMapProperties(material, mapProperties) {
|
|
|
346
346
|
// Glass-like transparency threshold: any standard material authored as
|
|
347
347
|
// `transparent` with opacity below this gets the fresnel treatment.
|
|
348
348
|
const GLASS_OPACITY_THRESHOLD = 0.6;
|
|
349
|
+
/** Glass lets light through; shadow maps would otherwise treat it as a solid panel. */
|
|
350
|
+
export function materialCastsShadow(material) {
|
|
351
|
+
const materials = Array.isArray(material) ? material : [material];
|
|
352
|
+
return !materials.some((entry) => entry.transmission > 0 ||
|
|
353
|
+
(entry.transparent && entry.opacity < GLASS_OPACITY_THRESHOLD));
|
|
354
|
+
}
|
|
349
355
|
/**
|
|
350
356
|
* Fresnel-driven opacity for glass: nearly the authored opacity head-on,
|
|
351
357
|
* increasingly opaque (showing the environment reflection) at grazing angles.
|
|
@@ -446,14 +452,19 @@ export function createMaterial(material, shading = 'rendered') {
|
|
|
446
452
|
* the dangling-ref fallback for the whole session.
|
|
447
453
|
*/
|
|
448
454
|
export function materialPresetRefSignature(ref) {
|
|
449
|
-
return getMaterialPresetByRef(ref) ? ref : `${ref}#unresolved`;
|
|
455
|
+
return getMaterialPresetByRef(ref) || parseMaterialColor(ref) ? ref : `${ref}#unresolved`;
|
|
450
456
|
}
|
|
451
457
|
/**
|
|
452
|
-
* Resolve a MaterialRef ('library:<id>' | 'scene:<id>')
|
|
453
|
-
*
|
|
454
|
-
*
|
|
458
|
+
* Resolve a MaterialRef ('library:<id>' | 'scene:<id>') or a plain '#rrggbb'
|
|
459
|
+
* colour to a three.js material; the colour paints like
|
|
460
|
+
* `material: { properties: { color } }`. Returns null for an unknown / dangling
|
|
461
|
+
* ref so callers fall back to the slot's default (authored material, then
|
|
462
|
+
* themed default). Never throws.
|
|
455
463
|
*/
|
|
456
464
|
export function resolveMaterialRef(ref, sceneMaterials, shading = 'rendered') {
|
|
465
|
+
const color = parseMaterialColor(ref);
|
|
466
|
+
if (color)
|
|
467
|
+
return createMaterial(materialColorPaint(color), shading);
|
|
457
468
|
const parsed = parseMaterialRef(ref);
|
|
458
469
|
if (!parsed)
|
|
459
470
|
return null;
|
|
@@ -470,7 +481,22 @@ export function resolveMaterialRef(ref, sceneMaterials, shading = 'rendered') {
|
|
|
470
481
|
* procedural kinds whose colored-mode unpainted appearance comes from a
|
|
471
482
|
* declarative default (slab, wall).
|
|
472
483
|
*/
|
|
484
|
+
let slotDefaultOverrides = null;
|
|
485
|
+
/**
|
|
486
|
+
* Swaps declared slot DEFAULTS (never painted refs) for another finish, e.g.
|
|
487
|
+
* the worker thumbnail renders unpainted walls as plaster instead of the
|
|
488
|
+
* prepared-drywall default. Must be set before the scene builds its
|
|
489
|
+
* materials; `null` restores the declared defaults.
|
|
490
|
+
*/
|
|
491
|
+
export function setSlotDefaultOverrides(overrides) {
|
|
492
|
+
slotDefaultOverrides = overrides;
|
|
493
|
+
}
|
|
494
|
+
/** A declared slot default after `setSlotDefaultOverrides`. */
|
|
495
|
+
export function resolveSlotDefaultRef(slotDefault) {
|
|
496
|
+
return slotDefaultOverrides?.[slotDefault] ?? slotDefault;
|
|
497
|
+
}
|
|
473
498
|
export function resolveSlotDefaultMaterial(slotDefault, shading = 'rendered', roughness = 0.9) {
|
|
499
|
+
slotDefault = resolveSlotDefaultRef(slotDefault);
|
|
474
500
|
if (parseMaterialRef(slotDefault)?.kind === 'library') {
|
|
475
501
|
return (createMaterialFromPresetRef(slotDefault, shading) ??
|
|
476
502
|
cachedDefaultMaterial(`slot-#ffffff-${roughness}`, '#ffffff', roughness, shading));
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type AnyNodeId } from '@pascal-app/core';
|
|
2
|
+
type HeightSampler = (x: number, z: number) => number | null;
|
|
3
|
+
/** Samples one locked node's upward-facing mesh in level-local coordinates. */
|
|
4
|
+
export declare function createNodeTopSurfaceHeightSampler(hostId: AnyNodeId, levelId: AnyNodeId): HeightSampler | null;
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=node-top-surface-height.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-top-surface-height.d.ts","sourceRoot":"","sources":["../../src/lib/node-top-surface-height.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiB,MAAM,kBAAkB,CAAA;AAKhE,KAAK,aAAa,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;AAS5D,+EAA+E;AAC/E,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,SAAS,GACjB,aAAa,GAAG,IAAI,CAqEtB"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { sceneRegistry } from '@pascal-app/core';
|
|
2
|
+
import { Box3, Matrix3, Raycaster, Vector3 } from 'three';
|
|
3
|
+
import useViewer from '../store/use-viewer';
|
|
4
|
+
import { setSurfaceRaycastLayers } from './layers';
|
|
5
|
+
const samplerCache = new WeakMap();
|
|
6
|
+
/** Samples one locked node's upward-facing mesh in level-local coordinates. */
|
|
7
|
+
export function createNodeTopSurfaceHeightSampler(hostId, levelId) {
|
|
8
|
+
const host = sceneRegistry.nodes.get(hostId);
|
|
9
|
+
const level = sceneRegistry.nodes.get(levelId);
|
|
10
|
+
if (!(host && level))
|
|
11
|
+
return null;
|
|
12
|
+
host.updateWorldMatrix(true, false);
|
|
13
|
+
level.updateWorldMatrix(true, false);
|
|
14
|
+
const revision = useViewer.getState().geometryRevision;
|
|
15
|
+
const cached = samplerCache.get(host)?.get(level);
|
|
16
|
+
if (cached?.revision === revision &&
|
|
17
|
+
cached.hostMatrix.every((value, index) => value === host.matrixWorld.elements[index]) &&
|
|
18
|
+
cached.levelMatrix.every((value, index) => value === level.matrixWorld.elements[index])) {
|
|
19
|
+
return cached.sample;
|
|
20
|
+
}
|
|
21
|
+
host.updateWorldMatrix(true, true);
|
|
22
|
+
const bounds = new Box3().setFromObject(host);
|
|
23
|
+
if (bounds.isEmpty())
|
|
24
|
+
return null;
|
|
25
|
+
const raycaster = new Raycaster();
|
|
26
|
+
setSurfaceRaycastLayers(raycaster.layers);
|
|
27
|
+
raycaster.far = Math.max(4, bounds.max.y - bounds.min.y + 4);
|
|
28
|
+
const origin = new Vector3();
|
|
29
|
+
const direction = new Vector3(0, -1, 0);
|
|
30
|
+
const normal = new Vector3();
|
|
31
|
+
const normalMatrix = new Matrix3();
|
|
32
|
+
const registeredRoots = new Set(sceneRegistry.nodes.values());
|
|
33
|
+
const sample = (x, z) => {
|
|
34
|
+
origin.set(x, 0, z);
|
|
35
|
+
level.localToWorld(origin);
|
|
36
|
+
origin.y = bounds.max.y + 2;
|
|
37
|
+
raycaster.set(origin, direction);
|
|
38
|
+
for (const hit of raycaster.intersectObject(host, true)) {
|
|
39
|
+
if (!hit.face)
|
|
40
|
+
continue;
|
|
41
|
+
let owner = hit.object.parent;
|
|
42
|
+
let nestedNode = false;
|
|
43
|
+
while (owner && owner !== host) {
|
|
44
|
+
if (registeredRoots.has(owner)) {
|
|
45
|
+
nestedNode = true;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
owner = owner.parent;
|
|
49
|
+
}
|
|
50
|
+
if (nestedNode)
|
|
51
|
+
continue;
|
|
52
|
+
normalMatrix.getNormalMatrix(hit.object.matrixWorld);
|
|
53
|
+
normal.copy(hit.face.normal).applyMatrix3(normalMatrix).normalize();
|
|
54
|
+
if (normal.y < 0.1)
|
|
55
|
+
continue;
|
|
56
|
+
const point = hit.point.clone();
|
|
57
|
+
level.worldToLocal(point);
|
|
58
|
+
return point.y;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
};
|
|
62
|
+
let byLevel = samplerCache.get(host);
|
|
63
|
+
if (!byLevel) {
|
|
64
|
+
byLevel = new WeakMap();
|
|
65
|
+
samplerCache.set(host, byLevel);
|
|
66
|
+
}
|
|
67
|
+
byLevel.set(level, {
|
|
68
|
+
revision,
|
|
69
|
+
hostMatrix: [...host.matrixWorld.elements],
|
|
70
|
+
levelMatrix: [...level.matrixWorld.elements],
|
|
71
|
+
sample,
|
|
72
|
+
});
|
|
73
|
+
return sample;
|
|
74
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
type TemporalLike = {
|
|
2
|
+
temporal: {
|
|
3
|
+
getState(): {
|
|
4
|
+
pastStates: readonly unknown[];
|
|
5
|
+
futureStates: readonly unknown[];
|
|
6
|
+
isTracking: boolean;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
type SelectionLike = {
|
|
11
|
+
getState(): {
|
|
12
|
+
selection: {
|
|
13
|
+
buildingId: string | null;
|
|
14
|
+
levelId: string | null;
|
|
15
|
+
zoneId: string | null;
|
|
16
|
+
selectedIds: readonly string[];
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export type PerfHistoryState = {
|
|
21
|
+
/** Undo entries. */
|
|
22
|
+
past: number;
|
|
23
|
+
/** Redo entries. */
|
|
24
|
+
future: number;
|
|
25
|
+
/** False while any owner has history paused. */
|
|
26
|
+
tracking: boolean;
|
|
27
|
+
/** Refcounted pause owners (`pauseSceneHistory` + leases); direct `temporal.pause()` is not counted. */
|
|
28
|
+
pauseDepth: number;
|
|
29
|
+
};
|
|
30
|
+
export type PerfSelectionState = {
|
|
31
|
+
buildingId: string | null;
|
|
32
|
+
levelId: string | null;
|
|
33
|
+
zoneId: string | null;
|
|
34
|
+
selectedIds: string[];
|
|
35
|
+
};
|
|
36
|
+
export declare function readPerfHistory(store: TemporalLike): PerfHistoryState;
|
|
37
|
+
export declare function readPerfSelection(store: SelectionLike): PerfSelectionState;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=perf-probe-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf-probe-state.d.ts","sourceRoot":"","sources":["../../src/lib/perf-probe-state.ts"],"names":[],"mappings":"AAMA,KAAK,YAAY,GAAG;IAClB,QAAQ,EAAE;QACR,QAAQ,IAAI;YACV,UAAU,EAAE,SAAS,OAAO,EAAE,CAAA;YAC9B,YAAY,EAAE,SAAS,OAAO,EAAE,CAAA;YAChC,UAAU,EAAE,OAAO,CAAA;SACpB,CAAA;KACF,CAAA;CACF,CAAA;AAED,KAAK,aAAa,GAAG;IACnB,QAAQ,IAAI;QACV,SAAS,EAAE;YACT,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;YACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;YACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;YACrB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;SAC/B,CAAA;KACF,CAAA;CACF,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,oBAAoB;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,gDAAgD;IAChD,QAAQ,EAAE,OAAO,CAAA;IACjB,wGAAwG;IACxG,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB,CAAA;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,gBAAgB,CAQrE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,kBAAkB,CAG1E"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Read-only state for scripted `?perf` receipts (bench/next-house, scripts/perf): the scene
|
|
2
|
+
// history and the viewer selection, so production builds can report undo entries and
|
|
3
|
+
// history pauses without dev-only store handles.
|
|
4
|
+
import { getSceneHistoryPauseDepth } from '@pascal-app/core';
|
|
5
|
+
export function readPerfHistory(store) {
|
|
6
|
+
const temporal = store.temporal.getState();
|
|
7
|
+
return {
|
|
8
|
+
past: temporal.pastStates.length,
|
|
9
|
+
future: temporal.futureStates.length,
|
|
10
|
+
tracking: temporal.isTracking,
|
|
11
|
+
pauseDepth: getSceneHistoryPauseDepth(),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function readPerfSelection(store) {
|
|
15
|
+
const { buildingId, levelId, zoneId, selectedIds } = store.getState().selection;
|
|
16
|
+
return { buildingId, levelId, zoneId, selectedIds: [...selectedIds] };
|
|
17
|
+
}
|
|
@@ -3,5 +3,13 @@ import * as THREE from 'three';
|
|
|
3
3
|
/** Opt in only when the raycast appends hits without reading prior hits or mutating scene/query state. */
|
|
4
4
|
export declare function markPureRaycast<T extends THREE.Object3D['raycast']>(raycast: T): T;
|
|
5
5
|
export declare function choosePointerEvents(search?: string): typeof createWebEvents;
|
|
6
|
+
/**
|
|
7
|
+
* Whether a mesh has a material for everything a raycast reads: three indexes
|
|
8
|
+
* a material array by each geometry group, and one with no entry throws on
|
|
9
|
+
* `material.side`. A mesh caught mid-swap (a preview restored an older array
|
|
10
|
+
* over a rebuilt geometry) is skipped for this move instead of taking the whole
|
|
11
|
+
* pointer event down; its children are still tested.
|
|
12
|
+
*/
|
|
13
|
+
export declare function hasMaterialsForGroups(object: THREE.Object3D): boolean;
|
|
6
14
|
export declare function createPascalPointerEvents(store: RootStore): EventManager<HTMLElement>;
|
|
7
15
|
//# sourceMappingURL=pointer-events.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pointer-events.d.ts","sourceRoot":"","sources":["../../src/lib/pointer-events.ts"],"names":[],"mappings":"AA6BA,OAAO,EACL,MAAM,IAAI,eAAe,EAGzB,KAAK,YAAY,EAMjB,KAAK,SAAS,EAEf,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAqC9B,0GAA0G;AAC1G,wBAAgB,eAAe,CAAC,CAAC,SAAS,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAGlF;AAYD,wBAAgB,mBAAmB,CACjC,MAAM,SAA8D,GACnE,OAAO,eAAe,CAIxB;
|
|
1
|
+
{"version":3,"file":"pointer-events.d.ts","sourceRoot":"","sources":["../../src/lib/pointer-events.ts"],"names":[],"mappings":"AA6BA,OAAO,EACL,MAAM,IAAI,eAAe,EAGzB,KAAK,YAAY,EAMjB,KAAK,SAAS,EAEf,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAqC9B,0GAA0G;AAC1G,wBAAgB,eAAe,CAAC,CAAC,SAAS,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAGlF;AAYD,wBAAgB,mBAAmB,CACjC,MAAM,SAA8D,GACnE,OAAO,eAAe,CAIxB;AAiED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,GAAG,OAAO,CAUrE;AAkHD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,SAAS,GAAG,YAAY,CAAC,WAAW,CAAC,CAmBrF"}
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
22
|
* SOFTWARE.
|
|
23
23
|
*
|
|
24
|
-
* Vendored from R3F 9.
|
|
25
|
-
* https://raw.githubusercontent.com/pmndrs/react-three-fiber/v9.
|
|
24
|
+
* Vendored from R3F 9.8.1:
|
|
25
|
+
* https://raw.githubusercontent.com/pmndrs/react-three-fiber/v9.8.1/packages/fiber/src/core/events.ts
|
|
26
26
|
* Ray collection is cached; camera-drag moves are throttled before stock dispatch.
|
|
27
27
|
* The throttle is a vendored-manager policy; dev ?stockEvents uses untouched R3F.
|
|
28
28
|
*/
|
|
@@ -112,6 +112,28 @@ function querySnapshot(raycaster) {
|
|
|
112
112
|
}
|
|
113
113
|
return snapshot;
|
|
114
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Whether a mesh has a material for everything a raycast reads: three indexes
|
|
117
|
+
* a material array by each geometry group, and one with no entry throws on
|
|
118
|
+
* `material.side`. A mesh caught mid-swap (a preview restored an older array
|
|
119
|
+
* over a rebuilt geometry) is skipped for this move instead of taking the whole
|
|
120
|
+
* pointer event down; its children are still tested.
|
|
121
|
+
*/
|
|
122
|
+
export function hasMaterialsForGroups(object) {
|
|
123
|
+
const mesh = object;
|
|
124
|
+
if (!mesh.isMesh)
|
|
125
|
+
return true;
|
|
126
|
+
const material = mesh.material;
|
|
127
|
+
if (!material)
|
|
128
|
+
return false;
|
|
129
|
+
if (!Array.isArray(material))
|
|
130
|
+
return true;
|
|
131
|
+
for (const group of mesh.geometry?.groups ?? []) {
|
|
132
|
+
if (!material[group.materialIndex ?? 0])
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
115
137
|
function distanceOrder(a, b) {
|
|
116
138
|
return a.distance - b.distance;
|
|
117
139
|
}
|
|
@@ -140,7 +162,8 @@ function createCachedRaycast() {
|
|
|
140
162
|
unsupportedObject = object;
|
|
141
163
|
return false;
|
|
142
164
|
}
|
|
143
|
-
|
|
165
|
+
if (hasMaterialsForGroups(object))
|
|
166
|
+
result = object.raycast(raycaster, hits);
|
|
144
167
|
}
|
|
145
168
|
if (result !== false) {
|
|
146
169
|
const children = object.children;
|
|
@@ -246,7 +269,7 @@ export function createPascalPointerEvents(store) {
|
|
|
246
269
|
return manager;
|
|
247
270
|
}
|
|
248
271
|
function makeId(event) {
|
|
249
|
-
// biome-ignore lint/style/useTemplate: Keep the vendored dispatch identical to R3F 9.
|
|
272
|
+
// biome-ignore lint/style/useTemplate: Keep the vendored dispatch identical to R3F 9.8.1.
|
|
250
273
|
return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
|
|
251
274
|
}
|
|
252
275
|
/**
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EvaluatedLight } from '@pascal-app/core/procedural-items';
|
|
2
|
+
import type { Material, Mesh, Object3D } from 'three';
|
|
3
|
+
export declare function cloneWithProceduralEmission(material: Material, color: string, on: boolean): Material;
|
|
4
|
+
export declare function setProceduralEmission(material: Material, on: boolean): void;
|
|
5
|
+
export declare function proceduralSlotMeshes(root: Object3D, slots: Set<string>): Mesh[];
|
|
6
|
+
export declare function decorateProceduralEmission(root: Object3D, lights: EvaluatedLight[], on: boolean): () => void;
|
|
7
|
+
//# sourceMappingURL=procedural-emission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procedural-emission.d.ts","sourceRoot":"","sources":["../../src/lib/procedural-emission.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAA;AACvE,OAAO,KAAK,EAAS,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAQ5D,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,OAAO,GACV,QAAQ,CAQV;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CAG3E;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,CAW/E;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,cAAc,EAAE,EACxB,EAAE,EAAE,OAAO,GACV,MAAM,IAAI,CAwBZ"}
|