@pascal-app/viewer 1.0.0-beta.4 → 1.0.0-beta.5
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 +52 -0
- package/dist/components/viewer/frame-limiter.d.ts +11 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +58 -18
- package/dist/components/viewer/index.d.ts +15 -0
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +34 -50
- package/dist/components/viewer/perf-monitor.js +2 -2
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +7 -2
- package/dist/components/viewer/unsupported-gpu-fallback.d.ts +2 -0
- package/dist/components/viewer/unsupported-gpu-fallback.d.ts.map +1 -0
- package/dist/components/viewer/unsupported-gpu-fallback.js +4 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/lib/dispose-object3d.d.ts +4 -0
- package/dist/lib/dispose-object3d.d.ts.map +1 -0
- package/dist/lib/dispose-object3d.js +27 -0
- package/dist/lib/geometry-groups.d.ts +17 -0
- package/dist/lib/geometry-groups.d.ts.map +1 -0
- package/dist/lib/geometry-groups.js +53 -0
- package/dist/lib/isolation.d.ts +8 -8
- package/dist/lib/isolation.d.ts.map +1 -1
- package/dist/lib/isolation.js +18 -30
- package/dist/lib/layers.d.ts +16 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +18 -0
- package/dist/lib/renderer-capability.d.ts +56 -0
- package/dist/lib/renderer-capability.d.ts.map +1 -0
- package/dist/lib/renderer-capability.js +114 -0
- package/dist/lib/scene-visibility.d.ts +14 -0
- package/dist/lib/scene-visibility.d.ts.map +1 -0
- package/dist/lib/scene-visibility.js +48 -0
- package/dist/lib/shadow-only.d.ts +16 -0
- package/dist/lib/shadow-only.d.ts.map +1 -1
- package/dist/lib/shadow-only.js +8 -17
- package/dist/lib/snapshot-pipeline.d.ts +9 -0
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +33 -9
- package/dist/store/use-viewer.d.ts +5 -4
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +9 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +2 -26
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +7 -1
- package/dist/systems/wall/level-miter-cache.d.ts +10 -0
- package/dist/systems/wall/level-miter-cache.d.ts.map +1 -0
- package/dist/systems/wall/level-miter-cache.js +40 -0
- package/dist/systems/wall/wall-cutout.d.ts +12 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +41 -11
- package/dist/systems/wall/wall-system.d.ts +4 -0
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +31 -18
- package/package.json +4 -2
- package/dist/components/viewer/scene-bvh.test.d.ts +0 -2
- package/dist/components/viewer/scene-bvh.test.d.ts.map +0 -1
- package/dist/components/viewer/scene-bvh.test.js +0 -13
- package/dist/lib/csg-utils.test.d.ts +0 -2
- package/dist/lib/csg-utils.test.d.ts.map +0 -1
- package/dist/lib/csg-utils.test.js +0 -45
- package/dist/lib/materials.test.d.ts +0 -2
- package/dist/lib/materials.test.d.ts.map +0 -1
- package/dist/lib/materials.test.js +0 -25
- package/dist/lib/merged-outline-node.test.d.ts +0 -2
- package/dist/lib/merged-outline-node.test.d.ts.map +0 -1
- package/dist/lib/merged-outline-node.test.js +0 -22
- package/dist/lib/polygon-union.test.d.ts +0 -2
- package/dist/lib/polygon-union.test.d.ts.map +0 -1
- package/dist/lib/polygon-union.test.js +0 -105
- package/dist/lib/texture-reference.test.d.ts +0 -2
- package/dist/lib/texture-reference.test.d.ts.map +0 -1
- package/dist/lib/texture-reference.test.js +0 -86
- package/dist/store/use-viewer.test.d.ts +0 -2
- package/dist/store/use-viewer.test.d.ts.map +0 -1
- package/dist/store/use-viewer.test.js +0 -61
- package/dist/systems/door/door-animation.test.d.ts +0 -2
- package/dist/systems/door/door-animation.test.d.ts.map +0 -1
- package/dist/systems/door/door-animation.test.js +0 -51
- package/dist/systems/door/door-floor-alignment.test.d.ts +0 -2
- package/dist/systems/door/door-floor-alignment.test.d.ts.map +0 -1
- package/dist/systems/door/door-floor-alignment.test.js +0 -50
- package/dist/systems/geometry/geometry-system.test.d.ts +0 -2
- package/dist/systems/geometry/geometry-system.test.d.ts.map +0 -1
- package/dist/systems/geometry/geometry-system.test.js +0 -30
- package/dist/systems/slab/slab-system.test.d.ts +0 -2
- package/dist/systems/slab/slab-system.test.d.ts.map +0 -1
- package/dist/systems/slab/slab-system.test.js +0 -102
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts +0 -2
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +0 -1
- package/dist/systems/wall/opening-cutout-geometry.test.js +0 -172
- package/dist/systems/wall/wall-opening-cutout.test.d.ts +0 -2
- package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +0 -1
- package/dist/systems/wall/wall-opening-cutout.test.js +0 -59
- package/dist/systems/wall/wall-support-extension.test.d.ts +0 -2
- package/dist/systems/wall/wall-support-extension.test.d.ts.map +0 -1
- package/dist/systems/wall/wall-support-extension.test.js +0 -116
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts +0 -2
- package/dist/systems/wall/wall-terrain-bottom.test.d.ts.map +0 -1
- package/dist/systems/wall/wall-terrain-bottom.test.js +0 -34
|
@@ -4,6 +4,7 @@ import type { EdgeMode } from '../lib/edge-style';
|
|
|
4
4
|
import type { ColorPreset, RenderShading } from '../lib/materials';
|
|
5
5
|
export type RenderContext = 'editor' | 'viewer';
|
|
6
6
|
export type MetricNotation = 'meters' | 'millimeters';
|
|
7
|
+
export type WallMode = 'up' | 'cutaway' | 'down' | 'translucent';
|
|
7
8
|
type SelectionPath = {
|
|
8
9
|
buildingId: BuildingNode['id'] | null;
|
|
9
10
|
levelId: LevelNode['id'] | null;
|
|
@@ -70,8 +71,8 @@ type ViewerState = {
|
|
|
70
71
|
unitExplicit: boolean;
|
|
71
72
|
levelMode: 'stacked' | 'exploded' | 'solo' | 'manual';
|
|
72
73
|
setLevelMode: (mode: 'stacked' | 'exploded' | 'solo' | 'manual') => void;
|
|
73
|
-
wallMode:
|
|
74
|
-
setWallMode: (mode:
|
|
74
|
+
wallMode: WallMode;
|
|
75
|
+
setWallMode: (mode: WallMode) => void;
|
|
75
76
|
showScans: boolean;
|
|
76
77
|
setShowScans: (show: boolean) => void;
|
|
77
78
|
showGuides: boolean;
|
|
@@ -133,7 +134,7 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
|
|
|
133
134
|
setOptions: (options: Partial<import("zustand/middleware").PersistOptions<ViewerState, {
|
|
134
135
|
metricNotation: MetricNotation;
|
|
135
136
|
levelMode: "manual" | "stacked" | "exploded" | "solo";
|
|
136
|
-
wallMode:
|
|
137
|
+
wallMode: WallMode;
|
|
137
138
|
projectPreferences: Record<string, {
|
|
138
139
|
showScans?: boolean;
|
|
139
140
|
showGuides?: boolean;
|
|
@@ -157,7 +158,7 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
|
|
|
157
158
|
getOptions: () => Partial<import("zustand/middleware").PersistOptions<ViewerState, {
|
|
158
159
|
metricNotation: MetricNotation;
|
|
159
160
|
levelMode: "manual" | "stacked" | "exploded" | "solo";
|
|
160
|
-
wallMode:
|
|
161
|
+
wallMode: WallMode;
|
|
161
162
|
projectPreferences: Record<string, {
|
|
162
163
|
showScans?: boolean;
|
|
163
164
|
showGuides?: boolean;
|
|
@@ -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;AAC/C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,YA6EhC,CAAA"}
|
|
@@ -64,6 +64,15 @@ export const FloorElevationSystem = () => {
|
|
|
64
64
|
const position = effectiveNode.position;
|
|
65
65
|
if (!position)
|
|
66
66
|
return;
|
|
67
|
+
// `applies === false` means the kind opts OUT of floor stacking for this
|
|
68
|
+
// node: its Y belongs to a host frame (a wall/ceiling-mounted item, a
|
|
69
|
+
// cabinet module inside a run, a wall duct terminal). `getFloorPlacedElevation`
|
|
70
|
+
// already returns 0 for them, so the write below would degenerate to
|
|
71
|
+
// copying `position[1]` into the mesh — and tools publish live transforms
|
|
72
|
+
// in WORLD space, so during a drag that lifts the ghost off its host by
|
|
73
|
+
// the host frame's own elevation.
|
|
74
|
+
if (floorPlaced.applies && !floorPlaced.applies(effectiveNode))
|
|
75
|
+
return;
|
|
67
76
|
// This system is the single drag-time authority for floor-stack mesh Y:
|
|
68
77
|
// tools publish base positions to live stores, renderers may
|
|
69
78
|
// reconcile that base Y onto the group, then this presentation system
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"geometry-system.d.ts","sourceRoot":"","sources":["../../../src/systems/geometry/geometry-system.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAa,KAAK,KAAK,EAA4B,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"geometry-system.d.ts","sourceRoot":"","sources":["../../../src/systems/geometry/geometry-system.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAa,KAAK,KAAK,EAA4B,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAA;AAStF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,cAAc,YA8L1B,CAAA;AA8ID,eAAe,cAAc,CAAA;AAE7B,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,KAAK,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAU7D;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,EAC3C,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,OAAO,CAKT"}
|
|
@@ -3,6 +3,7 @@ import { findLevelAncestorId, getEffectiveNode, levelBaseElevationAt, nodeRegist
|
|
|
3
3
|
import { useFrame } from '@react-three/fiber';
|
|
4
4
|
import { useEffect, useRef } from 'react';
|
|
5
5
|
import { FrontSide } from 'three';
|
|
6
|
+
import { disposeObject3DResources } from '../../lib/dispose-object3d';
|
|
6
7
|
import { createSurfaceRoleMaterial, } from '../../lib/materials';
|
|
7
8
|
import useViewer from '../../store/use-viewer';
|
|
8
9
|
/**
|
|
@@ -289,28 +290,7 @@ function disposeChildren(group) {
|
|
|
289
290
|
if (!fromGeometry)
|
|
290
291
|
continue;
|
|
291
292
|
group.remove(child);
|
|
292
|
-
|
|
293
|
-
if (mesh.geometry?.dispose)
|
|
294
|
-
mesh.geometry.dispose();
|
|
295
|
-
if ('material' in mesh) {
|
|
296
|
-
const m = mesh.material;
|
|
297
|
-
if (Array.isArray(m)) {
|
|
298
|
-
for (const mat of m) {
|
|
299
|
-
if (isCachedMaterial(mat))
|
|
300
|
-
continue;
|
|
301
|
-
if (mat && typeof mat.dispose === 'function') {
|
|
302
|
-
;
|
|
303
|
-
mat.dispose();
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
else if (isCachedMaterial(m)) {
|
|
308
|
-
}
|
|
309
|
-
else if (m && typeof m.dispose === 'function') {
|
|
310
|
-
;
|
|
311
|
-
m.dispose();
|
|
312
|
-
}
|
|
313
|
-
}
|
|
293
|
+
disposeObject3DResources(child);
|
|
314
294
|
}
|
|
315
295
|
}
|
|
316
296
|
function applyDefaultSurfaceRole(root, defaultRole, colorPreset, sceneTheme) {
|
|
@@ -339,10 +319,6 @@ function getMaterialSide(material) {
|
|
|
339
319
|
const source = Array.isArray(material) ? material[0] : material;
|
|
340
320
|
return source?.side ?? FrontSide;
|
|
341
321
|
}
|
|
342
|
-
function isCachedMaterial(value) {
|
|
343
|
-
return Boolean(value?.userData
|
|
344
|
-
?.__pascalCachedMaterial);
|
|
345
|
-
}
|
|
346
322
|
export default GeometrySystem;
|
|
347
323
|
export function markGeometryBuildOutput(child) {
|
|
348
324
|
// Tag the builder subtree so paint previews can reach nested meshes (for
|
|
@@ -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,YA6DvB,CAAA"}
|
|
@@ -35,7 +35,13 @@ export const LevelSystem = () => {
|
|
|
35
35
|
const baseY = levelElevations.get(levelId)?.baseY ?? 0;
|
|
36
36
|
const explodedExtra = levelMode === 'exploded' ? index * EXPLODED_GAP : 0;
|
|
37
37
|
const targetY = baseY + explodedExtra;
|
|
38
|
-
|
|
38
|
+
// Clamped smoothing. The naive `lerp(y, target, delta*12)` multiplies
|
|
39
|
+
// the error by |1 - 12*delta| per frame — DIVERGENT once a frame
|
|
40
|
+
// exceeds ~166 ms (slow machines, headless GL, heavy scenes): levels
|
|
41
|
+
// oscillated kilometers off-screen and the level-fit camera followed
|
|
42
|
+
// (blank viewport). Clamping keeps every step a contraction: identical
|
|
43
|
+
// feel at 60 fps, exact snap instead of overshoot on slow frames.
|
|
44
|
+
obj.position.y = lerp(obj.position.y, targetY, Math.min(1, delta * 12));
|
|
39
45
|
// Solo: hidden levels ABOVE the soloed one stay in the shadow map
|
|
40
46
|
// (shadow-caster-only) so the sun still shadows the soloed floor through
|
|
41
47
|
// them; levels below can't block the sun, so they plain-hide.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type WallMiterData, type WallNode } from '@pascal-app/core';
|
|
2
|
+
export declare function sameMiterInputs(a: WallNode[], b: WallNode[]): boolean;
|
|
3
|
+
export declare function getCachedLevelMiters(levelId: string, levelWalls: WallNode[]): WallMiterData;
|
|
4
|
+
/**
|
|
5
|
+
* The cache is module-level, so it outlives any single mount. Editor teardown
|
|
6
|
+
* resets the other shared singletons; without the same reset a remount in the
|
|
7
|
+
* same tab keeps every previous level's walls reachable.
|
|
8
|
+
*/
|
|
9
|
+
export declare function clearLevelMiterCache(): void;
|
|
10
|
+
//# sourceMappingURL=level-miter-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"level-miter-cache.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/level-miter-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAa1F,wBAAgB,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,QAAQ,EAAE,GAAG,OAAO,CAoBrE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,aAAa,CAO3F;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { calculateLevelMiters } from '@pascal-app/core';
|
|
2
|
+
const levelMiterCache = new Map();
|
|
3
|
+
export function sameMiterInputs(a, b) {
|
|
4
|
+
if (a.length !== b.length)
|
|
5
|
+
return false;
|
|
6
|
+
for (let i = 0; i < a.length; i++) {
|
|
7
|
+
const x = a[i];
|
|
8
|
+
const y = b[i];
|
|
9
|
+
if (x === y)
|
|
10
|
+
continue;
|
|
11
|
+
if (!x || !y)
|
|
12
|
+
return false;
|
|
13
|
+
if (x.id !== y.id ||
|
|
14
|
+
x.start[0] !== y.start[0] ||
|
|
15
|
+
x.start[1] !== y.start[1] ||
|
|
16
|
+
x.end[0] !== y.end[0] ||
|
|
17
|
+
x.end[1] !== y.end[1] ||
|
|
18
|
+
x.thickness !== y.thickness ||
|
|
19
|
+
x.curveOffset !== y.curveOffset) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
export function getCachedLevelMiters(levelId, levelWalls) {
|
|
26
|
+
const cached = levelMiterCache.get(levelId);
|
|
27
|
+
if (cached && sameMiterInputs(cached.walls, levelWalls))
|
|
28
|
+
return cached.data;
|
|
29
|
+
const data = calculateLevelMiters(levelWalls);
|
|
30
|
+
levelMiterCache.set(levelId, { walls: levelWalls, data });
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The cache is module-level, so it outlives any single mount. Editor teardown
|
|
35
|
+
* resets the other shared singletons; without the same reset a remount in the
|
|
36
|
+
* same tab keeps every previous level's walls reachable.
|
|
37
|
+
*/
|
|
38
|
+
export function clearLevelMiterCache() {
|
|
39
|
+
levelMiterCache.clear();
|
|
40
|
+
}
|
|
@@ -1,2 +1,14 @@
|
|
|
1
|
+
import { type WallNode } from '@pascal-app/core';
|
|
2
|
+
import { type Mesh, Vector3 } from 'three/webgpu';
|
|
3
|
+
import { type WallMode } from '../../store/use-viewer';
|
|
4
|
+
/**
|
|
5
|
+
* Whether a wall should be hidden or see-through for the current camera and
|
|
6
|
+
* wall mode. Pure: reads only its arguments and the mesh's world direction.
|
|
7
|
+
*
|
|
8
|
+
* Exported so hosts rendering their own layers inside `<Viewer>` can match
|
|
9
|
+
* these semantics instead of re-deriving the facing test or inferring state
|
|
10
|
+
* from the assigned material variant.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getWallHideState(wallNode: WallNode, wallMesh: Mesh, wallMode: WallMode, cameraDir: Vector3): boolean;
|
|
1
13
|
export declare const WallCutout: () => null;
|
|
2
14
|
//# sourceMappingURL=wall-cutout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-cutout.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"wall-cutout.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-cutout.tsx"],"names":[],"mappings":"AAAA,OAAO,EAUL,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EAAE,KAAK,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACjD,OAAkB,EAAE,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAWjE;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,IAAI,EACd,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,OAAO,GACjB,OAAO,CAmBT;AAMD,eAAO,MAAM,UAAU,YA+MtB,CAAA"}
|
|
@@ -2,12 +2,20 @@ import { emitter, getWallFaceBandConfig, getWallPlaneTop, resolveLevelId, resolv
|
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
3
|
import { useEffect, useRef } from 'react';
|
|
4
4
|
import { Vector3 } from 'three/webgpu';
|
|
5
|
-
import useViewer from '../../store/use-viewer';
|
|
5
|
+
import useViewer, {} from '../../store/use-viewer';
|
|
6
6
|
import { getMaterialsForWall, getSelectionHighlightMaterials, getWallMaterialHash, } from './wall-materials';
|
|
7
7
|
const tmpVec = new Vector3();
|
|
8
8
|
const u = new Vector3();
|
|
9
9
|
const v = new Vector3();
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Whether a wall should be hidden or see-through for the current camera and
|
|
12
|
+
* wall mode. Pure: reads only its arguments and the mesh's world direction.
|
|
13
|
+
*
|
|
14
|
+
* Exported so hosts rendering their own layers inside `<Viewer>` can match
|
|
15
|
+
* these semantics instead of re-deriving the facing test or inferring state
|
|
16
|
+
* from the assigned material variant.
|
|
17
|
+
*/
|
|
18
|
+
export function getWallHideState(wallNode, wallMesh, wallMode, cameraDir) {
|
|
11
19
|
let hideWall = wallNode.frontSide === 'interior' && wallNode.backSide === 'interior';
|
|
12
20
|
if (wallMode === 'up') {
|
|
13
21
|
hideWall = false;
|
|
@@ -40,6 +48,13 @@ export const WallCutout = () => {
|
|
|
40
48
|
const lastNumberOfWalls = useRef(0);
|
|
41
49
|
const lastHighlightKey = useRef('');
|
|
42
50
|
const lastWallAppearanceKey = useRef('');
|
|
51
|
+
const wallAppearanceKeyRef = useRef('');
|
|
52
|
+
const wallAppearanceInputs = useRef({
|
|
53
|
+
nodes: null,
|
|
54
|
+
materials: null,
|
|
55
|
+
shading: null,
|
|
56
|
+
wallCount: -1,
|
|
57
|
+
});
|
|
43
58
|
const lastTextures = useRef(useViewer.getState().textures);
|
|
44
59
|
const lastColorPreset = useRef(useViewer.getState().colorPreset);
|
|
45
60
|
const lastSceneTheme = useRef(useViewer.getState().sceneTheme);
|
|
@@ -65,15 +80,30 @@ export const WallCutout = () => {
|
|
|
65
80
|
? hoveredId
|
|
66
81
|
: null;
|
|
67
82
|
const highlightKey = `${Array.from(highlightedWallIds).sort().join('|')}::${deleteHoveredWallId ?? ''}`;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
.
|
|
83
|
+
// Sorting every wall id, hashing each wall's material and JSON-dumping its
|
|
84
|
+
// face bands is a full-scene scan; its inputs are immutable store slices,
|
|
85
|
+
// so identity is enough to know the key cannot have changed.
|
|
86
|
+
const wallCount = sceneRegistry.byType.wall.size;
|
|
87
|
+
const appearanceInputs = wallAppearanceInputs.current;
|
|
88
|
+
if (appearanceInputs.nodes !== sceneState.nodes ||
|
|
89
|
+
appearanceInputs.materials !== sceneState.materials ||
|
|
90
|
+
appearanceInputs.shading !== shading ||
|
|
91
|
+
appearanceInputs.wallCount !== wallCount) {
|
|
92
|
+
appearanceInputs.nodes = sceneState.nodes;
|
|
93
|
+
appearanceInputs.materials = sceneState.materials;
|
|
94
|
+
appearanceInputs.shading = shading;
|
|
95
|
+
appearanceInputs.wallCount = wallCount;
|
|
96
|
+
wallAppearanceKeyRef.current = Array.from(sceneRegistry.byType.wall)
|
|
97
|
+
.sort()
|
|
98
|
+
.map((wallId) => {
|
|
99
|
+
const wallNode = sceneState.nodes[wallId];
|
|
100
|
+
if (wallNode?.type !== 'wall')
|
|
101
|
+
return `${wallId}:missing`;
|
|
102
|
+
return `${wallId}:${getWallMaterialHash(wallNode, shading, sceneState.materials)}:${JSON.stringify(wallNode.faceBands ?? null)}`;
|
|
103
|
+
})
|
|
104
|
+
.join('|');
|
|
105
|
+
}
|
|
106
|
+
const wallAppearanceKey = wallAppearanceKeyRef.current;
|
|
77
107
|
const distanceMoved = currentCameraPosition.distanceTo(lastCameraPosition.current);
|
|
78
108
|
const directionChanged = tmpVec.distanceTo(lastCameraTarget.current);
|
|
79
109
|
const timeSinceUpdate = currentTime - lastUpdateTime.current;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { type AnyNode, type WallMiterData, type WallNode, type WallSlabSupportSegment } from '@pascal-app/core';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
|
+
/** Moves every rebuild notice collected so far into `into`. */
|
|
4
|
+
export declare function drainRebuiltWalls(into: Set<string>): void;
|
|
5
|
+
/** Rebuilds this system still owes — neighbours deferred during a drag. */
|
|
6
|
+
export declare function getPendingWallRebuildCount(): number;
|
|
3
7
|
export declare const WallSystem: () => null;
|
|
4
8
|
type WallTerrainBottomSampler = (x: number, z: number) => number | null;
|
|
5
9
|
export declare function generateExtrudedWall(wallNode: WallNode, childrenNodes: AnyNode[], miterData: WallMiterData, slabElevation?: number, baseElevation?: number, baseSegments?: readonly WallSlabSupportSegment[], storeyHeight?: number, terrainBottomAt?: WallTerrainBottomSampler): THREE.BufferGeometry;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"wall-system.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EA0BZ,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,sBAAsB,EAI5B,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAyc9B,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAGzD;AAED,2EAA2E;AAC3E,wBAAgB,0BAA0B,IAAI,MAAM,CAMnD;AAED,eAAO,MAAM,UAAU,YAiKtB,CAAA;AAgND,KAAK,wBAAwB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAA;AAgEvE,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,OAAO,EAAE,EACxB,SAAS,EAAE,aAAa,EACxB,aAAa,SAAI,EACjB,aAAa,SAAgB,EAC7B,YAAY,GAAE,SAAS,sBAAsB,EAE5C,EACD,YAAY,SAAuB,EACnC,eAAe,CAAC,EAAE,wBAAwB,GACzC,KAAK,CAAC,cAAc,CA0OtB"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DEFAULT_LEVEL_HEIGHT, getAdjacentWallIds, getEffectiveNode, getWallBandSlotId, getWallCurveFrameAt, getWallFaceBandConfig, getWallFaceBandForHeight, getWallMiterBoundaryPoints, getWallPlaneTop, getWallPlanFootprint, getWallSurfacePolygon, getWallThickness, isCurvedWall, pointToKey, resolveLevelId, resolveWallTop, sceneRegistry, spatialGridManager, terrainSupportLift, useLiveNodeOverrides, useLiveTransforms, useScene, } from '@pascal-app/core';
|
|
2
2
|
import { useFrame } from '@react-three/fiber';
|
|
3
|
+
import { useEffect } from 'react';
|
|
3
4
|
import * as THREE from 'three';
|
|
4
5
|
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
5
6
|
import { Brush, Evaluator, SUBTRACTION } from 'three-bvh-csg';
|
|
6
7
|
import { computeBoundsTree } from 'three-mesh-bvh';
|
|
7
8
|
import { ensureRenderableGeometryAttributes, prepareBrushForCSG } from '../../lib/csg-utils';
|
|
9
|
+
import { setGroupsSortedByMaterial } from '../../lib/geometry-groups';
|
|
8
10
|
import { buildTerrainPerimeterFillGeometry } from '../../lib/terrain-perimeter-fill';
|
|
11
|
+
import { clearLevelMiterCache, getCachedLevelMiters } from './level-miter-cache';
|
|
9
12
|
import { buildOpeningCutoutGeometry, getOpeningCutoutBottomPadding, } from './opening-cutout-geometry';
|
|
10
13
|
// Reusable CSG evaluator for better performance
|
|
11
14
|
const csgEvaluator = new Evaluator();
|
|
@@ -210,18 +213,7 @@ function assignWallMaterialGroups(geometry, wall, boundaryEdges, effectiveWallHe
|
|
|
210
213
|
}
|
|
211
214
|
triangleMaterials[triangleIndex] = getWallFaceMaterialIndex(wall, nearestTag, centroid.y, effectiveWallHeight);
|
|
212
215
|
}
|
|
213
|
-
geometry
|
|
214
|
-
let currentMaterial = triangleMaterials[0] ?? 0;
|
|
215
|
-
let groupStart = 0;
|
|
216
|
-
for (let triangleIndex = 1; triangleIndex < triangleCount; triangleIndex += 1) {
|
|
217
|
-
const materialIndex = triangleMaterials[triangleIndex] ?? 0;
|
|
218
|
-
if (materialIndex === currentMaterial)
|
|
219
|
-
continue;
|
|
220
|
-
geometry.addGroup(groupStart * 3, (triangleIndex - groupStart) * 3, currentMaterial);
|
|
221
|
-
groupStart = triangleIndex;
|
|
222
|
-
currentMaterial = materialIndex;
|
|
223
|
-
}
|
|
224
|
-
geometry.addGroup(groupStart * 3, (triangleCount - groupStart) * 3, currentMaterial);
|
|
216
|
+
setGroupsSortedByMaterial(geometry, triangleMaterials);
|
|
225
217
|
}
|
|
226
218
|
function interpolateSplitVertex(a, b, t) {
|
|
227
219
|
return {
|
|
@@ -345,7 +337,21 @@ const WALL_PROGRESSIVE_DIRTY_THRESHOLD = MAX_WALL_REBUILDS_PER_FRAME;
|
|
|
345
337
|
const WALL_PROGRESSIVE_TIME_BUDGET_MS = 8;
|
|
346
338
|
let lastWallDirtyAtMs = 0;
|
|
347
339
|
const pendingAdjacentByLevel = new Map();
|
|
348
|
-
|
|
340
|
+
// Walls whose geometry this system replaced since the last drain.
|
|
341
|
+
//
|
|
342
|
+
// The store's dirty mark is cleared the moment a wall is rebuilt, so anything
|
|
343
|
+
// running later in the same frame would never see it. This is that same
|
|
344
|
+
// signal, held until a consumer picks it up. Neighbours rebuilt by the
|
|
345
|
+
// trailing-edge flush land here too — those never carry a dirty mark at all.
|
|
346
|
+
const rebuiltWalls = new Set();
|
|
347
|
+
/** Moves every rebuild notice collected so far into `into`. */
|
|
348
|
+
export function drainRebuiltWalls(into) {
|
|
349
|
+
for (const wallId of rebuiltWalls)
|
|
350
|
+
into.add(wallId);
|
|
351
|
+
rebuiltWalls.clear();
|
|
352
|
+
}
|
|
353
|
+
/** Rebuilds this system still owes — neighbours deferred during a drag. */
|
|
354
|
+
export function getPendingWallRebuildCount() {
|
|
349
355
|
let count = 0;
|
|
350
356
|
for (const ids of pendingAdjacentByLevel.values()) {
|
|
351
357
|
count += ids.size;
|
|
@@ -362,6 +368,10 @@ export const WallSystem = () => {
|
|
|
362
368
|
// drags could land an override and a markDirty in the same React
|
|
363
369
|
// tick and the next `useFrame` would still see the stale closure.
|
|
364
370
|
useLiveNodeOverrides((s) => s.overrides);
|
|
371
|
+
// The miter cache is module-level, so it outlives this mount. Editor
|
|
372
|
+
// teardown resets the other shared singletons; without the same reset here a
|
|
373
|
+
// remount in the same tab keeps every previous level's walls reachable.
|
|
374
|
+
useEffect(() => () => clearLevelMiterCache(), []);
|
|
365
375
|
useFrame(() => {
|
|
366
376
|
const hasDirty = dirtyNodes.size > 0;
|
|
367
377
|
const hasPending = pendingAdjacentByLevel.size > 0;
|
|
@@ -401,7 +411,7 @@ export const WallSystem = () => {
|
|
|
401
411
|
break;
|
|
402
412
|
}
|
|
403
413
|
const levelWalls = getLevelWalls(levelId);
|
|
404
|
-
const miterData =
|
|
414
|
+
const miterData = getCachedLevelMiters(levelId, levelWalls);
|
|
405
415
|
const rebuiltWallIds = new Set();
|
|
406
416
|
// Update dirty walls — always, no throttling. The dragged wall must
|
|
407
417
|
// follow the cursor with full fidelity (cutouts and all). Large imports
|
|
@@ -420,6 +430,7 @@ export const WallSystem = () => {
|
|
|
420
430
|
if (mesh) {
|
|
421
431
|
updateWallGeometry(wallId, miterData);
|
|
422
432
|
clearDirty(wallId);
|
|
433
|
+
rebuiltWalls.add(wallId);
|
|
423
434
|
rebuiltWallIds.add(wallId);
|
|
424
435
|
rebuiltWallsThisFrame += 1;
|
|
425
436
|
}
|
|
@@ -447,7 +458,7 @@ export const WallSystem = () => {
|
|
|
447
458
|
// their correct miter joins.
|
|
448
459
|
const quiet = !hasDirtyWalls && now - lastWallDirtyAtMs >= DRAG_FLUSH_MS;
|
|
449
460
|
if (quiet && pendingAdjacentByLevel.size > 0) {
|
|
450
|
-
const pendingCount =
|
|
461
|
+
const pendingCount = getPendingWallRebuildCount();
|
|
451
462
|
const useProgressiveAdjacentRebuilds = pendingCount > WALL_PROGRESSIVE_DIRTY_THRESHOLD;
|
|
452
463
|
let rebuiltAdjacentThisFrame = 0;
|
|
453
464
|
const adjacentFrameStartedAt = performance.now();
|
|
@@ -455,7 +466,7 @@ export const WallSystem = () => {
|
|
|
455
466
|
if (pendingIds.size === 0)
|
|
456
467
|
continue;
|
|
457
468
|
const levelWalls = getLevelWalls(levelId);
|
|
458
|
-
const miterData =
|
|
469
|
+
const miterData = getCachedLevelMiters(levelId, levelWalls);
|
|
459
470
|
for (const wallId of Array.from(pendingIds)) {
|
|
460
471
|
if (useProgressiveAdjacentRebuilds) {
|
|
461
472
|
if (rebuiltAdjacentThisFrame >= MAX_WALL_REBUILDS_PER_FRAME) {
|
|
@@ -467,8 +478,10 @@ export const WallSystem = () => {
|
|
|
467
478
|
}
|
|
468
479
|
}
|
|
469
480
|
const mesh = sceneRegistry.nodes.get(wallId);
|
|
470
|
-
if (mesh)
|
|
481
|
+
if (mesh) {
|
|
471
482
|
updateWallGeometry(wallId, miterData);
|
|
483
|
+
rebuiltWalls.add(wallId);
|
|
484
|
+
}
|
|
472
485
|
pendingIds.delete(wallId);
|
|
473
486
|
rebuiltAdjacentThisFrame += 1;
|
|
474
487
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/viewer",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.5",
|
|
4
4
|
"description": "3D viewer component for Pascal building editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc --build",
|
|
21
21
|
"dev": "tsgo --build --watch",
|
|
22
|
+
"test": "bun test src",
|
|
22
23
|
"prepublishOnly": "npm run build"
|
|
23
24
|
},
|
|
24
25
|
"peerDependencies": {
|
|
25
|
-
"@pascal-app/core": "^1.0.0-beta.
|
|
26
|
+
"@pascal-app/core": "^1.0.0-beta.5",
|
|
26
27
|
"@react-three/drei": "^10",
|
|
27
28
|
"@react-three/fiber": "^9",
|
|
28
29
|
"react": "^18 || ^19",
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
"zustand": "^5"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
38
|
+
"@pascal-app/core": "^1.0.0-beta.5",
|
|
37
39
|
"@pascal/typescript-config": "*",
|
|
38
40
|
"@types/node": "^22",
|
|
39
41
|
"@types/react": "^19.2.2",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scene-bvh.test.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/scene-bvh.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// @ts-expect-error — bun:test is provided by the Bun runtime; viewer does not
|
|
2
|
-
// include Bun ambient types in its production declaration build.
|
|
3
|
-
import { describe, expect, test } from 'bun:test';
|
|
4
|
-
import { Mesh } from 'three';
|
|
5
|
-
import { isSceneBvhExcluded } from './scene-bvh';
|
|
6
|
-
describe('scene BVH exclusions', () => {
|
|
7
|
-
test('keeps annotation meshes on their normal raycast path', () => {
|
|
8
|
-
expect(isSceneBvhExcluded(new Mesh())).toBe(false);
|
|
9
|
-
const annotation = new Mesh();
|
|
10
|
-
annotation.userData.excludeFromBvh = true;
|
|
11
|
-
expect(isSceneBvhExcluded(annotation)).toBe(true);
|
|
12
|
-
});
|
|
13
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"csg-utils.test.d.ts","sourceRoot":"","sources":["../../src/lib/csg-utils.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,45 +0,0 @@
|
|
|
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 { ensureRenderableGeometryAttributes } from './csg-utils';
|
|
6
|
-
describe('ensureRenderableGeometryAttributes', () => {
|
|
7
|
-
test('fills missing render attributes to match position count', () => {
|
|
8
|
-
const geometry = new THREE.BufferGeometry();
|
|
9
|
-
geometry.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), 3));
|
|
10
|
-
ensureRenderableGeometryAttributes(geometry);
|
|
11
|
-
expect(geometry.getAttribute('position')?.count).toBe(3);
|
|
12
|
-
expect(geometry.getAttribute('normal')?.count).toBe(3);
|
|
13
|
-
expect(geometry.getAttribute('uv')?.count).toBe(3);
|
|
14
|
-
expect(geometry.getAttribute('uv2')?.count).toBe(3);
|
|
15
|
-
});
|
|
16
|
-
test('replaces render attributes with the wrong item size', () => {
|
|
17
|
-
const geometry = new THREE.BufferGeometry();
|
|
18
|
-
geometry.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), 3));
|
|
19
|
-
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(new Float32Array([0, 0, 0]), 1));
|
|
20
|
-
geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(new Float32Array([0, 0, 0]), 1));
|
|
21
|
-
ensureRenderableGeometryAttributes(geometry);
|
|
22
|
-
expect(geometry.getAttribute('uv')?.itemSize).toBe(2);
|
|
23
|
-
expect(geometry.getAttribute('uv2')?.itemSize).toBe(2);
|
|
24
|
-
expect(geometry.getAttribute('uv')?.count).toBe(3);
|
|
25
|
-
expect(geometry.getAttribute('uv2')?.count).toBe(3);
|
|
26
|
-
});
|
|
27
|
-
test('copies uv into uv2 without depending on backing array layout', () => {
|
|
28
|
-
const geometry = new THREE.BufferGeometry();
|
|
29
|
-
geometry.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), 3));
|
|
30
|
-
geometry.setAttribute('uv', new THREE.InterleavedBufferAttribute(new THREE.InterleavedBuffer(new Float32Array([0, 0, 7, 1, 0, 8, 0, 1, 9]), 3), 2, 0));
|
|
31
|
-
ensureRenderableGeometryAttributes(geometry);
|
|
32
|
-
const uv2 = geometry.getAttribute('uv2');
|
|
33
|
-
expect(Array.from(uv2.array)).toEqual([0, 0, 1, 0, 0, 1]);
|
|
34
|
-
});
|
|
35
|
-
test('replaces empty geometries with a degenerate renderable triangle', () => {
|
|
36
|
-
const geometry = new THREE.BufferGeometry();
|
|
37
|
-
ensureRenderableGeometryAttributes(geometry);
|
|
38
|
-
expect(geometry.getIndex()).toBeNull();
|
|
39
|
-
expect(geometry.groups).toHaveLength(0);
|
|
40
|
-
expect(geometry.getAttribute('position')?.count).toBe(3);
|
|
41
|
-
expect(geometry.getAttribute('normal')?.count).toBe(3);
|
|
42
|
-
expect(geometry.getAttribute('uv')?.count).toBe(3);
|
|
43
|
-
expect(geometry.getAttribute('uv2')?.count).toBe(3);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"materials.test.d.ts","sourceRoot":"","sources":["../../src/lib/materials.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,25 +0,0 @@
|
|
|
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 { getTextureKey, resolveTextureRepeat } from './materials';
|
|
5
|
-
function materialWithRepeat(repeat) {
|
|
6
|
-
return {
|
|
7
|
-
texture: {
|
|
8
|
-
url: 'https://example.com/texture.png',
|
|
9
|
-
repeat,
|
|
10
|
-
},
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
describe('legacy texture repeat values', () => {
|
|
14
|
-
test('normalizes tuple, scalar, and Vector2-shaped repeats', () => {
|
|
15
|
-
expect(resolveTextureRepeat([2, 3], undefined)).toEqual([2, 3]);
|
|
16
|
-
expect(resolveTextureRepeat(2, undefined)).toEqual([2, 2]);
|
|
17
|
-
expect(resolveTextureRepeat({ x: 2, y: 3 }, undefined)).toEqual([2, 3]);
|
|
18
|
-
});
|
|
19
|
-
test('falls back to scale for malformed repeats', () => {
|
|
20
|
-
expect(resolveTextureRepeat({ width: 2 }, 4)).toEqual([4, 4]);
|
|
21
|
-
});
|
|
22
|
-
test('keeps distinct Vector2-shaped repeats in distinct cache entries', () => {
|
|
23
|
-
expect(getTextureKey(materialWithRepeat({ x: 2, y: 3 }))).not.toBe(getTextureKey(materialWithRepeat({ x: 4, y: 5 })));
|
|
24
|
-
});
|
|
25
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"merged-outline-node.test.d.ts","sourceRoot":"","sources":["../../src/lib/merged-outline-node.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,22 +0,0 @@
|
|
|
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 { Object3D, PerspectiveCamera, Scene } from 'three';
|
|
5
|
-
import { mergedOutline } from './merged-outline-node';
|
|
6
|
-
describe('merged outline rendering', () => {
|
|
7
|
-
test('keeps selected outlines active during camera interaction', () => {
|
|
8
|
-
const cameraInteractionActive = true;
|
|
9
|
-
const params = {
|
|
10
|
-
enabled: () => !cameraInteractionActive,
|
|
11
|
-
primaryObjects: [new Object3D()],
|
|
12
|
-
};
|
|
13
|
-
const outline = mergedOutline(new Scene(), new PerspectiveCamera(), params);
|
|
14
|
-
const frame = {
|
|
15
|
-
get renderer() {
|
|
16
|
-
throw new Error('outline renderer was reached');
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
expect(() => outline.updateBefore(frame)).toThrow('outline renderer was reached');
|
|
20
|
-
outline.dispose();
|
|
21
|
-
});
|
|
22
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"polygon-union.test.d.ts","sourceRoot":"","sources":["../../src/lib/polygon-union.test.ts"],"names":[],"mappings":""}
|