@pascal-app/viewer 0.9.2 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -5
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +25 -5
- package/dist/components/viewer/glb-scene.js +2 -2
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +10 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -1
- package/dist/components/viewer/glb-walkthrough-controller.js +96 -8
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +9 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +1 -0
- package/dist/components/viewer/selection-manager.js +5 -2
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +2 -5
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -8
- package/dist/lib/hero-pose.d.ts +47 -0
- package/dist/lib/hero-pose.d.ts.map +1 -0
- package/dist/lib/hero-pose.js +227 -0
- package/dist/lib/ktx2-loader.d.ts +3 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -1
- package/dist/lib/ktx2-loader.js +6 -0
- package/dist/lib/materials.d.ts +2 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +56 -9
- package/dist/lib/materials.test.d.ts +2 -0
- package/dist/lib/materials.test.d.ts.map +1 -0
- package/dist/lib/materials.test.js +25 -0
- package/dist/lib/merged-outline-node.test.d.ts +2 -0
- package/dist/lib/merged-outline-node.test.d.ts.map +1 -0
- package/dist/lib/merged-outline-node.test.js +22 -0
- package/dist/lib/snapshot-pipeline.d.ts +42 -0
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -0
- package/dist/lib/snapshot-pipeline.js +254 -0
- package/dist/lib/terrain-perimeter-fill.d.ts +7 -0
- package/dist/lib/terrain-perimeter-fill.d.ts.map +1 -0
- package/dist/lib/terrain-perimeter-fill.js +75 -0
- package/dist/lib/texture-node-guard.d.ts +13 -0
- package/dist/lib/texture-node-guard.d.ts.map +1 -0
- package/dist/lib/texture-node-guard.js +39 -0
- package/dist/lib/texture-reference.d.ts +31 -0
- package/dist/lib/texture-reference.d.ts.map +1 -0
- package/dist/lib/texture-reference.js +139 -0
- package/dist/lib/texture-reference.test.d.ts +2 -0
- package/dist/lib/texture-reference.test.d.ts.map +1 -0
- package/dist/lib/texture-reference.test.js +86 -0
- package/dist/store/use-viewer.d.ts +13 -0
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +16 -1
- package/dist/store/use-viewer.test.js +19 -0
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +8 -4
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +30 -7
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +8 -7
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +7 -6
- package/dist/systems/slab/slab-system.d.ts +5 -4
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +24 -94
- package/dist/systems/slab/slab-system.test.js +51 -12
- package/dist/systems/stair/stair-system.js +1 -1
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +5 -2
- package/dist/systems/wall/wall-support-extension.test.js +69 -1
- package/dist/systems/wall/wall-system.d.ts +3 -7
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +87 -22
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts +2 -0
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-terrain-bottom.test.js +34 -0
- package/package.json +2 -2
- package/dist/components/viewer/walkthrough-controls.d.ts +0 -3
- package/dist/components/viewer/walkthrough-controls.d.ts.map +0 -1
- package/dist/components/viewer/walkthrough-controls.js +0 -151
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// @ts-expect-error — bun:test is provided by the Bun runtime; viewer does not
|
|
2
|
+
// depend on @types/bun so the import type is unresolved at compile time.
|
|
3
|
+
import { describe, expect, test } from 'bun:test';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
import { getPascalTextureRef, stampPascalTextureRef } from './texture-reference';
|
|
6
|
+
// The module reads the storage origin lazily on first use, so setting the env
|
|
7
|
+
// here (before any stamp call) pins it for the whole test file.
|
|
8
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL ??= 'https://test-storage.supabase.co';
|
|
9
|
+
const STORAGE_ORIGIN = new URL(process.env.NEXT_PUBLIC_SUPABASE_URL).origin;
|
|
10
|
+
describe('Pascal texture references', () => {
|
|
11
|
+
test("resolves 'material' input to library-material for storage-bucket URLs", () => {
|
|
12
|
+
const texture = new THREE.Texture();
|
|
13
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
14
|
+
const src = `${STORAGE_ORIGIN}/storage/v1/object/public/materials/user/mtl_1/oak_basecolor_512.ktx2`;
|
|
15
|
+
const ref = stampPascalTextureRef(texture, { kind: 'material', src, slot: 'map' });
|
|
16
|
+
expect(ref).toEqual({
|
|
17
|
+
v: 1,
|
|
18
|
+
kind: 'library-material',
|
|
19
|
+
src,
|
|
20
|
+
map: 'basecolor',
|
|
21
|
+
colorSpace: 'srgb',
|
|
22
|
+
});
|
|
23
|
+
expect(getPascalTextureRef(texture)).toEqual(ref);
|
|
24
|
+
});
|
|
25
|
+
test("resolves 'material' input to app-material for assets-CDN catalog URLs", () => {
|
|
26
|
+
const cdnOrigin = new URL(process.env.NEXT_PUBLIC_ASSETS_CDN_URL || 'https://editor.pascal.app')
|
|
27
|
+
.origin;
|
|
28
|
+
const texture = new THREE.Texture();
|
|
29
|
+
const src = `${cdnOrigin}/material/concrete/prepared_drywall/prepared_drywall_normal_512.ktx2`;
|
|
30
|
+
const ref = stampPascalTextureRef(texture, { kind: 'material', src, slot: 'normalMap' });
|
|
31
|
+
expect(ref).toEqual({ v: 1, kind: 'app-material', src, map: 'normal', colorSpace: 'linear' });
|
|
32
|
+
expect(getPascalTextureRef(texture)).toEqual(ref);
|
|
33
|
+
const foreign = new THREE.Texture();
|
|
34
|
+
expect(stampPascalTextureRef(foreign, {
|
|
35
|
+
kind: 'material',
|
|
36
|
+
src: 'https://example.com/material/concrete/x/x_basecolor_512.ktx2',
|
|
37
|
+
slot: 'map',
|
|
38
|
+
})).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
test('stamps the exact project-asset payload for Pascal storage URLs', () => {
|
|
41
|
+
const texture = new THREE.Texture();
|
|
42
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
43
|
+
const ref = stampPascalTextureRef(texture, {
|
|
44
|
+
kind: 'project-asset',
|
|
45
|
+
src: `${STORAGE_ORIGIN}/storage/v1/object/public/project-assets/project/asset.png`,
|
|
46
|
+
slot: 'map',
|
|
47
|
+
});
|
|
48
|
+
expect(ref).toEqual({
|
|
49
|
+
v: 1,
|
|
50
|
+
kind: 'project-asset',
|
|
51
|
+
src: `${STORAGE_ORIGIN}/storage/v1/object/public/project-assets/project/asset.png`,
|
|
52
|
+
map: 'basecolor',
|
|
53
|
+
colorSpace: 'srgb',
|
|
54
|
+
});
|
|
55
|
+
expect(getPascalTextureRef(texture)).toEqual(ref);
|
|
56
|
+
});
|
|
57
|
+
test('keeps local and non-Pascal URLs unstamped', () => {
|
|
58
|
+
for (const src of [
|
|
59
|
+
'asset://project/asset',
|
|
60
|
+
'blob:https://editor.pascal.app/asset',
|
|
61
|
+
'data:image/png;base64,AAAA',
|
|
62
|
+
'https://example.com/storage/v1/object/public/project-assets/project/asset.png',
|
|
63
|
+
]) {
|
|
64
|
+
const texture = new THREE.Texture();
|
|
65
|
+
expect(stampPascalTextureRef(texture, { kind: 'project-asset', src, slot: 'map' })).toBeNull();
|
|
66
|
+
expect(texture.userData.pascalTextureRef).toBeUndefined();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
test('includes imageIndex only for item GLB references', () => {
|
|
70
|
+
const texture = new THREE.Texture();
|
|
71
|
+
const ref = stampPascalTextureRef(texture, {
|
|
72
|
+
kind: 'item-glb',
|
|
73
|
+
src: `${STORAGE_ORIGIN}/storage/v1/object/public/items/system/chair/model.glb`,
|
|
74
|
+
slot: 'normalMap',
|
|
75
|
+
imageIndex: 2,
|
|
76
|
+
});
|
|
77
|
+
expect(ref).toEqual({
|
|
78
|
+
v: 1,
|
|
79
|
+
kind: 'item-glb',
|
|
80
|
+
src: `${STORAGE_ORIGIN}/storage/v1/object/public/items/system/chair/model.glb`,
|
|
81
|
+
imageIndex: 2,
|
|
82
|
+
map: 'normal',
|
|
83
|
+
colorSpace: 'linear',
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -3,6 +3,7 @@ import type { Object3D } from 'three';
|
|
|
3
3
|
import type { EdgeMode } from '../lib/edge-style';
|
|
4
4
|
import type { ColorPreset, RenderShading } from '../lib/materials';
|
|
5
5
|
export type RenderContext = 'editor' | 'viewer';
|
|
6
|
+
export type MetricNotation = 'meters' | 'millimeters';
|
|
6
7
|
type SelectionPath = {
|
|
7
8
|
buildingId: BuildingNode['id'] | null;
|
|
8
9
|
levelId: LevelNode['id'] | null;
|
|
@@ -17,6 +18,10 @@ type ViewerState = {
|
|
|
17
18
|
selection: SelectionPath;
|
|
18
19
|
previewSelectedIds: BaseNode['id'][];
|
|
19
20
|
setPreviewSelectedIds: (ids: BaseNode['id'][]) => void;
|
|
21
|
+
/** Host-owned selection highlights rendered through the viewer's native
|
|
22
|
+
* selection paths without changing the local user's editable selection. */
|
|
23
|
+
externalSelectedIds: BaseNode['id'][];
|
|
24
|
+
setExternalSelectedIds: (ids: BaseNode['id'][]) => void;
|
|
20
25
|
hoverHighlightMode: string;
|
|
21
26
|
setHoverHighlightMode: (mode: string) => void;
|
|
22
27
|
hoveredId: AnyNode['id'] | ZoneNode['id'] | null;
|
|
@@ -57,6 +62,8 @@ type ViewerState = {
|
|
|
57
62
|
setShadows: (shadows: boolean) => void;
|
|
58
63
|
unit: 'metric' | 'imperial';
|
|
59
64
|
setUnit: (unit: 'metric' | 'imperial') => void;
|
|
65
|
+
metricNotation: MetricNotation;
|
|
66
|
+
setMetricNotation: (notation: MetricNotation) => void;
|
|
60
67
|
/** True once the user explicitly picked a unit. Until then `unit` is a
|
|
61
68
|
* locale-derived default and is not persisted, so the default can keep
|
|
62
69
|
* tracking the browser locale across sessions. */
|
|
@@ -100,6 +107,10 @@ type ViewerState = {
|
|
|
100
107
|
setDebugColors: (enabled: boolean) => void;
|
|
101
108
|
walkthroughMode: boolean;
|
|
102
109
|
setWalkthroughMode: (mode: boolean) => void;
|
|
110
|
+
/** Pointer lock temporarily released mid-walkthrough (⌘/PrintScreen — OS
|
|
111
|
+
* screenshot needs a movable cursor); clicking the canvas re-locks. */
|
|
112
|
+
walkthroughSuspended: boolean;
|
|
113
|
+
setWalkthroughSuspended: (suspended: boolean) => void;
|
|
103
114
|
cameraDragging: boolean;
|
|
104
115
|
setCameraDragging: (dragging: boolean) => void;
|
|
105
116
|
/**
|
|
@@ -120,6 +131,7 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
|
|
|
120
131
|
setState(state: ViewerState | ((state: ViewerState) => ViewerState), replace: true): unknown;
|
|
121
132
|
persist: {
|
|
122
133
|
setOptions: (options: Partial<import("zustand/middleware").PersistOptions<ViewerState, {
|
|
134
|
+
metricNotation: MetricNotation;
|
|
123
135
|
levelMode: "manual" | "stacked" | "exploded" | "solo";
|
|
124
136
|
wallMode: "up" | "down" | "cutaway" | "translucent";
|
|
125
137
|
projectPreferences: Record<string, {
|
|
@@ -143,6 +155,7 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
|
|
|
143
155
|
onHydrate: (fn: (state: ViewerState) => void) => () => void;
|
|
144
156
|
onFinishHydration: (fn: (state: ViewerState) => void) => () => void;
|
|
145
157
|
getOptions: () => Partial<import("zustand/middleware").PersistOptions<ViewerState, {
|
|
158
|
+
metricNotation: MetricNotation;
|
|
146
159
|
levelMode: "manual" | "stacked" | "exploded" | "solo";
|
|
147
160
|
wallMode: "up" | "down" | "cutaway" | "translucent";
|
|
148
161
|
projectPreferences: Record<string, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC5F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAIrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAGlE,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"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"}
|
package/dist/store/use-viewer.js
CHANGED
|
@@ -7,6 +7,7 @@ const RENDER_SHADINGS = ['solid', 'rendered'];
|
|
|
7
7
|
const COLOR_PRESETS = ['clay', 'white', 'mono', 'blueprint'];
|
|
8
8
|
const EDGE_MODES = ['off', 'soft', 'strong'];
|
|
9
9
|
const UNITS = ['metric', 'imperial'];
|
|
10
|
+
const METRIC_NOTATIONS = ['meters', 'millimeters'];
|
|
10
11
|
const LEVEL_MODES = ['stacked', 'exploded', 'solo', 'manual'];
|
|
11
12
|
const WALL_MODES = ['up', 'cutaway', 'down', 'translucent'];
|
|
12
13
|
// Countries still on imperial/US customary units: United States, Liberia, Myanmar.
|
|
@@ -112,6 +113,7 @@ function normalizePersistedViewerState(value) {
|
|
|
112
113
|
edges: pickString(state.edges, EDGE_MODES, 'soft'),
|
|
113
114
|
shadows: typeof state.shadows === 'boolean' ? state.shadows : true,
|
|
114
115
|
unit: pickString(state.unit, UNITS, detectDefaultUnit()),
|
|
116
|
+
metricNotation: pickString(state.metricNotation, METRIC_NOTATIONS, 'meters'),
|
|
115
117
|
unitExplicit: typeof state.unit === 'string' && UNITS.includes(state.unit),
|
|
116
118
|
levelMode: pickString(state.levelMode, LEVEL_MODES, 'stacked'),
|
|
117
119
|
wallMode: pickString(state.wallMode, WALL_MODES, 'up'),
|
|
@@ -122,6 +124,14 @@ const useViewer = create()(persist((set) => ({
|
|
|
122
124
|
selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [] },
|
|
123
125
|
previewSelectedIds: [],
|
|
124
126
|
setPreviewSelectedIds: (ids) => set({ previewSelectedIds: ids }),
|
|
127
|
+
externalSelectedIds: [],
|
|
128
|
+
setExternalSelectedIds: (ids) => set((state) => {
|
|
129
|
+
if (state.externalSelectedIds.length === ids.length &&
|
|
130
|
+
state.externalSelectedIds.every((id, index) => id === ids[index])) {
|
|
131
|
+
return state;
|
|
132
|
+
}
|
|
133
|
+
return { externalSelectedIds: ids };
|
|
134
|
+
}),
|
|
125
135
|
hoverHighlightMode: 'default',
|
|
126
136
|
setHoverHighlightMode: (mode) => set((state) => (state.hoverHighlightMode === mode ? state : { hoverHighlightMode: mode })),
|
|
127
137
|
hoveredId: null,
|
|
@@ -162,8 +172,10 @@ const useViewer = create()(persist((set) => ({
|
|
|
162
172
|
shadows: true,
|
|
163
173
|
setShadows: (shadows) => set({ shadows }),
|
|
164
174
|
unit: detectDefaultUnit(),
|
|
175
|
+
metricNotation: 'meters',
|
|
165
176
|
unitExplicit: false,
|
|
166
177
|
setUnit: (unit) => set({ unit, unitExplicit: true }),
|
|
178
|
+
setMetricNotation: (metricNotation) => set({ unit: 'metric', metricNotation, unitExplicit: true }),
|
|
167
179
|
levelMode: 'stacked',
|
|
168
180
|
setLevelMode: (mode) => set({ levelMode: mode }),
|
|
169
181
|
wallMode: 'up',
|
|
@@ -272,7 +284,9 @@ const useViewer = create()(persist((set) => ({
|
|
|
272
284
|
debugColors: false,
|
|
273
285
|
setDebugColors: (enabled) => set({ debugColors: enabled }),
|
|
274
286
|
walkthroughMode: false,
|
|
275
|
-
setWalkthroughMode: (mode) => set({ walkthroughMode: mode }),
|
|
287
|
+
setWalkthroughMode: (mode) => set({ walkthroughMode: mode, walkthroughSuspended: false }),
|
|
288
|
+
walkthroughSuspended: false,
|
|
289
|
+
setWalkthroughSuspended: (suspended) => set({ walkthroughSuspended: suspended }),
|
|
276
290
|
cameraDragging: false,
|
|
277
291
|
setCameraDragging: (dragging) => set({ cameraDragging: dragging }),
|
|
278
292
|
inputDragging: false,
|
|
@@ -292,6 +306,7 @@ const useViewer = create()(persist((set) => ({
|
|
|
292
306
|
edges: state.edges,
|
|
293
307
|
shadows: state.shadows,
|
|
294
308
|
...(state.unitExplicit ? { unit: state.unit } : {}),
|
|
309
|
+
metricNotation: state.metricNotation,
|
|
295
310
|
levelMode: state.levelMode,
|
|
296
311
|
wallMode: state.wallMode,
|
|
297
312
|
projectPreferences: state.projectPreferences,
|
|
@@ -4,10 +4,12 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
|
4
4
|
import useViewer from './use-viewer';
|
|
5
5
|
const resetMeasurementPreferences = () => {
|
|
6
6
|
useViewer.setState({
|
|
7
|
+
externalSelectedIds: [],
|
|
7
8
|
projectId: null,
|
|
8
9
|
projectPreferences: {},
|
|
9
10
|
showMeasurements: true,
|
|
10
11
|
unit: 'metric',
|
|
12
|
+
metricNotation: 'meters',
|
|
11
13
|
});
|
|
12
14
|
};
|
|
13
15
|
beforeEach(resetMeasurementPreferences);
|
|
@@ -39,4 +41,21 @@ describe('measurement display preferences', () => {
|
|
|
39
41
|
expect(useViewer.getState().projectPreferences).toEqual(preferences);
|
|
40
42
|
expect(useViewer.getState().showMeasurements).toBe(false);
|
|
41
43
|
});
|
|
44
|
+
test('selects millimeters as a metric display notation', () => {
|
|
45
|
+
useViewer.getState().setUnit('imperial');
|
|
46
|
+
useViewer.getState().setMetricNotation('millimeters');
|
|
47
|
+
expect(useViewer.getState()).toMatchObject({
|
|
48
|
+
unit: 'metric',
|
|
49
|
+
metricNotation: 'millimeters',
|
|
50
|
+
unitExplicit: true,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('external selection highlights', () => {
|
|
55
|
+
test('tracks host-owned highlights without changing the local selection', () => {
|
|
56
|
+
const localSelection = useViewer.getState().selection;
|
|
57
|
+
useViewer.getState().setExternalSelectedIds(['wall_remote']);
|
|
58
|
+
expect(useViewer.getState().externalSelectedIds).toEqual(['wall_remote']);
|
|
59
|
+
expect(useViewer.getState().selection).toBe(localSelection);
|
|
60
|
+
});
|
|
42
61
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAOjB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAgB9B,eAAO,MAAM,aAAa,YA4BzB,CAAA;AAgED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,WAAW,EACxB,UAAU,GAAE,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAM,GAC9C,KAAK,CAAC,cAAc,CA6DtB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getEffectiveNode, nodeRegistry, sceneRegistry, useLiveTransforms, useScene, } from '@pascal-app/core';
|
|
1
|
+
import { getEffectiveNode, nodeRegistry, resolveCeilingHeight, sceneRegistry, useLiveTransforms, useScene, } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
import { mergeSurfaceHolePolygons } from '../surface-hole-geometry';
|
|
@@ -30,7 +30,7 @@ export const CeilingSystem = () => {
|
|
|
30
30
|
// the final value on commit. Mirrors WallSystem / GeometrySystem.
|
|
31
31
|
const effective = getEffectiveNode(node);
|
|
32
32
|
const itemHoles = collectCeilingHoles(effective, nodes);
|
|
33
|
-
updateCeilingGeometry(effective, mesh, itemHoles);
|
|
33
|
+
updateCeilingGeometry(effective, mesh, itemHoles, nodes);
|
|
34
34
|
clearDirty(id);
|
|
35
35
|
}
|
|
36
36
|
// If mesh not found, keep it dirty for next frame
|
|
@@ -64,7 +64,7 @@ function collectCeilingHoles(ceiling, nodes) {
|
|
|
64
64
|
/**
|
|
65
65
|
* Updates the geometry for a single ceiling
|
|
66
66
|
*/
|
|
67
|
-
function updateCeilingGeometry(node, mesh, extraHoles = []) {
|
|
67
|
+
function updateCeilingGeometry(node, mesh, extraHoles = [], nodes = useScene.getState().nodes) {
|
|
68
68
|
const newGeo = generateCeilingGeometry(node, extraHoles);
|
|
69
69
|
mesh.geometry.dispose();
|
|
70
70
|
mesh.geometry = newGeo;
|
|
@@ -81,7 +81,11 @@ function updateCeilingGeometry(node, mesh, extraHoles = []) {
|
|
|
81
81
|
const liveTransform = useLiveTransforms.getState().get(node.id);
|
|
82
82
|
mesh.position.x = liveTransform?.position[0] ?? 0;
|
|
83
83
|
mesh.position.z = liveTransform?.position[2] ?? 0;
|
|
84
|
-
|
|
84
|
+
// Resolved height: explicit when stored, else the level-top bound — so a
|
|
85
|
+
// follows-mode ceiling re-parks under the current plane on every rebuild
|
|
86
|
+
// (level-height edits / covering-slab changes dirty-mark ceilings).
|
|
87
|
+
// Slight offset to avoid z-fighting with upper-level slabs.
|
|
88
|
+
mesh.position.y = resolveCeilingHeight(node, nodes) - 0.01 + (liveTransform?.position[1] ?? 0);
|
|
85
89
|
}
|
|
86
90
|
/**
|
|
87
91
|
* Generates flat ceiling geometry from polygon (no extrusion).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"floor-elevation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/floor-elevation/floor-elevation-system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"floor-elevation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/floor-elevation/floor-elevation-system.tsx"],"names":[],"mappings":"AAyCA;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,YAoEhC,CAAA"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { getEffectiveNode, getFloorStackedPosition, nodeRegistry, sceneRegistry, useLiveTransforms, useScene, } from '@pascal-app/core';
|
|
1
|
+
import { getEffectiveNode, getFloorStackedPosition, nodeRegistry, sceneRegistry, useLiveNodeOverrides, useLiveTransforms, useScene, } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
|
-
function withLiveTransform(node,
|
|
4
|
-
const liveTransform = useLiveTransforms.getState().get(id);
|
|
3
|
+
function withLiveTransform(node, liveTransform) {
|
|
5
4
|
if (!liveTransform)
|
|
6
5
|
return node;
|
|
7
6
|
const currentRotation = node.rotation;
|
|
@@ -38,10 +37,18 @@ export const FloorElevationSystem = () => {
|
|
|
38
37
|
const dirtyNodes = useScene((s) => s.dirtyNodes);
|
|
39
38
|
const clearDirty = useScene((s) => s.clearDirty);
|
|
40
39
|
useFrame(() => {
|
|
41
|
-
|
|
40
|
+
// Nodes with a live preview (override / transform) are reapplied EVERY
|
|
41
|
+
// frame, not only while dirty: the React commit that rebinds the group's
|
|
42
|
+
// base-Y position can land between frames, after the dirty mark was
|
|
43
|
+
// already consumed by the priority-2 systems — without this the lift
|
|
44
|
+
// vanishes until the next pointer tick re-dirties (visible Y blink
|
|
45
|
+
// during group drags over elevated slabs).
|
|
46
|
+
const overrides = useLiveNodeOverrides.getState().overrides;
|
|
47
|
+
const transforms = useLiveTransforms.getState().transforms;
|
|
48
|
+
if (dirtyNodes.size === 0 && overrides.size === 0 && transforms.size === 0)
|
|
42
49
|
return;
|
|
43
50
|
const nodes = useScene.getState().nodes;
|
|
44
|
-
|
|
51
|
+
const applyLift = (id) => {
|
|
45
52
|
const node = nodes[id];
|
|
46
53
|
if (!node)
|
|
47
54
|
return;
|
|
@@ -52,7 +59,8 @@ export const FloorElevationSystem = () => {
|
|
|
52
59
|
const mesh = sceneRegistry.nodes.get(id);
|
|
53
60
|
if (!mesh)
|
|
54
61
|
return;
|
|
55
|
-
const
|
|
62
|
+
const liveTransform = useLiveTransforms.getState().get(id);
|
|
63
|
+
const effectiveNode = withLiveTransform(getEffectiveNode(node), liveTransform);
|
|
56
64
|
const position = effectiveNode.position;
|
|
57
65
|
if (!position)
|
|
58
66
|
return;
|
|
@@ -67,11 +75,26 @@ export const FloorElevationSystem = () => {
|
|
|
67
75
|
node: effectiveNode,
|
|
68
76
|
nodes: resolverNodes,
|
|
69
77
|
position,
|
|
78
|
+
// 3D drags publish the pointer-decided surface cap with their live
|
|
79
|
+
// transform; honoring it here keeps this system's per-frame Y in
|
|
80
|
+
// agreement with the tool's preview (no deck/floor flicker).
|
|
81
|
+
maxElevation: liveTransform?.supportElevationCap,
|
|
70
82
|
});
|
|
71
83
|
mesh.position.y = visualPosition[1];
|
|
72
|
-
if (!(def.geometry || def.system)) {
|
|
84
|
+
if (!(def.geometry || def.system) && dirtyNodes.has(id)) {
|
|
73
85
|
clearDirty(id);
|
|
74
86
|
}
|
|
87
|
+
};
|
|
88
|
+
dirtyNodes.forEach((id) => {
|
|
89
|
+
applyLift(id);
|
|
90
|
+
});
|
|
91
|
+
overrides.forEach((_values, id) => {
|
|
92
|
+
if (!dirtyNodes.has(id))
|
|
93
|
+
applyLift(id);
|
|
94
|
+
});
|
|
95
|
+
transforms.forEach((_transform, id) => {
|
|
96
|
+
if (!dirtyNodes.has(id) && !overrides.has(id))
|
|
97
|
+
applyLift(id);
|
|
75
98
|
});
|
|
76
99
|
}, 1);
|
|
77
100
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"level-system.d.ts","sourceRoot":"","sources":["../../../src/systems/level/level-system.tsx"],"names":[],"mappings":"AAeA,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"level-system.d.ts","sourceRoot":"","sources":["../../../src/systems/level/level-system.tsx"],"names":[],"mappings":"AAeA,eAAO,MAAM,WAAW,YAuDvB,CAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getLevelElevations, sceneRegistry, useScene } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
3
|
import { lerp } from 'three/src/math/MathUtils.js';
|
|
4
4
|
import { applyShadowOnly, clearShadowOnly } from '../../lib/shadow-only';
|
|
@@ -14,23 +14,25 @@ export const LevelSystem = () => {
|
|
|
14
14
|
const nodes = useScene.getState().nodes;
|
|
15
15
|
const levelMode = useViewer.getState().levelMode;
|
|
16
16
|
const selectedLevel = useViewer.getState().selection.levelId;
|
|
17
|
+
const levelElevations = getLevelElevations(nodes);
|
|
17
18
|
const entries = [];
|
|
18
19
|
sceneRegistry.byType.level.forEach((levelId) => {
|
|
19
20
|
const obj = sceneRegistry.nodes.get(levelId);
|
|
20
21
|
const level = nodes[levelId];
|
|
21
22
|
if (obj && level) {
|
|
22
|
-
entries.push({
|
|
23
|
+
entries.push({
|
|
24
|
+
levelId,
|
|
25
|
+
index: level.level,
|
|
26
|
+
obj,
|
|
27
|
+
});
|
|
23
28
|
}
|
|
24
29
|
});
|
|
25
|
-
entries.sort((a, b) => a.index - b.index);
|
|
26
|
-
// Walk sorted levels, accumulating base Y offsets
|
|
27
30
|
const selectedIndex = selectedLevel
|
|
28
31
|
? entries.find((e) => e.levelId === selectedLevel)?.index
|
|
29
32
|
: undefined;
|
|
30
|
-
let cumulativeY = 0;
|
|
31
33
|
for (const { levelId, index, obj } of entries) {
|
|
32
34
|
const level = nodes[levelId];
|
|
33
|
-
const baseY =
|
|
35
|
+
const baseY = levelElevations.get(levelId)?.baseY ?? 0;
|
|
34
36
|
const explodedExtra = levelMode === 'exploded' ? index * EXPLODED_GAP : 0;
|
|
35
37
|
const targetY = baseY + explodedExtra;
|
|
36
38
|
obj.position.y = lerp(obj.position.y, targetY, delta * 12); // Smoothly animate to new Y position
|
|
@@ -51,7 +53,6 @@ export const LevelSystem = () => {
|
|
|
51
53
|
}
|
|
52
54
|
obj.visible = !hidden;
|
|
53
55
|
}
|
|
54
|
-
cumulativeY += getLevelHeight(levelId, nodes, (wallId) => sceneRegistry.nodes.get(wallId)?.position.y);
|
|
55
56
|
}
|
|
56
57
|
}, 5); // Using a lower priority so it runs after transforms from other systems have settled
|
|
57
58
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"level-utils.d.ts","sourceRoot":"","sources":["../../../src/systems/level/level-utils.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"level-utils.d.ts","sourceRoot":"","sources":["../../../src/systems/level/level-utils.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,IAAI,CAyCtD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getLevelElevations, sceneRegistry, useScene } from '@pascal-app/core';
|
|
2
2
|
/**
|
|
3
3
|
* Instantly snaps all level Objects3D to their true stacked Y positions
|
|
4
4
|
* (ignores levelMode — always uses stacked, no exploded gap).
|
|
@@ -18,18 +18,19 @@ export function snapLevelsToTruePositions() {
|
|
|
18
18
|
const obj = sceneRegistry.nodes.get(levelId);
|
|
19
19
|
const level = nodes[levelId];
|
|
20
20
|
if (obj && level) {
|
|
21
|
-
entries.push({
|
|
21
|
+
entries.push({
|
|
22
|
+
levelId,
|
|
23
|
+
obj,
|
|
24
|
+
});
|
|
22
25
|
}
|
|
23
26
|
});
|
|
24
|
-
|
|
27
|
+
const levelElevations = getLevelElevations(nodes);
|
|
25
28
|
// Snapshot current Y and visibility so we can restore them after the render
|
|
26
29
|
const snapshot = new Map(entries.map(({ levelId, obj }) => [levelId, { y: obj.position.y, visible: obj.visible }]));
|
|
27
30
|
// Snap to true stacked positions and make all levels visible
|
|
28
|
-
let cumulativeY = 0;
|
|
29
31
|
for (const { levelId, obj } of entries) {
|
|
30
|
-
obj.position.y =
|
|
32
|
+
obj.position.y = levelElevations.get(levelId)?.baseY ?? 0;
|
|
31
33
|
obj.visible = true;
|
|
32
|
-
cumulativeY += getLevelHeight(levelId, nodes, (wallId) => sceneRegistry.nodes.get(wallId)?.position.y);
|
|
33
34
|
}
|
|
34
35
|
return () => {
|
|
35
36
|
for (const { levelId, obj } of entries) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { type SlabNode, type SlabPolygonContext } from '@pascal-app/core';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
|
-
export declare const SlabSystem: () => null;
|
|
4
3
|
/**
|
|
5
|
-
* Generates
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Generates slab geometry from polygon. `context` carries the slab's level
|
|
5
|
+
* neighbourhood (walls + sibling slabs) driving the per-edge render offsets —
|
|
6
|
+
* see `getRenderableSlabPolygon`. Branches on the explicit `recessed` intent:
|
|
7
|
+
* a recessed slab is an open shell (pool), everything else a solid occupying
|
|
8
|
+
* `[elevation − thickness, elevation]`.
|
|
8
9
|
*/
|
|
9
10
|
export declare function generateSlabGeometry(slabNode: SlabNode, context: SlabPolygonContext): THREE.BufferGeometry;
|
|
10
11
|
//# sourceMappingURL=slab-system.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slab-system.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-system.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"slab-system.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACxB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAQ9B;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,kBAAkB,GAC1B,KAAK,CAAC,cAAc,CAItB"}
|