@pascal-app/viewer 0.9.1 → 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/error-boundary.d.ts +6 -0
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary.js +6 -0
- package/dist/components/renderers/node-renderer.d.ts +5 -1
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +5 -2
- package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/parametric-node-renderer.js +7 -4
- package/dist/components/viewer/bvh-ecctrl.d.ts +77 -0
- package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -0
- package/dist/components/viewer/bvh-ecctrl.js +552 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +59 -4
- package/dist/components/viewer/glb-interactive.d.ts +41 -0
- package/dist/components/viewer/glb-interactive.d.ts.map +1 -0
- package/dist/components/viewer/glb-interactive.js +394 -0
- package/dist/components/viewer/glb-reference-nodes.d.ts +29 -0
- package/dist/components/viewer/glb-reference-nodes.d.ts.map +1 -0
- package/dist/components/viewer/glb-reference-nodes.js +80 -0
- package/dist/components/viewer/glb-replace-instances.d.ts +18 -0
- package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -0
- package/dist/components/viewer/glb-replace-instances.js +61 -0
- package/dist/components/viewer/glb-scene.d.ts +54 -0
- package/dist/components/viewer/glb-scene.d.ts.map +1 -0
- package/dist/components/viewer/glb-scene.js +920 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +22 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -0
- package/dist/components/viewer/glb-walkthrough-controller.js +445 -0
- package/dist/components/viewer/index.d.ts +19 -2
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +113 -12
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +36 -5
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +17 -2
- package/dist/components/viewer/post-processing.d.ts +7 -1
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +180 -24
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +6 -4
- package/dist/components/viewer/scene-bvh.d.ts +2 -1
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -1
- package/dist/components/viewer/scene-bvh.js +3 -0
- package/dist/components/viewer/scene-bvh.test.d.ts +2 -0
- package/dist/components/viewer/scene-bvh.test.d.ts.map +1 -0
- package/dist/components/viewer/scene-bvh.test.js +13 -0
- package/dist/components/viewer/scene-environment.d.ts +2 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -0
- package/dist/components/viewer/scene-environment.js +79 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +23 -12
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +2 -24
- package/dist/hooks/use-node-events.d.ts +1 -1
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +22 -14
- package/dist/index.d.ts +24 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -13
- package/dist/lib/backdrop.d.ts +33 -0
- package/dist/lib/backdrop.d.ts.map +1 -0
- package/dist/lib/backdrop.js +92 -0
- package/dist/lib/box-uv.d.ts +13 -0
- package/dist/lib/box-uv.d.ts.map +1 -0
- package/dist/lib/box-uv.js +32 -0
- package/dist/lib/csg-utils.d.ts +3 -9
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +65 -5
- package/dist/lib/csg-utils.test.d.ts +2 -0
- package/dist/lib/csg-utils.test.d.ts.map +1 -0
- package/dist/lib/csg-utils.test.js +45 -0
- package/dist/lib/edge-style.d.ts +1 -0
- package/dist/lib/edge-style.d.ts.map +1 -1
- package/dist/lib/edge-style.js +26 -6
- 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/ink-edges.d.ts +1 -1
- package/dist/lib/ink-edges.d.ts.map +1 -1
- package/dist/lib/ink-edges.js +13 -7
- package/dist/lib/ktx2-loader.d.ts +46 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -0
- package/dist/lib/ktx2-loader.js +142 -0
- package/dist/lib/layers.d.ts +10 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +10 -0
- package/dist/lib/materials.d.ts +16 -1
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +158 -34
- 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/scene-themes.d.ts +1 -0
- package/dist/lib/scene-themes.d.ts.map +1 -1
- package/dist/lib/scene-themes.js +25 -16
- package/dist/lib/shadow-only.d.ts +4 -0
- package/dist/lib/shadow-only.d.ts.map +1 -0
- package/dist/lib/shadow-only.js +36 -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/lib/tsl-compat.d.ts +12 -0
- package/dist/lib/tsl-compat.d.ts.map +1 -0
- package/dist/lib/tsl-compat.js +13 -0
- package/dist/store/use-viewer.d.ts +73 -18
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +191 -4
- package/dist/store/use-viewer.test.d.ts +2 -0
- package/dist/store/use-viewer.test.d.ts.map +1 -0
- package/dist/store/use-viewer.test.js +61 -0
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +12 -5
- package/dist/systems/door/door-animation.test.d.ts +2 -0
- package/dist/systems/door/door-animation.test.d.ts.map +1 -0
- package/dist/systems/door/door-animation.test.js +51 -0
- package/dist/systems/door/door-floor-alignment.test.d.ts +2 -0
- package/dist/systems/door/door-floor-alignment.test.d.ts.map +1 -0
- package/dist/systems/door/door-floor-alignment.test.js +50 -0
- package/dist/systems/door/door-system.d.ts +24 -0
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +494 -161
- package/dist/systems/fence/fence-system.d.ts +17 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -1
- package/dist/systems/fence/fence-system.js +221 -53
- 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/geometry/geometry-system.d.ts +7 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +95 -26
- package/dist/systems/geometry/geometry-system.test.d.ts +2 -0
- package/dist/systems/geometry/geometry-system.test.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-system.test.js +30 -0
- package/dist/systems/interactive/control-widget.d.ts +10 -0
- package/dist/systems/interactive/control-widget.d.ts.map +1 -0
- package/dist/systems/interactive/control-widget.js +35 -0
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +55 -56
- package/dist/systems/item/item-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.js +20 -6
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +34 -9
- package/dist/systems/level/level-utils.d.ts +0 -3
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +7 -45
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +29 -8
- package/dist/systems/roof/roof-system.d.ts +21 -8
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1079 -221
- package/dist/systems/slab/slab-system.d.ts +7 -4
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +31 -83
- package/dist/systems/slab/slab-system.test.js +54 -14
- package/dist/systems/stair/stair-system.js +7 -4
- package/dist/systems/wall/opening-cutout-geometry.d.ts +31 -0
- package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -0
- package/dist/systems/wall/opening-cutout-geometry.js +167 -0
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts +2 -0
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +1 -0
- package/dist/systems/wall/opening-cutout-geometry.test.js +172 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +49 -17
- package/dist/systems/wall/wall-materials.d.ts +11 -7
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +244 -35
- package/dist/systems/wall/wall-opening-cutout.test.d.ts +2 -0
- package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-opening-cutout.test.js +59 -0
- package/dist/systems/wall/wall-support-extension.test.d.ts +2 -0
- package/dist/systems/wall/wall-support-extension.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-support-extension.test.js +116 -0
- package/dist/systems/wall/wall-system.d.ts +4 -8
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +397 -177
- 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/dist/systems/window/window-animation-system.d.ts +12 -0
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +16 -5
- package/dist/systems/window/window-system.d.ts +7 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +240 -14
- package/package.json +4 -4
- package/dist/components/viewer/ground-occluder.d.ts +0 -2
- package/dist/components/viewer/ground-occluder.d.ts.map +0 -1
- package/dist/components/viewer/ground-occluder.js +0 -67
- package/dist/components/viewer/walkthrough-controls.d.ts +0 -2
- package/dist/components/viewer/walkthrough-controls.d.ts.map +0 -1
- package/dist/components/viewer/walkthrough-controls.js +0 -131
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { getEffectiveWallSurfaceMaterial, getMaterialPresetByRef, getWallSurfaceMaterialSignature, resolveMaterial, } from '@pascal-app/core';
|
|
1
|
+
import { getEffectiveWallSurfaceMaterial, getMaterialPresetByRef, getWallSurfaceMaterialSignature, getWallSurfaceSideFromBandSlot, parseMaterialRef, resolveMaterial, WALL_SLOT_DEFAULT, WALL_SURFACE_SLOT_DEFAULTS, } from '@pascal-app/core';
|
|
2
2
|
import { Color } from 'three';
|
|
3
3
|
import { Fn, float, fract, length, mix, positionLocal, smoothstep, step, vec2 } from 'three/tsl';
|
|
4
4
|
import { MeshLambertNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
5
|
-
import { baseMaterial, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, resolveSurfaceColor, } from '../../lib/materials';
|
|
6
|
-
const DEFAULT_WALL_COLOR = '#
|
|
5
|
+
import { baseMaterial, createDefaultMaterial, createMaterial, createMaterialFromPresetRef, createSurfaceRoleMaterial, resolveMaterialRef, resolveSurfaceColor, } from '../../lib/materials';
|
|
6
|
+
const DEFAULT_WALL_COLOR = '#e9e7e3';
|
|
7
7
|
const WALL_HIGHLIGHT_PROFILES = {
|
|
8
8
|
delete: {
|
|
9
9
|
color: new Color('#dc2626'),
|
|
@@ -11,12 +11,6 @@ const WALL_HIGHLIGHT_PROFILES = {
|
|
|
11
11
|
emissiveBlend: 0.92,
|
|
12
12
|
emissiveIntensity: 0.46,
|
|
13
13
|
},
|
|
14
|
-
selection: {
|
|
15
|
-
color: new Color('#818cf8'),
|
|
16
|
-
blend: 0.32,
|
|
17
|
-
emissiveBlend: 0.7,
|
|
18
|
-
emissiveIntensity: 0.42,
|
|
19
|
-
},
|
|
20
14
|
};
|
|
21
15
|
const wallMaterialCache = new Map();
|
|
22
16
|
const dotPattern = Fn(() => {
|
|
@@ -42,6 +36,108 @@ function getSurfaceVisibleMaterial(spec, shading) {
|
|
|
42
36
|
function hasExplicitMaterial(spec) {
|
|
43
37
|
return Boolean(spec.materialPreset || spec.material);
|
|
44
38
|
}
|
|
39
|
+
// Resolve a wall face's declared default — a catalog `library:` finish or a
|
|
40
|
+
// flat colour — to a renderable material.
|
|
41
|
+
function resolveWallSlotDefault(slotDefault, shading) {
|
|
42
|
+
if (parseMaterialRef(slotDefault)?.kind === 'library') {
|
|
43
|
+
return createMaterialFromPresetRef(slotDefault, shading) ?? baseMaterial(shading);
|
|
44
|
+
}
|
|
45
|
+
return createDefaultMaterial(slotDefault, 0.9, shading);
|
|
46
|
+
}
|
|
47
|
+
// Slot-first resolution for one wall face, matching every other paintable kind:
|
|
48
|
+
// node.slots[side] ref → legacy inline fields → declared slot default.
|
|
49
|
+
// A dangling `scene:` ref (material deleted / copied across scenes) falls back
|
|
50
|
+
// to the declared default — it never blocks rendering (the dangling-ref rule).
|
|
51
|
+
function resolveWallFaceMaterial(wallNode, side, shading, sceneMaterials) {
|
|
52
|
+
const ref = wallNode.slots?.[side];
|
|
53
|
+
if (ref) {
|
|
54
|
+
return (resolveMaterialRef(ref, sceneMaterials, shading) ??
|
|
55
|
+
resolveWallSlotDefault(WALL_SLOT_DEFAULT[side], shading));
|
|
56
|
+
}
|
|
57
|
+
const spec = getEffectiveWallSurfaceMaterial(wallNode, side);
|
|
58
|
+
if (hasExplicitMaterial(spec)) {
|
|
59
|
+
return getSurfaceVisibleMaterial(spec, shading);
|
|
60
|
+
}
|
|
61
|
+
return resolveWallSlotDefault(WALL_SLOT_DEFAULT[side], shading);
|
|
62
|
+
}
|
|
63
|
+
function resolveWallSlotMaterial(wallNode, slotId, shading, sceneMaterials) {
|
|
64
|
+
const ref = wallNode.slots?.[slotId];
|
|
65
|
+
if (ref) {
|
|
66
|
+
return (resolveMaterialRef(ref, sceneMaterials, shading) ??
|
|
67
|
+
resolveWallSlotDefault(WALL_SURFACE_SLOT_DEFAULTS[slotId], shading));
|
|
68
|
+
}
|
|
69
|
+
const side = getWallSurfaceSideFromBandSlot(slotId);
|
|
70
|
+
if (side) {
|
|
71
|
+
return resolveWallFaceMaterial(wallNode, side, shading, sceneMaterials);
|
|
72
|
+
}
|
|
73
|
+
return resolveWallSlotDefault(WALL_SURFACE_SLOT_DEFAULTS[slotId], shading);
|
|
74
|
+
}
|
|
75
|
+
// Cache-key fragment for one face: the slot ref plus, for a `scene:` ref, the
|
|
76
|
+
// referenced material's *content* — so editing a scene material assigned to a
|
|
77
|
+
// wall invalidates the cache (a `library:` ref is static catalog content, so
|
|
78
|
+
// its id alone is enough). Falls back to the legacy signature when unmigrated.
|
|
79
|
+
function wallFaceMaterialSignature(wallNode, side, sceneMaterials) {
|
|
80
|
+
const ref = wallNode.slots?.[side];
|
|
81
|
+
if (ref) {
|
|
82
|
+
const parsed = parseMaterialRef(ref);
|
|
83
|
+
if (parsed?.kind === 'scene') {
|
|
84
|
+
return JSON.stringify({
|
|
85
|
+
ref,
|
|
86
|
+
material: sceneMaterials?.[parsed.id]?.material ?? null,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return JSON.stringify({ ref });
|
|
90
|
+
}
|
|
91
|
+
return getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(wallNode, side));
|
|
92
|
+
}
|
|
93
|
+
function wallSlotMaterialSignature(wallNode, slotId, sceneMaterials) {
|
|
94
|
+
const ref = wallNode.slots?.[slotId];
|
|
95
|
+
if (ref) {
|
|
96
|
+
const parsed = parseMaterialRef(ref);
|
|
97
|
+
if (parsed?.kind === 'scene') {
|
|
98
|
+
return JSON.stringify({
|
|
99
|
+
ref,
|
|
100
|
+
material: sceneMaterials?.[parsed.id]?.material ?? null,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return JSON.stringify({ ref });
|
|
104
|
+
}
|
|
105
|
+
const side = getWallSurfaceSideFromBandSlot(slotId);
|
|
106
|
+
if (side)
|
|
107
|
+
return wallFaceMaterialSignature(wallNode, side, sceneMaterials);
|
|
108
|
+
return JSON.stringify({ default: WALL_SURFACE_SLOT_DEFAULTS[slotId] });
|
|
109
|
+
}
|
|
110
|
+
// Slot-first tint for the cutaway/invisible wall variant.
|
|
111
|
+
function resolveWallFaceColor(wallNode, side, sceneMaterials, fallback) {
|
|
112
|
+
const ref = wallNode.slots?.[side];
|
|
113
|
+
if (ref) {
|
|
114
|
+
const parsed = parseMaterialRef(ref);
|
|
115
|
+
if (parsed?.kind === 'library') {
|
|
116
|
+
return getMaterialPresetByRef(ref)?.mapProperties?.color ?? fallback;
|
|
117
|
+
}
|
|
118
|
+
if (parsed?.kind === 'scene') {
|
|
119
|
+
const sceneMaterial = sceneMaterials?.[parsed.id];
|
|
120
|
+
return sceneMaterial ? resolveMaterial(sceneMaterial.material).color : fallback;
|
|
121
|
+
}
|
|
122
|
+
return fallback;
|
|
123
|
+
}
|
|
124
|
+
return getSurfaceColor(getEffectiveWallSurfaceMaterial(wallNode, side), fallback);
|
|
125
|
+
}
|
|
126
|
+
function resolveWallSlotColor(wallNode, slotId, sceneMaterials, fallback) {
|
|
127
|
+
const ref = wallNode.slots?.[slotId];
|
|
128
|
+
if (ref) {
|
|
129
|
+
const parsed = parseMaterialRef(ref);
|
|
130
|
+
if (parsed?.kind === 'library') {
|
|
131
|
+
return getMaterialPresetByRef(ref)?.mapProperties?.color ?? fallback;
|
|
132
|
+
}
|
|
133
|
+
if (parsed?.kind === 'scene') {
|
|
134
|
+
const sceneMaterial = sceneMaterials?.[parsed.id];
|
|
135
|
+
return sceneMaterial ? resolveMaterial(sceneMaterial.material).color : fallback;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const side = getWallSurfaceSideFromBandSlot(slotId);
|
|
139
|
+
return side ? resolveWallFaceColor(wallNode, side, sceneMaterials, fallback) : fallback;
|
|
140
|
+
}
|
|
45
141
|
function getSurfaceColor(spec, fallback = DEFAULT_WALL_COLOR) {
|
|
46
142
|
const preset = getMaterialPresetByRef(spec.materialPreset);
|
|
47
143
|
if (preset?.mapProperties?.color) {
|
|
@@ -73,6 +169,60 @@ function createHighlightedWallMaterial(material, kind) {
|
|
|
73
169
|
highlightedMaterial.needsUpdate = true;
|
|
74
170
|
return highlightedMaterial;
|
|
75
171
|
}
|
|
172
|
+
// Light selection highlight for walls (walls are excluded from the generic
|
|
173
|
+
// editor selection highlight, so they need their own). Adds a gentle indigo
|
|
174
|
+
// emissive (no albedo tint) so the real material/texture stays readable with a
|
|
175
|
+
// soft "selected" glow. Two NodeMaterial-clone gotchas are handled:
|
|
176
|
+
// 1. `clone()` on the WebGPU backend drops the texture-map nodes → re-attach
|
|
177
|
+
// them from the source (shared by reference).
|
|
178
|
+
// 2. The wall's finish texture loads async, so an early clone has no map yet →
|
|
179
|
+
// cache keyed by the source `.map` and rebuild when it changes (self-heals
|
|
180
|
+
// once the texture lands).
|
|
181
|
+
const SELECTION_HIGHLIGHT_COLOR = new Color('#818cf8');
|
|
182
|
+
const SELECTION_EMISSIVE_BLEND = 0.4;
|
|
183
|
+
const SELECTION_EMISSIVE_INTENSITY = 0.12;
|
|
184
|
+
const SELECTION_TEXTURE_MAP_KEYS = [
|
|
185
|
+
'map',
|
|
186
|
+
'normalMap',
|
|
187
|
+
'roughnessMap',
|
|
188
|
+
'metalnessMap',
|
|
189
|
+
'aoMap',
|
|
190
|
+
'emissiveMap',
|
|
191
|
+
'bumpMap',
|
|
192
|
+
'displacementMap',
|
|
193
|
+
'alphaMap',
|
|
194
|
+
'lightMap',
|
|
195
|
+
];
|
|
196
|
+
const selectionHighlightCache = new WeakMap();
|
|
197
|
+
function getSelectionHighlightMaterial(base) {
|
|
198
|
+
const baseMap = base.map ?? null;
|
|
199
|
+
const cached = selectionHighlightCache.get(base);
|
|
200
|
+
if (cached && cached.map === baseMap)
|
|
201
|
+
return cached.clone;
|
|
202
|
+
const clone = base.clone();
|
|
203
|
+
// Re-attach texture maps the WebGPU NodeMaterial clone drops.
|
|
204
|
+
const src = base;
|
|
205
|
+
const dst = clone;
|
|
206
|
+
for (const key of SELECTION_TEXTURE_MAP_KEYS) {
|
|
207
|
+
if (src[key])
|
|
208
|
+
dst[key] = src[key];
|
|
209
|
+
}
|
|
210
|
+
if ('emissive' in clone && clone.emissive) {
|
|
211
|
+
clone.emissive = clone.emissive
|
|
212
|
+
.clone()
|
|
213
|
+
.lerp(SELECTION_HIGHLIGHT_COLOR, SELECTION_EMISSIVE_BLEND);
|
|
214
|
+
}
|
|
215
|
+
if ('emissiveIntensity' in clone) {
|
|
216
|
+
clone.emissiveIntensity = Math.max(clone.emissiveIntensity ?? 0, SELECTION_EMISSIVE_INTENSITY);
|
|
217
|
+
}
|
|
218
|
+
clone.needsUpdate = true;
|
|
219
|
+
selectionHighlightCache.set(base, { clone, map: baseMap });
|
|
220
|
+
return clone;
|
|
221
|
+
}
|
|
222
|
+
/** Lazy light-emissive selection variant of a wall's material array (keeps texture). */
|
|
223
|
+
export function getSelectionHighlightMaterials(materials) {
|
|
224
|
+
return materials.map(getSelectionHighlightMaterial);
|
|
225
|
+
}
|
|
76
226
|
function createInvisibleWallMaterial(color, shading) {
|
|
77
227
|
const material = shading === 'solid'
|
|
78
228
|
? new MeshLambertNodeMaterial({
|
|
@@ -90,6 +240,22 @@ function createInvisibleWallMaterial(color, shading) {
|
|
|
90
240
|
material.opacityNode = mix(float(0.0), float(0.24), dotPattern());
|
|
91
241
|
return material;
|
|
92
242
|
}
|
|
243
|
+
function createTranslucentWallMaterial(color, shading) {
|
|
244
|
+
const material = shading === 'solid'
|
|
245
|
+
? new MeshLambertNodeMaterial({
|
|
246
|
+
transparent: true,
|
|
247
|
+
color,
|
|
248
|
+
opacity: 0.35,
|
|
249
|
+
depthWrite: false,
|
|
250
|
+
})
|
|
251
|
+
: new MeshStandardNodeMaterial({
|
|
252
|
+
transparent: true,
|
|
253
|
+
color,
|
|
254
|
+
opacity: 0.35,
|
|
255
|
+
depthWrite: false,
|
|
256
|
+
});
|
|
257
|
+
return material;
|
|
258
|
+
}
|
|
93
259
|
function mapWallMaterialArray(materials, iteratee) {
|
|
94
260
|
return materials.map(iteratee);
|
|
95
261
|
}
|
|
@@ -104,17 +270,25 @@ function disposeOwnedMaterials(materials) {
|
|
|
104
270
|
material.dispose();
|
|
105
271
|
});
|
|
106
272
|
}
|
|
107
|
-
export function getWallMaterialHash(wallNode, shading) {
|
|
273
|
+
export function getWallMaterialHash(wallNode, shading, sceneMaterials) {
|
|
108
274
|
return JSON.stringify({
|
|
109
275
|
shading,
|
|
110
|
-
interior:
|
|
111
|
-
exterior:
|
|
276
|
+
interior: wallFaceMaterialSignature(wallNode, 'interior', sceneMaterials),
|
|
277
|
+
exterior: wallFaceMaterialSignature(wallNode, 'exterior', sceneMaterials),
|
|
278
|
+
lowerInterior: wallSlotMaterialSignature(wallNode, 'lowerInterior', sceneMaterials),
|
|
279
|
+
middleInterior: wallSlotMaterialSignature(wallNode, 'middleInterior', sceneMaterials),
|
|
280
|
+
upperInterior: wallSlotMaterialSignature(wallNode, 'upperInterior', sceneMaterials),
|
|
281
|
+
topInterior: wallSlotMaterialSignature(wallNode, 'topInterior', sceneMaterials),
|
|
282
|
+
lowerExterior: wallSlotMaterialSignature(wallNode, 'lowerExterior', sceneMaterials),
|
|
283
|
+
middleExterior: wallSlotMaterialSignature(wallNode, 'middleExterior', sceneMaterials),
|
|
284
|
+
upperExterior: wallSlotMaterialSignature(wallNode, 'upperExterior', sceneMaterials),
|
|
285
|
+
topExterior: wallSlotMaterialSignature(wallNode, 'topExterior', sceneMaterials),
|
|
112
286
|
});
|
|
113
287
|
}
|
|
114
|
-
export function getMaterialsForWall(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme) {
|
|
288
|
+
export function getMaterialsForWall(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme, sceneMaterials) {
|
|
115
289
|
const cacheKey = `${wallNode.id}-${shading}-${textures}-${colorPreset}-${sceneTheme ?? 'base'}`;
|
|
116
290
|
const materialHash = textures
|
|
117
|
-
? getWallMaterialHash(wallNode, shading)
|
|
291
|
+
? getWallMaterialHash(wallNode, shading, sceneMaterials)
|
|
118
292
|
: JSON.stringify({ textures, colorPreset, sceneTheme });
|
|
119
293
|
const existing = wallMaterialCache.get(cacheKey);
|
|
120
294
|
if (existing && existing.materialHash === materialHash) {
|
|
@@ -123,50 +297,85 @@ export function getMaterialsForWall(wallNode, shading = 'rendered', textures = t
|
|
|
123
297
|
if (existing) {
|
|
124
298
|
disposeOwnedMaterials([
|
|
125
299
|
existing.invisible,
|
|
300
|
+
existing.translucent,
|
|
126
301
|
existing.deleteVisible,
|
|
127
302
|
existing.deleteInvisible,
|
|
128
|
-
existing.
|
|
129
|
-
existing.highlightedInvisible,
|
|
303
|
+
existing.deleteTranslucent,
|
|
130
304
|
]);
|
|
131
305
|
}
|
|
132
|
-
const interiorSpec = getEffectiveWallSurfaceMaterial(wallNode, 'interior');
|
|
133
|
-
const exteriorSpec = getEffectiveWallSurfaceMaterial(wallNode, 'exterior');
|
|
134
306
|
const wallRoleMaterial = createSurfaceRoleMaterial('wall', colorPreset, undefined, sceneTheme);
|
|
135
|
-
//
|
|
136
|
-
//
|
|
307
|
+
// Colored mode: each face resolves slot-first (node.slots ref → legacy inline
|
|
308
|
+
// fields → declared slot default, parity with the retired DEFAULT_WALL_MATERIAL).
|
|
309
|
+
// Textures-off collapses every face to the themed wall role (the guaranteed
|
|
310
|
+
// escape hatch). The edge/cap slot (index 0) stays role-based.
|
|
137
311
|
const visible = textures
|
|
138
312
|
? [
|
|
139
313
|
wallRoleMaterial,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
314
|
+
resolveWallFaceMaterial(wallNode, 'interior', shading, sceneMaterials),
|
|
315
|
+
resolveWallFaceMaterial(wallNode, 'exterior', shading, sceneMaterials),
|
|
316
|
+
resolveWallSlotMaterial(wallNode, 'lowerInterior', shading, sceneMaterials),
|
|
317
|
+
resolveWallSlotMaterial(wallNode, 'middleInterior', shading, sceneMaterials),
|
|
318
|
+
resolveWallSlotMaterial(wallNode, 'upperInterior', shading, sceneMaterials),
|
|
319
|
+
resolveWallSlotMaterial(wallNode, 'topInterior', shading, sceneMaterials),
|
|
320
|
+
resolveWallSlotMaterial(wallNode, 'lowerExterior', shading, sceneMaterials),
|
|
321
|
+
resolveWallSlotMaterial(wallNode, 'middleExterior', shading, sceneMaterials),
|
|
322
|
+
resolveWallSlotMaterial(wallNode, 'upperExterior', shading, sceneMaterials),
|
|
323
|
+
resolveWallSlotMaterial(wallNode, 'topExterior', shading, sceneMaterials),
|
|
146
324
|
]
|
|
147
|
-
:
|
|
325
|
+
: Array.from({ length: 11 }, () => wallRoleMaterial);
|
|
148
326
|
const wallRoleColor = resolveSurfaceColor('wall', colorPreset, sceneTheme);
|
|
149
327
|
const invisible = [
|
|
150
328
|
createInvisibleWallMaterial(wallRoleColor, textures ? shading : 'solid'),
|
|
151
|
-
|
|
152
|
-
|
|
329
|
+
...['interior', 'exterior'].map((side) => createInvisibleWallMaterial(textures
|
|
330
|
+
? resolveWallFaceColor(wallNode, side, sceneMaterials, wallRoleColor)
|
|
331
|
+
: wallRoleColor, textures ? shading : 'solid')),
|
|
332
|
+
...[
|
|
333
|
+
'lowerInterior',
|
|
334
|
+
'middleInterior',
|
|
335
|
+
'upperInterior',
|
|
336
|
+
'topInterior',
|
|
337
|
+
'lowerExterior',
|
|
338
|
+
'middleExterior',
|
|
339
|
+
'upperExterior',
|
|
340
|
+
'topExterior',
|
|
341
|
+
].map((slotId) => createInvisibleWallMaterial(textures
|
|
342
|
+
? resolveWallSlotColor(wallNode, slotId, sceneMaterials, wallRoleColor)
|
|
343
|
+
: wallRoleColor, textures ? shading : 'solid')),
|
|
344
|
+
];
|
|
345
|
+
const translucent = [
|
|
346
|
+
createTranslucentWallMaterial(wallRoleColor, textures ? shading : 'solid'),
|
|
347
|
+
...['interior', 'exterior'].map((side) => createTranslucentWallMaterial(textures
|
|
348
|
+
? resolveWallFaceColor(wallNode, side, sceneMaterials, wallRoleColor)
|
|
349
|
+
: wallRoleColor, textures ? shading : 'solid')),
|
|
350
|
+
...[
|
|
351
|
+
'lowerInterior',
|
|
352
|
+
'middleInterior',
|
|
353
|
+
'upperInterior',
|
|
354
|
+
'topInterior',
|
|
355
|
+
'lowerExterior',
|
|
356
|
+
'middleExterior',
|
|
357
|
+
'upperExterior',
|
|
358
|
+
'topExterior',
|
|
359
|
+
].map((slotId) => createTranslucentWallMaterial(textures
|
|
360
|
+
? resolveWallSlotColor(wallNode, slotId, sceneMaterials, wallRoleColor)
|
|
361
|
+
: wallRoleColor, textures ? shading : 'solid')),
|
|
153
362
|
];
|
|
154
|
-
const highlightedVisible = mapWallMaterialArray(visible, (material) => createHighlightedWallMaterial(material, 'selection'));
|
|
155
|
-
const highlightedInvisible = mapWallMaterialArray(invisible, (material) => createHighlightedWallMaterial(material, 'selection'));
|
|
156
363
|
const deleteVisible = mapWallMaterialArray(visible, (material) => createHighlightedWallMaterial(material, 'delete'));
|
|
157
364
|
const deleteInvisible = mapWallMaterialArray(invisible, (material) => createHighlightedWallMaterial(material, 'delete'));
|
|
365
|
+
const deleteTranslucent = mapWallMaterialArray(translucent, (material) => createHighlightedWallMaterial(material, 'delete'));
|
|
158
366
|
const result = {
|
|
159
367
|
visible,
|
|
160
368
|
invisible,
|
|
369
|
+
translucent,
|
|
161
370
|
deleteVisible,
|
|
162
371
|
deleteInvisible,
|
|
163
|
-
|
|
164
|
-
highlightedInvisible,
|
|
372
|
+
deleteTranslucent,
|
|
165
373
|
materialHash,
|
|
166
374
|
};
|
|
167
375
|
wallMaterialCache.set(cacheKey, result);
|
|
168
376
|
return result;
|
|
169
377
|
}
|
|
170
|
-
export function getVisibleWallMaterials(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme) {
|
|
171
|
-
return getMaterialsForWall(wallNode, shading, textures, colorPreset, sceneTheme)
|
|
378
|
+
export function getVisibleWallMaterials(wallNode, shading = 'rendered', textures = true, colorPreset = 'clay', sceneTheme, sceneMaterials) {
|
|
379
|
+
return getMaterialsForWall(wallNode, shading, textures, colorPreset, sceneTheme, sceneMaterials)
|
|
380
|
+
.visible;
|
|
172
381
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wall-opening-cutout.test.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-opening-cutout.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,59 @@
|
|
|
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 { calculateLevelMiters, DoorNode, sceneRegistry, WallNode } from '@pascal-app/core';
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
import { generateExtrudedWall } from './wall-system';
|
|
7
|
+
describe('wall opening cutout', () => {
|
|
8
|
+
test('cuts a floor-level door directly from node geometry without a proxy mesh', () => {
|
|
9
|
+
const wall = WallNode.parse({
|
|
10
|
+
id: 'wall_floor-opening-cutout',
|
|
11
|
+
start: [0, 0],
|
|
12
|
+
end: [2, 0],
|
|
13
|
+
height: 2.5,
|
|
14
|
+
thickness: 0.1,
|
|
15
|
+
});
|
|
16
|
+
const door = DoorNode.parse({
|
|
17
|
+
id: 'door_floor-opening-cutout',
|
|
18
|
+
wallId: wall.id,
|
|
19
|
+
position: [1, 1.05, 0],
|
|
20
|
+
width: 0.9,
|
|
21
|
+
height: 2.1,
|
|
22
|
+
});
|
|
23
|
+
const wallMesh = new THREE.Mesh();
|
|
24
|
+
sceneRegistry.nodes.set(wall.id, wallMesh);
|
|
25
|
+
try {
|
|
26
|
+
const geometry = generateExtrudedWall(wall, [door], calculateLevelMiters([wall]));
|
|
27
|
+
const position = geometry.getAttribute('position');
|
|
28
|
+
const index = geometry.index;
|
|
29
|
+
const openingLeft = door.position[0] - door.width / 2;
|
|
30
|
+
const openingRight = door.position[0] + door.width / 2;
|
|
31
|
+
let wallFaceTrianglesInsideOpening = 0;
|
|
32
|
+
let baseTrianglesInsideOpening = 0;
|
|
33
|
+
for (let offset = 0; offset < (index?.count ?? position.count); offset += 3) {
|
|
34
|
+
const indices = [0, 1, 2].map((corner) => index ? index.getX(offset + corner) : offset + corner);
|
|
35
|
+
const vertices = indices.map((vertexIndex) => new THREE.Vector3(position.getX(vertexIndex), position.getY(vertexIndex), position.getZ(vertexIndex)));
|
|
36
|
+
const centroid = vertices
|
|
37
|
+
.reduce((sum, vertex) => sum.add(vertex), new THREE.Vector3())
|
|
38
|
+
.multiplyScalar(1 / 3);
|
|
39
|
+
const insideOpeningX = centroid.x > openingLeft + 1e-4 && centroid.x < openingRight - 1e-4;
|
|
40
|
+
if (!insideOpeningX)
|
|
41
|
+
continue;
|
|
42
|
+
const onWallFace = vertices.every((vertex) => Math.abs(Math.abs(vertex.z) - (wall.thickness ?? 0.1) / 2) < 1e-5);
|
|
43
|
+
if (onWallFace && centroid.y > 1e-4 && centroid.y < door.height - 1e-4) {
|
|
44
|
+
wallFaceTrianglesInsideOpening += 1;
|
|
45
|
+
}
|
|
46
|
+
if (vertices.every((vertex) => Math.abs(vertex.y) < 1e-5)) {
|
|
47
|
+
baseTrianglesInsideOpening += 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
expect(wallFaceTrianglesInsideOpening).toBe(0);
|
|
51
|
+
expect(baseTrianglesInsideOpening).toBe(0);
|
|
52
|
+
geometry.dispose();
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
sceneRegistry.nodes.delete(wall.id);
|
|
56
|
+
wallMesh.geometry.dispose();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wall-support-extension.test.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-support-extension.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,116 @@
|
|
|
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 { calculateLevelMiters, getWallPlaneTop, WallNode, } from '@pascal-app/core';
|
|
5
|
+
import { generateExtrudedWall } from './wall-system';
|
|
6
|
+
describe('wall support extension', () => {
|
|
7
|
+
test('preserves the raised wall origin and top while filling down to the lower support', () => {
|
|
8
|
+
const wall = WallNode.parse({ start: [0, 0], end: [4, 0], height: 2.5, thickness: 0.1 });
|
|
9
|
+
const geometry = generateExtrudedWall(wall, [], calculateLevelMiters([wall]), 0.6, 0.05);
|
|
10
|
+
geometry.computeBoundingBox();
|
|
11
|
+
expect(geometry.boundingBox?.min.y).toBeCloseTo(-0.55);
|
|
12
|
+
expect(geometry.boundingBox?.max.y).toBeCloseTo(2.5);
|
|
13
|
+
// With mesh.position.y = 0.6, the wall spans world Y=0.05..3.1.
|
|
14
|
+
expect((geometry.boundingBox?.min.y ?? 0) + 0.6).toBeCloseTo(0.05);
|
|
15
|
+
expect((geometry.boundingBox?.max.y ?? 0) + 0.6).toBeCloseTo(3.1);
|
|
16
|
+
geometry.dispose();
|
|
17
|
+
});
|
|
18
|
+
test('retains the existing negative-slab top constraint', () => {
|
|
19
|
+
const wall = WallNode.parse({ start: [0, 0], end: [4, 0], height: 2.5, thickness: 0.1 });
|
|
20
|
+
const geometry = generateExtrudedWall(wall, [], calculateLevelMiters([wall]), -0.4, -0.4);
|
|
21
|
+
geometry.computeBoundingBox();
|
|
22
|
+
expect(geometry.boundingBox?.min.y).toBeCloseTo(0);
|
|
23
|
+
expect(geometry.boundingBox?.max.y).toBeCloseTo(2.9);
|
|
24
|
+
expect((geometry.boundingBox?.max.y ?? 0) - 0.4).toBeCloseTo(2.5);
|
|
25
|
+
geometry.dispose();
|
|
26
|
+
});
|
|
27
|
+
test('plane-bound wall tops out at the storey plane regardless of slab elevation', () => {
|
|
28
|
+
const wall = WallNode.parse({ start: [0, 0], end: [4, 0], thickness: 0.1 });
|
|
29
|
+
const flat = generateExtrudedWall(wall, [], calculateLevelMiters([wall]), 0, 0, undefined, 3);
|
|
30
|
+
flat.computeBoundingBox();
|
|
31
|
+
expect(flat.boundingBox?.max.y).toBeCloseTo(3);
|
|
32
|
+
expect(flat.boundingBox?.min.y).toBeCloseTo(0);
|
|
33
|
+
flat.dispose();
|
|
34
|
+
const raised = generateExtrudedWall(wall, [], calculateLevelMiters([wall]), 0.6, 0.6, undefined, 3);
|
|
35
|
+
raised.computeBoundingBox();
|
|
36
|
+
// Mesh sits at Y=0.6, so a 2.4 local top keeps the world top at the 3m
|
|
37
|
+
// plane — the raised slab shortens the wall instead of lifting its top.
|
|
38
|
+
expect(raised.boundingBox?.max.y).toBeCloseTo(2.4);
|
|
39
|
+
expect(raised.boundingBox?.min.y).toBeCloseTo(0);
|
|
40
|
+
raised.dispose();
|
|
41
|
+
});
|
|
42
|
+
test('plane-bound wall under a flush thick deck tops out at the deck underside', () => {
|
|
43
|
+
// level_1 carries a flush deck occupying [-0.3, 0] above the storey
|
|
44
|
+
// plane: the covering-clamped plane for level_0 is 2.5 − 0.3 = 2.2.
|
|
45
|
+
const wall = WallNode.parse({
|
|
46
|
+
start: [0.5, 2],
|
|
47
|
+
end: [3.5, 2],
|
|
48
|
+
thickness: 0.1,
|
|
49
|
+
parentId: 'level_0',
|
|
50
|
+
});
|
|
51
|
+
const base = { object: 'node', parentId: null, visible: true, metadata: {}, children: [] };
|
|
52
|
+
const nodes = {
|
|
53
|
+
level_0: {
|
|
54
|
+
...base,
|
|
55
|
+
id: 'level_0',
|
|
56
|
+
type: 'level',
|
|
57
|
+
level: 0,
|
|
58
|
+
height: 2.5,
|
|
59
|
+
children: [wall.id],
|
|
60
|
+
},
|
|
61
|
+
level_1: {
|
|
62
|
+
...base,
|
|
63
|
+
id: 'level_1',
|
|
64
|
+
type: 'level',
|
|
65
|
+
level: 1,
|
|
66
|
+
height: 2.5,
|
|
67
|
+
children: ['slab_deck'],
|
|
68
|
+
},
|
|
69
|
+
slab_deck: {
|
|
70
|
+
...base,
|
|
71
|
+
id: 'slab_deck',
|
|
72
|
+
type: 'slab',
|
|
73
|
+
parentId: 'level_1',
|
|
74
|
+
polygon: [
|
|
75
|
+
[0, 0],
|
|
76
|
+
[4, 0],
|
|
77
|
+
[4, 4],
|
|
78
|
+
[0, 4],
|
|
79
|
+
],
|
|
80
|
+
holes: [],
|
|
81
|
+
elevation: 0,
|
|
82
|
+
thickness: 0.3,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
const planeTop = getWallPlaneTop(wall, 'level_0', nodes);
|
|
86
|
+
expect(planeTop).toBeCloseTo(2.2);
|
|
87
|
+
const geometry = generateExtrudedWall(wall, [], calculateLevelMiters([wall]), 0, 0, undefined, planeTop);
|
|
88
|
+
geometry.computeBoundingBox();
|
|
89
|
+
// Mesh sits at Y=0, so the world top lands at the deck underside instead
|
|
90
|
+
// of colliding with the slab solid above.
|
|
91
|
+
expect(geometry.boundingBox?.max.y).toBeCloseTo(2.2);
|
|
92
|
+
expect(geometry.boundingBox?.min.y).toBeCloseTo(0);
|
|
93
|
+
geometry.dispose();
|
|
94
|
+
});
|
|
95
|
+
test('raises only the high-supported part of a mixed wall run', () => {
|
|
96
|
+
const wall = WallNode.parse({ start: [0, 0], end: [4, 0], height: 2.5, thickness: 0.1 });
|
|
97
|
+
const geometry = generateExtrudedWall(wall, [], calculateLevelMiters([wall]), 0.6, 0.05, [
|
|
98
|
+
{ start: 0, end: 0.5, elevation: 0.6 },
|
|
99
|
+
{ start: 0.5, end: 1, elevation: 0.05 },
|
|
100
|
+
]);
|
|
101
|
+
const position = geometry.getAttribute('position');
|
|
102
|
+
let highSpanMinY = Number.POSITIVE_INFINITY;
|
|
103
|
+
let lowSpanMinY = Number.POSITIVE_INFINITY;
|
|
104
|
+
for (let index = 0; index < position.count; index++) {
|
|
105
|
+
const x = position.getX(index);
|
|
106
|
+
const y = position.getY(index);
|
|
107
|
+
if (x < 1.9)
|
|
108
|
+
highSpanMinY = Math.min(highSpanMinY, y);
|
|
109
|
+
if (x > 2.1)
|
|
110
|
+
lowSpanMinY = Math.min(lowSpanMinY, y);
|
|
111
|
+
}
|
|
112
|
+
expect(highSpanMinY).toBeCloseTo(0);
|
|
113
|
+
expect(lowSpanMinY).toBeCloseTo(-0.55);
|
|
114
|
+
geometry.dispose();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import { type AnyNode, type WallMiterData, type WallNode } from '@pascal-app/core';
|
|
1
|
+
import { type AnyNode, type WallMiterData, type WallNode, type WallSlabSupportSegment } from '@pascal-app/core';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
export declare const WallSystem: () => null;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* Key insight from demo: polygon is built in WORLD coordinates first,
|
|
8
|
-
* then we transform to wall-local for the 3D mesh.
|
|
9
|
-
*/
|
|
10
|
-
export declare function generateExtrudedWall(wallNode: WallNode, childrenNodes: AnyNode[], miterData: WallMiterData, slabElevation?: number): THREE.BufferGeometry;
|
|
4
|
+
type WallTerrainBottomSampler = (x: number, z: number) => number | null;
|
|
5
|
+
export declare function generateExtrudedWall(wallNode: WallNode, childrenNodes: AnyNode[], miterData: WallMiterData, slabElevation?: number, baseElevation?: number, baseSegments?: readonly WallSlabSupportSegment[], storeyHeight?: number, terrainBottomAt?: WallTerrainBottomSampler): THREE.BufferGeometry;
|
|
6
|
+
export {};
|
|
11
7
|
//# sourceMappingURL=wall-system.d.ts.map
|
|
@@ -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,EA2BZ,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,sBAAsB,EAI5B,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAqd9B,eAAO,MAAM,UAAU,YAwJtB,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"}
|