@pascal-app/viewer 1.0.0-beta.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/capture/asset-url.d.ts +2 -0
- package/dist/capture/asset-url.d.ts.map +1 -0
- package/dist/capture/asset-url.js +13 -0
- package/dist/capture/capture-runtime.d.ts +43 -0
- package/dist/capture/capture-runtime.d.ts.map +1 -0
- package/dist/capture/capture-runtime.js +228 -0
- package/dist/capture/frame.d.ts +4 -0
- package/dist/capture/frame.d.ts.map +1 -0
- package/dist/capture/frame.js +21 -0
- package/dist/capture/index.d.ts +12 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +11 -0
- package/dist/capture/layer-visibility.d.ts +5 -0
- package/dist/capture/layer-visibility.d.ts.map +1 -0
- package/dist/capture/layer-visibility.js +11 -0
- package/dist/capture/layers/clay-matcap.d.ts +3 -0
- package/dist/capture/layers/clay-matcap.d.ts.map +1 -0
- package/dist/capture/layers/clay-matcap.js +26 -0
- package/dist/capture/layers/device-motion-layer.d.ts +7 -0
- package/dist/capture/layers/device-motion-layer.d.ts.map +1 -0
- package/dist/capture/layers/device-motion-layer.js +78 -0
- package/dist/capture/layers/point-cloud-layer.d.ts +15 -0
- package/dist/capture/layers/point-cloud-layer.d.ts.map +1 -0
- package/dist/capture/layers/point-cloud-layer.js +106 -0
- package/dist/capture/layers/room-model-layer.d.ts +9 -0
- package/dist/capture/layers/room-model-layer.d.ts.map +1 -0
- package/dist/capture/layers/room-model-layer.js +71 -0
- package/dist/capture/layers/surface-mesh-data.d.ts +9 -0
- package/dist/capture/layers/surface-mesh-data.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-data.js +74 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts +7 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-layer.js +16 -0
- package/dist/capture/preview.d.ts +3 -0
- package/dist/capture/preview.d.ts.map +1 -0
- package/dist/capture/preview.js +2 -0
- package/dist/capture/source-state.d.ts +22 -0
- package/dist/capture/source-state.d.ts.map +1 -0
- package/dist/capture/source-state.js +182 -0
- package/dist/capture/stream-rendering.d.ts +14 -0
- package/dist/capture/stream-rendering.d.ts.map +1 -0
- package/dist/capture/stream-rendering.js +69 -0
- package/dist/capture/trajectory.d.ts +20 -0
- package/dist/capture/trajectory.d.ts.map +1 -0
- package/dist/capture/trajectory.js +77 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts +14 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts.map +1 -0
- package/dist/components/viewer/batched-mesh-spike.js +64 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +4 -3
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +70 -62
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +123 -89
- package/dist/components/viewer/perf-monitor.d.ts +6 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +307 -116
- package/dist/components/viewer/perf-panel.d.ts +3 -0
- package/dist/components/viewer/perf-panel.d.ts.map +1 -0
- package/dist/components/viewer/perf-panel.js +178 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
- package/dist/components/viewer/pointer-raycast-layers.js +28 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +124 -83
- package/dist/components/viewer/registered-systems.d.ts +14 -5
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +22 -10
- package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
- package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
- package/dist/components/viewer/scene-atmosphere.js +89 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -1
- package/dist/components/viewer/scene-environment.js +19 -5
- package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
- package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
- package/dist/components/viewer/scene-ground-replacement.js +35 -0
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +4 -1
- package/dist/components/viewer/viewer-presentations.d.ts +67 -0
- package/dist/components/viewer/viewer-presentations.d.ts.map +1 -0
- package/dist/components/viewer/viewer-presentations.js +120 -0
- package/dist/hooks/use-library-materials-version.d.ts +7 -0
- package/dist/hooks/use-library-materials-version.d.ts.map +1 -0
- package/dist/hooks/use-library-materials-version.js +10 -0
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +1 -0
- package/dist/index.d.ts +15 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -6
- package/dist/lib/csg-utils.d.ts +1 -0
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +6 -1
- package/dist/lib/gpu-perf.d.ts +0 -2
- package/dist/lib/gpu-perf.d.ts.map +1 -1
- package/dist/lib/gpu-perf.js +7 -21
- package/dist/lib/layer-pass.d.ts +28 -0
- package/dist/lib/layer-pass.d.ts.map +1 -0
- package/dist/lib/layer-pass.js +254 -0
- package/dist/lib/materials.d.ts +12 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +51 -17
- package/dist/lib/merged-outline-node.d.ts +17 -8
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +194 -52
- package/dist/lib/perf-actions.d.ts +55 -0
- package/dist/lib/perf-actions.d.ts.map +1 -0
- package/dist/lib/perf-actions.js +211 -0
- package/dist/lib/perf-observers.d.ts +2 -0
- package/dist/lib/perf-observers.d.ts.map +1 -0
- package/dist/lib/perf-observers.js +17 -0
- package/dist/lib/perf-panel-store.d.ts +71 -0
- package/dist/lib/perf-panel-store.d.ts.map +1 -0
- package/dist/lib/perf-panel-store.js +29 -0
- package/dist/lib/perf-tracks.d.ts +43 -0
- package/dist/lib/perf-tracks.d.ts.map +1 -0
- package/dist/lib/perf-tracks.js +113 -0
- package/dist/lib/pointer-events.d.ts +7 -0
- package/dist/lib/pointer-events.d.ts.map +1 -0
- package/dist/lib/pointer-events.js +680 -0
- package/dist/lib/post-processing-resources.d.ts +12 -0
- package/dist/lib/post-processing-resources.d.ts.map +1 -0
- package/dist/lib/post-processing-resources.js +19 -0
- package/dist/lib/renderer-capability.d.ts.map +1 -1
- package/dist/lib/renderer-capability.js +4 -2
- package/dist/lib/scene-visibility.d.ts +2 -1
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +22 -1
- package/dist/store/use-viewer.d.ts +2 -2
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +1 -1
- package/dist/systems/door/door-animation-system.d.ts.map +1 -1
- package/dist/systems/door/door-animation-system.js +6 -9
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +13 -2
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +3 -2
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +1 -1
- package/dist/systems/level/level-utils.d.ts +9 -0
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +14 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1196 -58
- package/dist/systems/scan/scan-system.js +6 -5
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +1 -2
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +9 -8
- package/dist/systems/surface-hole-geometry.d.ts +1 -1
- package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
- package/dist/systems/surface-hole-geometry.js +1 -1
- package/dist/systems/wall/level-miter-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
- package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
- package/dist/systems/wall/wall-build-lifecycle.js +74 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
- package/dist/systems/wall/wall-cutout-cache.js +304 -0
- package/dist/systems/wall/wall-cutout.d.ts +12 -1
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +24 -161
- package/dist/systems/wall/wall-material-variant.d.ts +31 -0
- package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
- package/dist/systems/wall/wall-material-variant.js +10 -0
- package/dist/systems/wall/wall-materials.d.ts +6 -0
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +29 -13
- package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
- package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
- package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
- package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
- package/dist/systems/wall/wall-system.d.ts +11 -4
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +405 -153
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +9 -8
- package/dist/systems/window/window-system.d.ts +1 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +16 -2
- package/package.json +17 -4
- package/dist/lib/polygon-union.d.ts +0 -4
- package/dist/lib/polygon-union.d.ts.map +0 -1
- package/dist/lib/polygon-union.js +0 -289
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { emitter, sceneRegistry, useInteractive, useScene, } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
|
-
import { AWNING_WINDOW_SASH_NAME, CASEMENT_WINDOW_SASH_NAME, DOUBLE_HUNG_BOTTOM_SASH_NAME, DOUBLE_HUNG_TOP_SASH_NAME, FRENCH_CASEMENT_LEFT_SASH_NAME, FRENCH_CASEMENT_RIGHT_SASH_NAME, HOPPER_WINDOW_SASH_NAME, LOUVERED_WINDOW_SLATS_NAME, SINGLE_HUNG_ACTIVE_SASH_NAME, SLIDING_WINDOW_ACTIVE_PANEL_NAME, } from './window-system';
|
|
3
|
+
import { AWNING_WINDOW_SASH_NAME, CASEMENT_WINDOW_SASH_NAME, DOUBLE_HUNG_BOTTOM_SASH_NAME, DOUBLE_HUNG_TOP_SASH_NAME, FRENCH_CASEMENT_LEFT_SASH_NAME, FRENCH_CASEMENT_RIGHT_SASH_NAME, HOPPER_WINDOW_SASH_NAME, LOUVERED_WINDOW_SLATS_NAME, pendingWindowAnimationRebuilds, SINGLE_HUNG_ACTIVE_SASH_NAME, SLIDING_WINDOW_ACTIVE_PANEL_NAME, } from './window-system';
|
|
4
4
|
const easeWindowAnimation = (value) => value * value * (3 - 2 * value);
|
|
5
|
-
function markWindowDirty(windowId) {
|
|
6
|
-
const scene = useScene.getState();
|
|
7
|
-
const node = scene.nodes[windowId];
|
|
8
|
-
scene.dirtyNodes.add(windowId);
|
|
9
|
-
}
|
|
10
5
|
/**
|
|
11
6
|
* Pose a window's moving parts (sash/panel/slats) at `value` (0 = closed,
|
|
12
7
|
* 1 = open) by mutating the named child groups under `mesh`. Returns true when
|
|
@@ -123,18 +118,24 @@ export const WindowAnimationSystem = () => {
|
|
|
123
118
|
const value = animation.from + (animation.to - animation.from) * easeWindowAnimation(progress);
|
|
124
119
|
interactive.setWindowOpenState(typedWindowId, { [animation.field]: value });
|
|
125
120
|
const appliedDirectly = applyDirectWindowAnimation(typedWindowId, value);
|
|
121
|
+
// A dirty mark is one-shot work, not a needs-frame signal — per-tick
|
|
122
|
+
// marks kept the scene from ever settling. Types without a direct pose
|
|
123
|
+
// path get a transient rebuild request instead.
|
|
126
124
|
if (!appliedDirectly)
|
|
127
|
-
|
|
125
|
+
pendingWindowAnimationRebuilds.add(typedWindowId);
|
|
128
126
|
if (progress < 1)
|
|
129
127
|
continue;
|
|
130
128
|
interactive.cancelWindowAnimation(typedWindowId);
|
|
131
129
|
if (animation.persist) {
|
|
132
130
|
scene.updateNode(typedWindowId, { [animation.field]: animation.to });
|
|
133
131
|
interactive.removeWindowOpenState(typedWindowId);
|
|
134
|
-
|
|
132
|
+
// One-shot: the rebuild re-derives the pose from the persisted node.
|
|
133
|
+
scene.markDirty(typedWindowId);
|
|
135
134
|
}
|
|
136
135
|
else {
|
|
137
136
|
interactive.setWindowOpenState(typedWindowId, { [animation.field]: animation.to });
|
|
137
|
+
if (!appliedDirectly)
|
|
138
|
+
scene.markDirty(typedWindowId);
|
|
138
139
|
}
|
|
139
140
|
emitter.emit('window:animation-completed', {
|
|
140
141
|
windowId: typedWindowId,
|
|
@@ -10,6 +10,7 @@ export declare const DOUBLE_HUNG_BOTTOM_SASH_NAME = "double-hung-bottom-sash";
|
|
|
10
10
|
export declare const LOUVERED_WINDOW_SLATS_NAME = "louvered-window-slats";
|
|
11
11
|
export declare const AWNING_WINDOW_SASH_NAME = "awning-window-sash";
|
|
12
12
|
export declare const HOPPER_WINDOW_SASH_NAME = "hopper-window-sash";
|
|
13
|
+
export declare const pendingWindowAnimationRebuilds: Set<`building_${string}` | `level_${string}` | `elevator_${string}` | `item_${string}` | `block_${string}` | `ceiling_${string}` | `column_${string}` | `wall_${string}` | `roof_${string}` | `slab_${string}` | `fence_${string}` | `construction-dimension_${string}` | `structural-grid_${string}` | `zone_${string}` | `stair_${string}` | `scan_${string}` | `guide_${string}` | `measurement_${string}` | `spawn_${string}` | `shelf_${string}` | `duct-segment_${string}` | `duct-fitting_${string}` | `duct-terminal_${string}` | `hvac-equipment_${string}` | `lineset_${string}` | `liquid-line_${string}` | `pipe-segment_${string}` | `pipe-fitting_${string}` | `pipe-trap_${string}` | `site_${string}` | `leanto_${string}` | `door_${string}` | `window_${string}` | `cabinet-module_${string}` | `cabinet_${string}` | `rseg_${string}` | `sseg_${string}` | `bvent_${string}` | `rvent_${string}` | `tvent_${string}` | `cupola_${string}` | `eyebrow-vent_${string}` | `gutter_${string}` | `chimney_${string}` | `solarpanel_${string}` | `skylight_${string}` | `dormer_${string}` | `downspout_${string}`>;
|
|
13
14
|
export declare const WindowSystem: () => null;
|
|
14
15
|
/**
|
|
15
16
|
* Build a fresh window mesh for preview/ghost rendering.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"window-system.d.ts","sourceRoot":"","sources":["../../../src/systems/window/window-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAYL,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"window-system.d.ts","sourceRoot":"","sources":["../../../src/systems/window/window-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAYL,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAyB9B,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAC/D,eAAO,MAAM,8BAA8B,8BAA8B,CAAA;AACzE,eAAO,MAAM,+BAA+B,+BAA+B,CAAA;AAC3E,eAAO,MAAM,gCAAgC,gCAAgC,CAAA;AAC7E,eAAO,MAAM,4BAA4B,4BAA4B,CAAA;AACrE,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAC/D,eAAO,MAAM,4BAA4B,4BAA4B,CAAA;AACrE,eAAO,MAAM,0BAA0B,0BAA0B,CAAA;AACjE,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAC3D,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAS3D,eAAO,MAAM,8BAA8B,kkCAAuB,CAAA;AAElE,eAAO,MAAM,YAAY,YAoHxB,CAAA;AA28GD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,KAAK,CAAC,IAAI,CAInE"}
|
|
@@ -4,6 +4,7 @@ import { useEffect, useRef } from 'react';
|
|
|
4
4
|
import * as THREE from 'three';
|
|
5
5
|
import { applyWorldScaleBoxUVs } from '../../lib/box-uv';
|
|
6
6
|
import { createSurfaceRoleMaterial, glassMaterial as defaultGlassMaterial, baseMaterial as getBaseMaterial, resolveMaterialRef, } from '../../lib/materials';
|
|
7
|
+
import { timeSpan } from '../../lib/perf-tracks';
|
|
7
8
|
import useViewer from '../../store/use-viewer';
|
|
8
9
|
import { getOpeningCutoutProxyDepth } from '../wall/opening-cutout-geometry';
|
|
9
10
|
// Invisible material for root mesh — used as selection hitbox only
|
|
@@ -30,6 +31,10 @@ export const HOPPER_WINDOW_SASH_NAME = 'hopper-window-sash';
|
|
|
30
31
|
const MAX_WINDOW_REBUILDS_PER_FRAME = 16;
|
|
31
32
|
const WINDOW_PROGRESSIVE_DIRTY_THRESHOLD = MAX_WINDOW_REBUILDS_PER_FRAME;
|
|
32
33
|
const WINDOW_PROGRESSIVE_TIME_BUDGET_MS = 8;
|
|
34
|
+
// Transient rebuild requests from WindowAnimationSystem for windows whose type
|
|
35
|
+
// has no direct pose path: drained every frame. Deliberately not dirtyNodes —
|
|
36
|
+
// a running animation must not keep the dirty set from reaching zero.
|
|
37
|
+
export const pendingWindowAnimationRebuilds = new Set();
|
|
33
38
|
export const WindowSystem = () => {
|
|
34
39
|
const dirtyNodes = useScene((state) => state.dirtyNodes);
|
|
35
40
|
const clearDirty = useScene((state) => state.clearDirty);
|
|
@@ -74,7 +79,7 @@ export const WindowSystem = () => {
|
|
|
74
79
|
}
|
|
75
80
|
}, [sceneMaterials]);
|
|
76
81
|
useFrame(() => {
|
|
77
|
-
if (dirtyNodes.size === 0)
|
|
82
|
+
if (dirtyNodes.size === 0 && pendingWindowAnimationRebuilds.size === 0)
|
|
78
83
|
return;
|
|
79
84
|
baseMaterial = textures
|
|
80
85
|
? getBaseMaterial(shading)
|
|
@@ -94,6 +99,13 @@ export const WindowSystem = () => {
|
|
|
94
99
|
return;
|
|
95
100
|
dirtyWindowIds.push(id);
|
|
96
101
|
});
|
|
102
|
+
if (pendingWindowAnimationRebuilds.size > 0) {
|
|
103
|
+
for (const id of pendingWindowAnimationRebuilds) {
|
|
104
|
+
if (nodes[id]?.type === 'window' && !dirtyWindowIds.includes(id))
|
|
105
|
+
dirtyWindowIds.push(id);
|
|
106
|
+
}
|
|
107
|
+
pendingWindowAnimationRebuilds.clear();
|
|
108
|
+
}
|
|
97
109
|
const useProgressiveWindowRebuilds = dirtyWindowIds.length > WINDOW_PROGRESSIVE_DIRTY_THRESHOLD;
|
|
98
110
|
const frameStartedAt = performance.now();
|
|
99
111
|
let rebuiltWindowsThisFrame = 0;
|
|
@@ -116,7 +128,9 @@ export const WindowSystem = () => {
|
|
|
116
128
|
// Merge any live override (width / height / position) so the mesh
|
|
117
129
|
// rebuild reflects the in-flight drag without zustand churn.
|
|
118
130
|
const effectiveNode = getEffectiveNode(node);
|
|
119
|
-
updateWindowMesh(effectiveNode, mesh)
|
|
131
|
+
timeSpan('window', () => updateWindowMesh(effectiveNode, mesh), {
|
|
132
|
+
properties: [['node', id]],
|
|
133
|
+
});
|
|
120
134
|
clearDirty(id);
|
|
121
135
|
rebuiltWindowsThisFrame += 1;
|
|
122
136
|
// Rebuild the parent wall so its cutout reflects the updated window geometry
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/viewer",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "3D viewer component for Pascal building editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js",
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./capture": {
|
|
15
|
+
"types": "./dist/capture/index.d.ts",
|
|
16
|
+
"import": "./dist/capture/index.js",
|
|
17
|
+
"default": "./dist/capture/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./capture/preview": {
|
|
20
|
+
"types": "./dist/capture/preview.d.ts",
|
|
21
|
+
"import": "./dist/capture/preview.js",
|
|
22
|
+
"default": "./dist/capture/preview.js"
|
|
13
23
|
}
|
|
14
24
|
},
|
|
15
25
|
"files": [
|
|
@@ -23,11 +33,12 @@
|
|
|
23
33
|
"prepublishOnly": "npm run build"
|
|
24
34
|
},
|
|
25
35
|
"peerDependencies": {
|
|
26
|
-
"@pascal-app/core": "^1.0.0
|
|
36
|
+
"@pascal-app/core": "^1.0.0",
|
|
27
37
|
"@react-three/drei": "^10",
|
|
28
38
|
"@react-three/fiber": "^9",
|
|
29
39
|
"react": "^18 || ^19",
|
|
30
|
-
"
|
|
40
|
+
"react-dom": "^18 || ^19",
|
|
41
|
+
"three": "^0.186"
|
|
31
42
|
},
|
|
32
43
|
"dependencies": {
|
|
33
44
|
"three-bvh-csg": "^0.0.18",
|
|
@@ -35,10 +46,12 @@
|
|
|
35
46
|
"zustand": "^5"
|
|
36
47
|
},
|
|
37
48
|
"devDependencies": {
|
|
38
|
-
"@pascal-app/core": "^1.0.0
|
|
49
|
+
"@pascal-app/core": "^1.0.0",
|
|
39
50
|
"@pascal/typescript-config": "*",
|
|
51
|
+
"@react-three/test-renderer": "^9.1.0",
|
|
40
52
|
"@types/node": "^22",
|
|
41
53
|
"@types/react": "^19.2.2",
|
|
54
|
+
"@types/react-dom": "19.2.2",
|
|
42
55
|
"@types/three": "^0.184.0",
|
|
43
56
|
"typescript": "6.0.3"
|
|
44
57
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"polygon-union.d.ts","sourceRoot":"","sources":["../../src/lib/polygon-union.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAwStC,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,EAAE,CAShE;AA4CD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,EAAE,CAkBjG"}
|
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
const EPSILON = 1e-7;
|
|
2
|
-
const KEY_SCALE = 1e6;
|
|
3
|
-
function pointsEqual(a, b, tolerance = EPSILON) {
|
|
4
|
-
return Math.hypot(a[0] - b[0], a[1] - b[1]) <= tolerance;
|
|
5
|
-
}
|
|
6
|
-
function pointKey(point) {
|
|
7
|
-
return `${Math.round(point[0] * KEY_SCALE)}:${Math.round(point[1] * KEY_SCALE)}`;
|
|
8
|
-
}
|
|
9
|
-
function interpolate(a, b, t) {
|
|
10
|
-
return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
|
11
|
-
}
|
|
12
|
-
function cross(ax, ay, bx, by) {
|
|
13
|
-
return ax * by - ay * bx;
|
|
14
|
-
}
|
|
15
|
-
function polygonArea(points) {
|
|
16
|
-
let area = 0;
|
|
17
|
-
for (let i = 0; i < points.length; i++) {
|
|
18
|
-
const current = points[i];
|
|
19
|
-
const next = points[(i + 1) % points.length];
|
|
20
|
-
area += current[0] * next[1] - next[0] * current[1];
|
|
21
|
-
}
|
|
22
|
-
return area / 2;
|
|
23
|
-
}
|
|
24
|
-
function pointOnSegment(point, start, end) {
|
|
25
|
-
const dx = end[0] - start[0];
|
|
26
|
-
const dz = end[1] - start[1];
|
|
27
|
-
const crossValue = cross(point[0] - start[0], point[1] - start[1], dx, dz);
|
|
28
|
-
if (Math.abs(crossValue) > EPSILON)
|
|
29
|
-
return false;
|
|
30
|
-
const dot = (point[0] - start[0]) * (point[0] - end[0]) + (point[1] - start[1]) * (point[1] - end[1]);
|
|
31
|
-
return dot <= EPSILON;
|
|
32
|
-
}
|
|
33
|
-
function pointInPolygonOrOnBoundary(point, polygon) {
|
|
34
|
-
if (polygon.some((start, index) => pointOnSegment(point, start, polygon[(index + 1) % polygon.length]))) {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
return pointInPolygon(point, polygon);
|
|
38
|
-
}
|
|
39
|
-
function pointInPolygon(point, polygon) {
|
|
40
|
-
let inside = false;
|
|
41
|
-
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
42
|
-
const pi = polygon[i];
|
|
43
|
-
const pj = polygon[j];
|
|
44
|
-
if (pointOnSegment(point, pj, pi))
|
|
45
|
-
return false;
|
|
46
|
-
const intersects = pi[1] > point[1] !== pj[1] > point[1] &&
|
|
47
|
-
point[0] < ((pj[0] - pi[0]) * (point[1] - pi[1])) / (pj[1] - pi[1]) + pi[0];
|
|
48
|
-
if (intersects)
|
|
49
|
-
inside = !inside;
|
|
50
|
-
}
|
|
51
|
-
return inside;
|
|
52
|
-
}
|
|
53
|
-
function normalizeRing(ring) {
|
|
54
|
-
const normalized = [];
|
|
55
|
-
for (const [x, z] of ring) {
|
|
56
|
-
if (!Number.isFinite(x) || !Number.isFinite(z))
|
|
57
|
-
continue;
|
|
58
|
-
const point = [x, z];
|
|
59
|
-
const previous = normalized[normalized.length - 1];
|
|
60
|
-
if (!previous || !pointsEqual(previous, point)) {
|
|
61
|
-
normalized.push(point);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
const first = normalized[0];
|
|
65
|
-
const last = normalized[normalized.length - 1];
|
|
66
|
-
if (first && last && pointsEqual(first, last)) {
|
|
67
|
-
normalized.pop();
|
|
68
|
-
}
|
|
69
|
-
if (normalized.length < 3 || Math.abs(polygonArea(normalized)) <= EPSILON)
|
|
70
|
-
return [];
|
|
71
|
-
return polygonArea(normalized) < 0 ? [...normalized].reverse() : normalized;
|
|
72
|
-
}
|
|
73
|
-
function addSplit(edge, t) {
|
|
74
|
-
if (t < -EPSILON || t > 1 + EPSILON)
|
|
75
|
-
return;
|
|
76
|
-
const clamped = Math.max(0, Math.min(1, t));
|
|
77
|
-
if (edge.splits.some((split) => Math.abs(split - clamped) <= EPSILON))
|
|
78
|
-
return;
|
|
79
|
-
edge.splits.push(clamped);
|
|
80
|
-
}
|
|
81
|
-
function parameterOnEdge(point, edge) {
|
|
82
|
-
const dx = edge.end[0] - edge.start[0];
|
|
83
|
-
const dz = edge.end[1] - edge.start[1];
|
|
84
|
-
const lengthSquared = dx * dx + dz * dz;
|
|
85
|
-
if (lengthSquared <= EPSILON)
|
|
86
|
-
return 0;
|
|
87
|
-
return ((point[0] - edge.start[0]) * dx + (point[1] - edge.start[1]) * dz) / lengthSquared;
|
|
88
|
-
}
|
|
89
|
-
function addIntersectionSplits(left, right) {
|
|
90
|
-
const rx = left.end[0] - left.start[0];
|
|
91
|
-
const rz = left.end[1] - left.start[1];
|
|
92
|
-
const sx = right.end[0] - right.start[0];
|
|
93
|
-
const sz = right.end[1] - right.start[1];
|
|
94
|
-
const qpx = right.start[0] - left.start[0];
|
|
95
|
-
const qpz = right.start[1] - left.start[1];
|
|
96
|
-
const denominator = cross(rx, rz, sx, sz);
|
|
97
|
-
const numerator = cross(qpx, qpz, rx, rz);
|
|
98
|
-
if (Math.abs(denominator) <= EPSILON) {
|
|
99
|
-
if (Math.abs(numerator) > EPSILON)
|
|
100
|
-
return;
|
|
101
|
-
for (const point of [left.start, left.end, right.start, right.end]) {
|
|
102
|
-
if (pointOnSegment(point, left.start, left.end) &&
|
|
103
|
-
pointOnSegment(point, right.start, right.end)) {
|
|
104
|
-
addSplit(left, parameterOnEdge(point, left));
|
|
105
|
-
addSplit(right, parameterOnEdge(point, right));
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
const t = cross(qpx, qpz, sx, sz) / denominator;
|
|
111
|
-
const u = cross(qpx, qpz, rx, rz) / denominator;
|
|
112
|
-
if (t < -EPSILON || t > 1 + EPSILON || u < -EPSILON || u > 1 + EPSILON)
|
|
113
|
-
return;
|
|
114
|
-
addSplit(left, t);
|
|
115
|
-
addSplit(right, u);
|
|
116
|
-
}
|
|
117
|
-
function buildEdges(polygons) {
|
|
118
|
-
const edges = [];
|
|
119
|
-
polygons.forEach((polygon, polygonIndex) => {
|
|
120
|
-
for (let i = 0; i < polygon.length; i++) {
|
|
121
|
-
edges.push({
|
|
122
|
-
start: polygon[i],
|
|
123
|
-
end: polygon[(i + 1) % polygon.length],
|
|
124
|
-
polygonIndex,
|
|
125
|
-
splits: [0, 1],
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
for (let i = 0; i < edges.length; i++) {
|
|
130
|
-
for (let j = i + 1; j < edges.length; j++) {
|
|
131
|
-
const left = edges[i];
|
|
132
|
-
const right = edges[j];
|
|
133
|
-
if (left.polygonIndex === right.polygonIndex)
|
|
134
|
-
continue;
|
|
135
|
-
addIntersectionSplits(left, right);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return edges;
|
|
139
|
-
}
|
|
140
|
-
function buildBoundarySegments(edges, polygons) {
|
|
141
|
-
const segments = [];
|
|
142
|
-
for (const edge of edges) {
|
|
143
|
-
const splits = [...edge.splits].sort((a, b) => a - b);
|
|
144
|
-
for (let i = 0; i < splits.length - 1; i++) {
|
|
145
|
-
const startT = splits[i];
|
|
146
|
-
const endT = splits[i + 1];
|
|
147
|
-
if (endT - startT <= EPSILON)
|
|
148
|
-
continue;
|
|
149
|
-
const start = interpolate(edge.start, edge.end, startT);
|
|
150
|
-
const end = interpolate(edge.start, edge.end, endT);
|
|
151
|
-
const mid = interpolate(edge.start, edge.end, (startT + endT) / 2);
|
|
152
|
-
const insideAnother = polygons.some((polygon, index) => index !== edge.polygonIndex && pointInPolygon(mid, polygon));
|
|
153
|
-
if (!insideAnother) {
|
|
154
|
-
segments.push({ start, end, used: false });
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return removeDuplicateInteriorSegments(segments);
|
|
159
|
-
}
|
|
160
|
-
function segmentKey(segment) {
|
|
161
|
-
const start = pointKey(segment.start);
|
|
162
|
-
const end = pointKey(segment.end);
|
|
163
|
-
return start < end ? `${start}|${end}` : `${end}|${start}`;
|
|
164
|
-
}
|
|
165
|
-
function removeDuplicateInteriorSegments(segments) {
|
|
166
|
-
const groups = new Map();
|
|
167
|
-
for (const segment of segments) {
|
|
168
|
-
const key = segmentKey(segment);
|
|
169
|
-
const group = groups.get(key);
|
|
170
|
-
if (group) {
|
|
171
|
-
group.push(segment);
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
groups.set(key, [segment]);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
const result = [];
|
|
178
|
-
for (const group of groups.values()) {
|
|
179
|
-
if (group.length === 1) {
|
|
180
|
-
result.push(group[0]);
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
const firstStart = pointKey(group[0].start);
|
|
184
|
-
const firstEnd = pointKey(group[0].end);
|
|
185
|
-
const hasOppositeDirection = group.some((segment) => pointKey(segment.start) === firstEnd && pointKey(segment.end) === firstStart);
|
|
186
|
-
if (!hasOppositeDirection) {
|
|
187
|
-
result.push(group[0]);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return result;
|
|
191
|
-
}
|
|
192
|
-
function assembleRings(segments) {
|
|
193
|
-
const byStart = new Map();
|
|
194
|
-
for (const segment of segments) {
|
|
195
|
-
const key = pointKey(segment.start);
|
|
196
|
-
const group = byStart.get(key);
|
|
197
|
-
if (group) {
|
|
198
|
-
group.push(segment);
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
byStart.set(key, [segment]);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
const rings = [];
|
|
205
|
-
for (const firstSegment of segments) {
|
|
206
|
-
if (firstSegment.used)
|
|
207
|
-
continue;
|
|
208
|
-
firstSegment.used = true;
|
|
209
|
-
const ring = [firstSegment.start, firstSegment.end];
|
|
210
|
-
const startKey = pointKey(firstSegment.start);
|
|
211
|
-
let currentKey = pointKey(firstSegment.end);
|
|
212
|
-
while (currentKey !== startKey) {
|
|
213
|
-
const next = byStart.get(currentKey)?.find((segment) => !segment.used);
|
|
214
|
-
if (!next)
|
|
215
|
-
break;
|
|
216
|
-
next.used = true;
|
|
217
|
-
ring.push(next.end);
|
|
218
|
-
currentKey = pointKey(next.end);
|
|
219
|
-
}
|
|
220
|
-
if (currentKey !== startKey)
|
|
221
|
-
continue;
|
|
222
|
-
const last = ring[ring.length - 1];
|
|
223
|
-
if (last && pointsEqual(ring[0], last)) {
|
|
224
|
-
ring.pop();
|
|
225
|
-
}
|
|
226
|
-
const normalized = normalizeRing(ring);
|
|
227
|
-
if (normalized.length >= 3) {
|
|
228
|
-
rings.push(normalized);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return rings;
|
|
232
|
-
}
|
|
233
|
-
export function unionPolygons(polygons) {
|
|
234
|
-
const validPolygons = polygons.map(normalizeRing).filter((polygon) => polygon.length >= 3);
|
|
235
|
-
if (validPolygons.length <= 1)
|
|
236
|
-
return validPolygons;
|
|
237
|
-
const edges = buildEdges(validPolygons);
|
|
238
|
-
const segments = buildBoundarySegments(edges, validPolygons);
|
|
239
|
-
const rings = assembleRings(segments);
|
|
240
|
-
return rings.length > 0 ? rings : validPolygons;
|
|
241
|
-
}
|
|
242
|
-
function buildDifferenceBoundarySegments(edges, polygons) {
|
|
243
|
-
const subject = polygons[0];
|
|
244
|
-
const cutters = polygons.slice(1);
|
|
245
|
-
if (!subject)
|
|
246
|
-
return [];
|
|
247
|
-
const segments = [];
|
|
248
|
-
for (const edge of edges) {
|
|
249
|
-
const splits = [...edge.splits].sort((a, b) => a - b);
|
|
250
|
-
for (let i = 0; i < splits.length - 1; i++) {
|
|
251
|
-
const startT = splits[i];
|
|
252
|
-
const endT = splits[i + 1];
|
|
253
|
-
if (endT - startT <= EPSILON)
|
|
254
|
-
continue;
|
|
255
|
-
const start = interpolate(edge.start, edge.end, startT);
|
|
256
|
-
const end = interpolate(edge.start, edge.end, endT);
|
|
257
|
-
const mid = interpolate(edge.start, edge.end, (startT + endT) / 2);
|
|
258
|
-
if (edge.polygonIndex === 0) {
|
|
259
|
-
const insideCutter = cutters.some((cutter) => pointInPolygonOrOnBoundary(mid, cutter));
|
|
260
|
-
if (!insideCutter) {
|
|
261
|
-
segments.push({ start, end, used: false });
|
|
262
|
-
}
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
const insideSubject = pointInPolygon(mid, subject);
|
|
266
|
-
const insideAnotherCutter = cutters.some((cutter, cutterIndex) => cutterIndex !== edge.polygonIndex - 1 && pointInPolygonOrOnBoundary(mid, cutter));
|
|
267
|
-
if (insideSubject && !insideAnotherCutter) {
|
|
268
|
-
segments.push({ start: end, end: start, used: false });
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return removeDuplicateInteriorSegments(segments);
|
|
273
|
-
}
|
|
274
|
-
export function subtractPolygonsFromPolygon(subject, cutters) {
|
|
275
|
-
const validSubject = normalizeRing(subject);
|
|
276
|
-
if (validSubject.length < 3)
|
|
277
|
-
return [];
|
|
278
|
-
const validCutters = cutters.map(normalizeRing).filter((polygon) => polygon.length >= 3);
|
|
279
|
-
if (validCutters.length === 0)
|
|
280
|
-
return [validSubject];
|
|
281
|
-
const polygons = [validSubject, ...validCutters];
|
|
282
|
-
const edges = buildEdges(polygons);
|
|
283
|
-
const segments = buildDifferenceBoundarySegments(edges, polygons);
|
|
284
|
-
const rings = assembleRings(segments);
|
|
285
|
-
if (rings.length > 0)
|
|
286
|
-
return rings;
|
|
287
|
-
const fullyCovered = validSubject.every((point) => validCutters.some((cutter) => pointInPolygonOrOnBoundary(point, cutter)));
|
|
288
|
-
return fullyCovered ? [] : [validSubject];
|
|
289
|
-
}
|