@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.
Files changed (103) hide show
  1. package/README.md +52 -0
  2. package/dist/components/viewer/frame-limiter.d.ts +11 -0
  3. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  4. package/dist/components/viewer/frame-limiter.js +58 -18
  5. package/dist/components/viewer/index.d.ts +15 -0
  6. package/dist/components/viewer/index.d.ts.map +1 -1
  7. package/dist/components/viewer/index.js +34 -50
  8. package/dist/components/viewer/perf-monitor.js +2 -2
  9. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  10. package/dist/components/viewer/selection-manager.js +7 -2
  11. package/dist/components/viewer/unsupported-gpu-fallback.d.ts +2 -0
  12. package/dist/components/viewer/unsupported-gpu-fallback.d.ts.map +1 -0
  13. package/dist/components/viewer/unsupported-gpu-fallback.js +4 -0
  14. package/dist/index.d.ts +8 -5
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +7 -4
  17. package/dist/lib/dispose-object3d.d.ts +4 -0
  18. package/dist/lib/dispose-object3d.d.ts.map +1 -0
  19. package/dist/lib/dispose-object3d.js +27 -0
  20. package/dist/lib/geometry-groups.d.ts +17 -0
  21. package/dist/lib/geometry-groups.d.ts.map +1 -0
  22. package/dist/lib/geometry-groups.js +53 -0
  23. package/dist/lib/isolation.d.ts +8 -8
  24. package/dist/lib/isolation.d.ts.map +1 -1
  25. package/dist/lib/isolation.js +18 -30
  26. package/dist/lib/layers.d.ts +16 -0
  27. package/dist/lib/layers.d.ts.map +1 -1
  28. package/dist/lib/layers.js +18 -0
  29. package/dist/lib/renderer-capability.d.ts +56 -0
  30. package/dist/lib/renderer-capability.d.ts.map +1 -0
  31. package/dist/lib/renderer-capability.js +114 -0
  32. package/dist/lib/scene-visibility.d.ts +14 -0
  33. package/dist/lib/scene-visibility.d.ts.map +1 -0
  34. package/dist/lib/scene-visibility.js +48 -0
  35. package/dist/lib/shadow-only.d.ts +16 -0
  36. package/dist/lib/shadow-only.d.ts.map +1 -1
  37. package/dist/lib/shadow-only.js +8 -17
  38. package/dist/lib/snapshot-pipeline.d.ts +9 -0
  39. package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
  40. package/dist/lib/snapshot-pipeline.js +33 -9
  41. package/dist/store/use-viewer.d.ts +5 -4
  42. package/dist/store/use-viewer.d.ts.map +1 -1
  43. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
  44. package/dist/systems/floor-elevation/floor-elevation-system.js +9 -0
  45. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  46. package/dist/systems/geometry/geometry-system.js +2 -26
  47. package/dist/systems/level/level-system.d.ts.map +1 -1
  48. package/dist/systems/level/level-system.js +7 -1
  49. package/dist/systems/wall/level-miter-cache.d.ts +10 -0
  50. package/dist/systems/wall/level-miter-cache.d.ts.map +1 -0
  51. package/dist/systems/wall/level-miter-cache.js +40 -0
  52. package/dist/systems/wall/wall-cutout.d.ts +12 -0
  53. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  54. package/dist/systems/wall/wall-cutout.js +41 -11
  55. package/dist/systems/wall/wall-system.d.ts +4 -0
  56. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  57. package/dist/systems/wall/wall-system.js +31 -18
  58. package/package.json +4 -2
  59. package/dist/components/viewer/scene-bvh.test.d.ts +0 -2
  60. package/dist/components/viewer/scene-bvh.test.d.ts.map +0 -1
  61. package/dist/components/viewer/scene-bvh.test.js +0 -13
  62. package/dist/lib/csg-utils.test.d.ts +0 -2
  63. package/dist/lib/csg-utils.test.d.ts.map +0 -1
  64. package/dist/lib/csg-utils.test.js +0 -45
  65. package/dist/lib/materials.test.d.ts +0 -2
  66. package/dist/lib/materials.test.d.ts.map +0 -1
  67. package/dist/lib/materials.test.js +0 -25
  68. package/dist/lib/merged-outline-node.test.d.ts +0 -2
  69. package/dist/lib/merged-outline-node.test.d.ts.map +0 -1
  70. package/dist/lib/merged-outline-node.test.js +0 -22
  71. package/dist/lib/polygon-union.test.d.ts +0 -2
  72. package/dist/lib/polygon-union.test.d.ts.map +0 -1
  73. package/dist/lib/polygon-union.test.js +0 -105
  74. package/dist/lib/texture-reference.test.d.ts +0 -2
  75. package/dist/lib/texture-reference.test.d.ts.map +0 -1
  76. package/dist/lib/texture-reference.test.js +0 -86
  77. package/dist/store/use-viewer.test.d.ts +0 -2
  78. package/dist/store/use-viewer.test.d.ts.map +0 -1
  79. package/dist/store/use-viewer.test.js +0 -61
  80. package/dist/systems/door/door-animation.test.d.ts +0 -2
  81. package/dist/systems/door/door-animation.test.d.ts.map +0 -1
  82. package/dist/systems/door/door-animation.test.js +0 -51
  83. package/dist/systems/door/door-floor-alignment.test.d.ts +0 -2
  84. package/dist/systems/door/door-floor-alignment.test.d.ts.map +0 -1
  85. package/dist/systems/door/door-floor-alignment.test.js +0 -50
  86. package/dist/systems/geometry/geometry-system.test.d.ts +0 -2
  87. package/dist/systems/geometry/geometry-system.test.d.ts.map +0 -1
  88. package/dist/systems/geometry/geometry-system.test.js +0 -30
  89. package/dist/systems/slab/slab-system.test.d.ts +0 -2
  90. package/dist/systems/slab/slab-system.test.d.ts.map +0 -1
  91. package/dist/systems/slab/slab-system.test.js +0 -102
  92. package/dist/systems/wall/opening-cutout-geometry.test.d.ts +0 -2
  93. package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +0 -1
  94. package/dist/systems/wall/opening-cutout-geometry.test.js +0 -172
  95. package/dist/systems/wall/wall-opening-cutout.test.d.ts +0 -2
  96. package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +0 -1
  97. package/dist/systems/wall/wall-opening-cutout.test.js +0 -59
  98. package/dist/systems/wall/wall-support-extension.test.d.ts +0 -2
  99. package/dist/systems/wall/wall-support-extension.test.d.ts.map +0 -1
  100. package/dist/systems/wall/wall-support-extension.test.js +0 -116
  101. package/dist/systems/wall/wall-terrain-bottom.test.d.ts +0 -2
  102. package/dist/systems/wall/wall-terrain-bottom.test.d.ts.map +0 -1
  103. package/dist/systems/wall/wall-terrain-bottom.test.js +0 -34
@@ -1,105 +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 { subtractPolygonsFromPolygon, unionPolygons } from './polygon-union';
5
- function polygonArea(points) {
6
- let area = 0;
7
- for (let i = 0; i < points.length; i++) {
8
- const current = points[i];
9
- const next = points[(i + 1) % points.length];
10
- area += current[0] * next[1] - next[0] * current[1];
11
- }
12
- return Math.abs(area / 2);
13
- }
14
- describe('unionPolygons', () => {
15
- test('collapses a contained polygon into the containing polygon', () => {
16
- const small = [
17
- [0, 0],
18
- [1, 0],
19
- [1, 1],
20
- [0, 1],
21
- ];
22
- const large = [
23
- [-1, -1],
24
- [2, -1],
25
- [2, 2],
26
- [-1, 2],
27
- ];
28
- const result = unionPolygons([small, large]);
29
- expect(result).toHaveLength(1);
30
- expect(polygonArea(result[0])).toBeCloseTo(9);
31
- });
32
- test('combines overlapping rectangles into one boundary', () => {
33
- const left = [
34
- [0, 0],
35
- [2, 0],
36
- [2, 2],
37
- [0, 2],
38
- ];
39
- const right = [
40
- [1, 1],
41
- [3, 1],
42
- [3, 3],
43
- [1, 3],
44
- ];
45
- const result = unionPolygons([left, right]);
46
- expect(result).toHaveLength(1);
47
- expect(result[0]).toHaveLength(8);
48
- expect(polygonArea(result[0])).toBeCloseTo(7);
49
- });
50
- test('keeps disjoint polygons as separate boundaries', () => {
51
- const left = [
52
- [0, 0],
53
- [1, 0],
54
- [1, 1],
55
- [0, 1],
56
- ];
57
- const right = [
58
- [2, 0],
59
- [3, 0],
60
- [3, 1],
61
- [2, 1],
62
- ];
63
- const result = unionPolygons([left, right]);
64
- expect(result).toHaveLength(2);
65
- expect(result.map(polygonArea)).toEqual([1, 1]);
66
- });
67
- });
68
- describe('subtractPolygonsFromPolygon', () => {
69
- test('turns a boundary-overlapping cutter into an indentation', () => {
70
- const slab = [
71
- [0, 0],
72
- [4, 0],
73
- [4, 3],
74
- [0, 3],
75
- ];
76
- const cutout = [
77
- [1, -0.5],
78
- [3, -0.5],
79
- [3, 1],
80
- [1, 1],
81
- ];
82
- const result = subtractPolygonsFromPolygon(slab, [cutout]);
83
- expect(result).toHaveLength(1);
84
- expect(result[0]).toContainEqual([1, 1]);
85
- expect(result[0]).toContainEqual([3, 1]);
86
- expect(polygonArea(result[0])).toBeCloseTo(10);
87
- });
88
- test('returns separate contours when a cutter splits the subject', () => {
89
- const slab = [
90
- [0, 0],
91
- [4, 0],
92
- [4, 3],
93
- [0, 3],
94
- ];
95
- const cutout = [
96
- [1.5, -1],
97
- [2.5, -1],
98
- [2.5, 4],
99
- [1.5, 4],
100
- ];
101
- const result = subtractPolygonsFromPolygon(slab, [cutout]);
102
- expect(result).toHaveLength(2);
103
- expect(result.map(polygonArea).sort((a, b) => a - b)).toEqual([4.5, 4.5]);
104
- });
105
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=texture-reference.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"texture-reference.test.d.ts","sourceRoot":"","sources":["../../src/lib/texture-reference.test.ts"],"names":[],"mappings":""}
@@ -1,86 +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 { 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
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=use-viewer.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"use-viewer.test.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.test.ts"],"names":[],"mappings":""}
@@ -1,61 +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 { afterEach, beforeEach, describe, expect, test } from 'bun:test';
4
- import useViewer from './use-viewer';
5
- const resetMeasurementPreferences = () => {
6
- useViewer.setState({
7
- externalSelectedIds: [],
8
- projectId: null,
9
- projectPreferences: {},
10
- showMeasurements: true,
11
- unit: 'metric',
12
- metricNotation: 'meters',
13
- });
14
- };
15
- beforeEach(resetMeasurementPreferences);
16
- afterEach(resetMeasurementPreferences);
17
- describe('measurement display preferences', () => {
18
- test('stores global visibility per project and defaults new projects to visible', () => {
19
- const viewer = useViewer.getState();
20
- viewer.setProjectId('project-a');
21
- viewer.setShowMeasurements(false);
22
- expect(useViewer.getState()).toMatchObject({
23
- projectId: 'project-a',
24
- showMeasurements: false,
25
- projectPreferences: {
26
- 'project-a': { showMeasurements: false },
27
- },
28
- });
29
- useViewer.getState().setProjectId('project-b');
30
- expect(useViewer.getState().showMeasurements).toBe(true);
31
- useViewer.getState().setProjectId('project-a');
32
- expect(useViewer.getState().showMeasurements).toBe(false);
33
- });
34
- test('changes display units without changing measurement visibility preferences', () => {
35
- const viewer = useViewer.getState();
36
- viewer.setProjectId('project-a');
37
- viewer.setShowMeasurements(false);
38
- const preferences = useViewer.getState().projectPreferences;
39
- useViewer.getState().setUnit('imperial');
40
- expect(useViewer.getState().unit).toBe('imperial');
41
- expect(useViewer.getState().projectPreferences).toEqual(preferences);
42
- expect(useViewer.getState().showMeasurements).toBe(false);
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
- });
61
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=door-animation.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"door-animation.test.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-animation.test.ts"],"names":[],"mappings":""}
@@ -1,51 +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 { DoorNode } from '@pascal-app/core';
5
- import * as THREE from 'three';
6
- import { buildDoorPreviewMesh, poseDoorMovingParts } from '../../index';
7
- // The named moving group each operation-door builder emits at the closed pose.
8
- const MOVING_GROUP = {
9
- sliding: 'door-sliding-active',
10
- pocket: 'door-pocket-leaf',
11
- barn: 'door-barn-leaf',
12
- 'garage-tiltup': 'door-tiltup-leaf',
13
- folding: 'door-fold-0',
14
- 'garage-sectional': 'door-sectional-0',
15
- 'garage-rollup': 'door-rollup-curtain',
16
- };
17
- function worldPosition(object) {
18
- object.updateMatrixWorld(true);
19
- return object.getWorldPosition(new THREE.Vector3());
20
- }
21
- describe('operation door open kinematics', () => {
22
- // Each operation door builds its moving parts in a named group at the closed
23
- // pose; `poseDoorMovingParts` then drives the open motion (the single source
24
- // of truth shared by the live system and the GLB exporter). This guards the
25
- // build-once + pose-at-t contract: the group exists, rests closed, and moves.
26
- for (const doorType of Object.keys(MOVING_GROUP)) {
27
- test(`${doorType} builds a moving group that opens`, () => {
28
- const node = DoorNode.parse({ id: `door_${doorType}`, doorType, operationState: 0 });
29
- const mesh = buildDoorPreviewMesh(node);
30
- const group = mesh.getObjectByName(MOVING_GROUP[doorType]);
31
- expect(group).toBeDefined();
32
- // Closed snapshot.
33
- poseDoorMovingParts(node, mesh, 0);
34
- const closedPos = worldPosition(group);
35
- const closedScaleY = group.scale.y;
36
- const closedRotX = group.rotation.x;
37
- const closedRotY = group.rotation.y;
38
- // Open snapshot — at least one of translation / rotation / scale changes.
39
- poseDoorMovingParts(node, mesh, 1);
40
- const openPos = worldPosition(group);
41
- const moved = closedPos.distanceTo(openPos) > 0.05 ||
42
- Math.abs(closedScaleY - group.scale.y) > 0.1 ||
43
- Math.abs(closedRotX - group.rotation.x) > 0.05 ||
44
- Math.abs(closedRotY - group.rotation.y) > 0.05;
45
- expect(moved).toBe(true);
46
- // Posing back to closed is the identity of the rest pose.
47
- poseDoorMovingParts(node, mesh, 0);
48
- expect(worldPosition(group).distanceTo(closedPos)).toBeLessThan(1e-6);
49
- });
50
- }
51
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=door-floor-alignment.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"door-floor-alignment.test.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-floor-alignment.test.ts"],"names":[],"mappings":""}
@@ -1,50 +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 { DoorNode } from '@pascal-app/core';
5
- import * as THREE from 'three';
6
- import { buildDoorPreviewMesh } from '../../index';
7
- const DOOR_TYPES = [
8
- 'hinged',
9
- 'double',
10
- 'french',
11
- 'folding',
12
- 'pocket',
13
- 'barn',
14
- 'sliding',
15
- 'garage-sectional',
16
- 'garage-rollup',
17
- 'garage-tiltup',
18
- ];
19
- function visibleBounds(mesh) {
20
- mesh.updateMatrixWorld(true);
21
- const bounds = new THREE.Box3();
22
- for (const child of mesh.children) {
23
- if (child.name === 'cutout')
24
- continue;
25
- bounds.expandByObject(child, true);
26
- }
27
- return bounds;
28
- }
29
- describe('door floor alignment', () => {
30
- for (const doorType of DOOR_TYPES) {
31
- test(`${doorType} does not extend below the opening floor`, () => {
32
- const node = DoorNode.parse({
33
- id: `door_floor-alignment-${doorType}`,
34
- doorType,
35
- operationState: 0,
36
- threshold: true,
37
- });
38
- const mesh = buildDoorPreviewMesh(node);
39
- const bounds = visibleBounds(mesh);
40
- expect(bounds.min.y).toBeGreaterThanOrEqual(-node.height / 2 - 1e-6);
41
- });
42
- }
43
- test('keeps the wall cutout bottom locked to the opening floor', () => {
44
- const node = DoorNode.parse({ id: 'door_floor-alignment-cutout' });
45
- const mesh = buildDoorPreviewMesh(node);
46
- const cutout = mesh.getObjectByName('cutout');
47
- cutout.geometry.computeBoundingBox();
48
- expect(cutout.geometry.boundingBox?.min.y).toBeCloseTo(-node.height / 2, 6);
49
- });
50
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=geometry-system.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"geometry-system.test.d.ts","sourceRoot":"","sources":["../../../src/systems/geometry/geometry-system.test.ts"],"names":[],"mappings":""}
@@ -1,30 +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 { BoxGeometry, Group, Mesh, MeshBasicMaterial } from 'three';
5
- import { markGeometryBuildOutput, shouldReuseGeometryBuild, } from './geometry-system';
6
- describe('markGeometryBuildOutput', () => {
7
- test('marks nested builder meshes for slot paint preview', () => {
8
- const root = new Group();
9
- const nested = new Group();
10
- const mesh = new Mesh(new BoxGeometry(1, 1, 1), new MeshBasicMaterial());
11
- mesh.userData.slotId = 'appliance';
12
- nested.add(mesh);
13
- root.add(nested);
14
- markGeometryBuildOutput(root);
15
- expect(root.userData.__fromGeometry).toBe(true);
16
- expect(nested.userData.__fromGeometry).toBe(true);
17
- expect(mesh.userData.__fromGeometry).toBe(true);
18
- expect(mesh.userData.slotId).toBe('appliance');
19
- });
20
- });
21
- describe('shouldReuseGeometryBuild', () => {
22
- test('rebuilds when the same node id remounts into a new group with the same key', () => {
23
- const cache = new Map();
24
- const firstGroup = new Group();
25
- const remountedGroup = new Group();
26
- expect(shouldReuseGeometryBuild(cache, 'duct_1', firstGroup, 'same-key')).toBe(false);
27
- expect(shouldReuseGeometryBuild(cache, 'duct_1', firstGroup, 'same-key')).toBe(true);
28
- expect(shouldReuseGeometryBuild(cache, 'duct_1', remountedGroup, 'same-key')).toBe(false);
29
- });
30
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=slab-system.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"slab-system.test.d.ts","sourceRoot":"","sources":["../../../src/systems/slab/slab-system.test.ts"],"names":[],"mappings":""}
@@ -1,102 +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 { SlabNode } from '@pascal-app/core';
5
- import { generateSlabGeometry } from './slab-system';
6
- const EMPTY_CONTEXT = { walls: [], siblingSlabs: [] };
7
- const SQUARE = [
8
- [0, 0],
9
- [4, 0],
10
- [4, 3],
11
- [0, 3],
12
- ];
13
- function hasVertexAt(geometry, x, z) {
14
- const positions = geometry.getAttribute('position');
15
- for (let index = 0; index < positions.count; index += 1) {
16
- if (Math.abs(positions.getX(index) - x) < 1e-6 && Math.abs(positions.getZ(index) - z) < 1e-6) {
17
- return true;
18
- }
19
- }
20
- return false;
21
- }
22
- function uniqueSortedYs(geometry) {
23
- const positions = geometry.getAttribute('position');
24
- const ys = new Set();
25
- for (let index = 0; index < positions.count; index += 1) {
26
- ys.add(Math.round(positions.getY(index) * 1e4) / 1e4);
27
- }
28
- return [...ys].sort((a, b) => a - b);
29
- }
30
- describe('generateSlabGeometry', () => {
31
- test('renders a boundary-overlapping hole as an open indentation', () => {
32
- const slab = SlabNode.parse({
33
- elevation: 0.05,
34
- polygon: SQUARE,
35
- holes: [
36
- [
37
- [1, -0.5],
38
- [3, -0.5],
39
- [3, 1],
40
- [1, 1],
41
- ],
42
- ],
43
- });
44
- const geometry = generateSlabGeometry(slab, EMPTY_CONTEXT);
45
- expect((geometry.index?.count ?? 0) / 3).toBeGreaterThan(0);
46
- expect(hasVertexAt(geometry, 1, 1)).toBe(true);
47
- expect(hasVertexAt(geometry, 3, 1)).toBe(true);
48
- });
49
- test('renders a boundary-overlapping hole as an open indentation on recessed slabs', () => {
50
- const slab = SlabNode.parse({
51
- elevation: -0.2,
52
- recessed: true,
53
- polygon: SQUARE,
54
- holes: [
55
- [
56
- [1, -0.5],
57
- [3, -0.5],
58
- [3, 1],
59
- [1, 1],
60
- ],
61
- ],
62
- });
63
- const geometry = generateSlabGeometry(slab, EMPTY_CONTEXT);
64
- expect((geometry.index?.count ?? 0) / 3).toBeGreaterThan(0);
65
- expect(hasVertexAt(geometry, 1, 1)).toBe(true);
66
- expect(hasVertexAt(geometry, 3, 1)).toBe(true);
67
- });
68
- test('solid slab occupies [elevation − thickness, elevation]', () => {
69
- const slab = SlabNode.parse({ elevation: 0.3, thickness: 0.1, polygon: SQUARE });
70
- const ys = uniqueSortedYs(generateSlabGeometry(slab, EMPTY_CONTEXT));
71
- expect(ys).toEqual([0.2, 0.3]);
72
- });
73
- test('migrated legacy slab (thickness = elevation) reproduces the [0, elevation] extrusion', () => {
74
- const slab = SlabNode.parse({ elevation: 0.05, thickness: 0.05, polygon: SQUARE });
75
- const geometry = generateSlabGeometry(slab, EMPTY_CONTEXT);
76
- // Old-style expectations: extrude-from-zero put the bottom cap at 0 and
77
- // the top cap at `elevation`, with 8 cap verts + 4 side quads (16 verts)
78
- // and 12 triangles for a plain quad slab.
79
- expect(uniqueSortedYs(geometry)).toEqual([0, 0.05]);
80
- expect(geometry.getAttribute('position').count).toBe(24);
81
- expect((geometry.index?.count ?? 0) / 3).toBe(12);
82
- for (const [x, z] of SQUARE) {
83
- expect(hasVertexAt(geometry, x, z)).toBe(true);
84
- }
85
- });
86
- test('recess is keyed by the flag, not the elevation sign', () => {
87
- const belowPlaneSolid = SlabNode.parse({ elevation: -0.2, thickness: 0.05, polygon: SQUARE });
88
- // Without `recessed`, a negative elevation is just a solid placed below
89
- // the level plane: closed body at [-0.25, -0.2], world-space Y baked in.
90
- expect(uniqueSortedYs(generateSlabGeometry(belowPlaneSolid, EMPTY_CONTEXT))).toEqual([
91
- -0.25, -0.2,
92
- ]);
93
- // The recessed shell is authored at local Y=0 (floor) up to |elevation|
94
- // (rim), with no top cap: 4 floor verts + 4 wall quads = 20 verts,
95
- // 2 floor + 8 wall triangles.
96
- const pool = SlabNode.parse({ elevation: -0.2, recessed: true, polygon: SQUARE });
97
- const poolGeometry = generateSlabGeometry(pool, EMPTY_CONTEXT);
98
- expect(uniqueSortedYs(poolGeometry)).toEqual([0, 0.2]);
99
- expect(poolGeometry.getAttribute('position').count).toBe(20);
100
- expect((poolGeometry.index?.count ?? 0) / 3).toBe(10);
101
- });
102
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=opening-cutout-geometry.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"opening-cutout-geometry.test.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/opening-cutout-geometry.test.ts"],"names":[],"mappings":""}