@pascal-app/viewer 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/error-boundary.d.ts +7 -3
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary.js +3 -1
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -1
- package/dist/components/renderers/ceiling/ceiling-renderer.js +19 -3
- package/dist/components/renderers/column/column-renderer.d.ts +5 -0
- package/dist/components/renderers/column/column-renderer.d.ts.map +1 -0
- package/dist/components/renderers/column/column-renderer.js +594 -0
- package/dist/components/renderers/door/door-renderer.d.ts.map +1 -1
- package/dist/components/renderers/door/door-renderer.js +4 -9
- package/dist/components/renderers/fence/fence-renderer.d.ts.map +1 -1
- package/dist/components/renderers/guide/guide-renderer.js +1 -1
- package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
- package/dist/components/renderers/item/item-renderer.js +2 -1
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +3 -1
- package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
- package/dist/components/renderers/roof/roof-renderer.js +1 -13
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -1
- package/dist/components/renderers/roof-segment/roof-segment-renderer.js +5 -20
- package/dist/components/renderers/slab/slab-renderer.d.ts.map +1 -1
- package/dist/components/renderers/slab/slab-renderer.js +39 -27
- package/dist/components/renderers/spawn/spawn-renderer.d.ts +5 -0
- package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +1 -0
- package/dist/components/renderers/spawn/spawn-renderer.js +32 -0
- package/dist/components/renderers/stair/stair-renderer.d.ts.map +1 -1
- package/dist/components/renderers/stair/stair-renderer.js +5 -23
- package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +1 -1
- package/dist/components/renderers/stair-segment/stair-segment-renderer.js +5 -22
- package/dist/components/viewer/index.d.ts +2 -0
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +88 -18
- package/dist/components/viewer/post-processing.d.ts +13 -1
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +71 -14
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +19 -2
- package/dist/hooks/use-node-events.d.ts +9 -1
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/lib/materials.d.ts +3 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +14 -0
- package/dist/lib/merged-outline-node.d.ts +2 -0
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +25 -5
- package/dist/store/use-viewer.d.ts +4 -4
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +2 -2
- package/dist/systems/ceiling/ceiling-system.d.ts +8 -0
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -0
- package/dist/systems/ceiling/ceiling-system.js +91 -0
- package/dist/systems/column/column-geometry.d.ts +22 -0
- package/dist/systems/column/column-geometry.d.ts.map +1 -0
- package/dist/systems/column/column-geometry.js +108 -0
- package/dist/systems/door/door-animation-system.d.ts +2 -0
- package/dist/systems/door/door-animation-system.d.ts.map +1 -0
- package/dist/systems/door/door-animation-system.js +53 -0
- package/dist/systems/door/door-system.d.ts +2 -0
- package/dist/systems/door/door-system.d.ts.map +1 -0
- package/dist/systems/door/door-system.js +867 -0
- package/dist/systems/fence/fence-system.d.ts +2 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -0
- package/dist/systems/fence/fence-system.js +185 -0
- package/dist/systems/item/item-system.d.ts +2 -0
- package/dist/systems/item/item-system.d.ts.map +1 -0
- package/dist/systems/item/item-system.js +44 -0
- package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts +14 -0
- package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts.map +1 -0
- package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.js +155 -0
- package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts +6 -0
- package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts.map +1 -0
- package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.js +75 -0
- package/dist/systems/item-mesh-metadata/sync-request.d.ts +7 -0
- package/dist/systems/item-mesh-metadata/sync-request.d.ts.map +1 -0
- package/dist/systems/item-mesh-metadata/sync-request.js +25 -0
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +18 -1
- package/dist/systems/roof/roof-system.d.ts +16 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -0
- package/dist/systems/roof/roof-system.js +797 -0
- package/dist/systems/slab/slab-system.d.ts +8 -0
- package/dist/systems/slab/slab-system.d.ts.map +1 -0
- package/dist/systems/slab/slab-system.js +155 -0
- package/dist/systems/stair/stair-materials.d.ts.map +1 -1
- package/dist/systems/stair/stair-materials.js +25 -2
- package/dist/systems/stair/stair-system.d.ts +2 -0
- package/dist/systems/stair/stair-system.d.ts.map +1 -0
- package/dist/systems/stair/stair-system.js +772 -0
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +2 -2
- package/dist/systems/wall/wall-system.d.ts +11 -0
- package/dist/systems/wall/wall-system.d.ts.map +1 -0
- package/dist/systems/wall/wall-system.js +592 -0
- package/dist/systems/window/window-animation-system.d.ts +2 -0
- package/dist/systems/window/window-animation-system.d.ts.map +1 -0
- package/dist/systems/window/window-animation-system.js +135 -0
- package/dist/systems/window/window-system.d.ts +12 -0
- package/dist/systems/window/window-system.d.ts.map +1 -0
- package/dist/systems/window/window-system.js +1430 -0
- package/package.json +4 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fence-system.d.ts","sourceRoot":"","sources":["../../../src/systems/fence/fence-system.tsx"],"names":[],"mappings":"AA6QA,eAAO,MAAM,WAAW,YAiBvB,CAAA"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { useFrame } from '@react-three/fiber';
|
|
2
|
+
import { getWallCurveFrameAt, getWallCurveLength, sceneRegistry, useScene, } from '@pascal-app/core';
|
|
3
|
+
import * as THREE from 'three';
|
|
4
|
+
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
5
|
+
const MIN_CURVE_SEGMENT_LENGTH = 0.18;
|
|
6
|
+
function createFencePartGeometry(part) {
|
|
7
|
+
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
|
8
|
+
geometry.scale(part.scale[0], part.scale[1], part.scale[2]);
|
|
9
|
+
if (part.rotationY) {
|
|
10
|
+
geometry.rotateY(part.rotationY);
|
|
11
|
+
}
|
|
12
|
+
geometry.translate(part.position[0], part.position[1], part.position[2]);
|
|
13
|
+
applyFenceUVs(geometry);
|
|
14
|
+
return geometry;
|
|
15
|
+
}
|
|
16
|
+
function getFencePointAt(fence, t) {
|
|
17
|
+
const frame = getWallCurveFrameAt(fence, t);
|
|
18
|
+
return {
|
|
19
|
+
point: frame.point,
|
|
20
|
+
tangentAngle: Math.atan2(frame.tangent.y, frame.tangent.x),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function createStraightFenceSpanPart(start, end, centerY, height, depth) {
|
|
24
|
+
const dx = end[0] - start[0];
|
|
25
|
+
const dz = end[1] - start[1];
|
|
26
|
+
const length = Math.hypot(dx, dz);
|
|
27
|
+
if (length <= 1e-4) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
position: [(start[0] + end[0]) / 2, centerY, (start[1] + end[1]) / 2],
|
|
32
|
+
rotationY: -Math.atan2(dz, dx),
|
|
33
|
+
scale: [length, height, depth],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function createFenceCurveSpanParts(fence, startT, endT, centerY, height, depth) {
|
|
37
|
+
const parts = [];
|
|
38
|
+
const frameCount = Math.max(1, Math.ceil((getWallCurveLength(fence) * Math.max(1e-4, endT - startT)) / MIN_CURVE_SEGMENT_LENGTH));
|
|
39
|
+
let previous = getFencePointAt(fence, startT);
|
|
40
|
+
for (let index = 1; index <= frameCount; index += 1) {
|
|
41
|
+
const t = startT + (endT - startT) * (index / frameCount);
|
|
42
|
+
const current = getFencePointAt(fence, t);
|
|
43
|
+
const segment = createStraightFenceSpanPart([previous.point.x, previous.point.y], [current.point.x, current.point.y], centerY, height, depth);
|
|
44
|
+
if (segment) {
|
|
45
|
+
parts.push(segment);
|
|
46
|
+
}
|
|
47
|
+
previous = current;
|
|
48
|
+
}
|
|
49
|
+
return parts;
|
|
50
|
+
}
|
|
51
|
+
function applyFenceUVs(geometry) {
|
|
52
|
+
const position = geometry.getAttribute('position');
|
|
53
|
+
const normal = geometry.getAttribute('normal');
|
|
54
|
+
if (!(position && normal))
|
|
55
|
+
return;
|
|
56
|
+
const uvs = new Float32Array(position.count * 2);
|
|
57
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
58
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
59
|
+
let minZ = Number.POSITIVE_INFINITY;
|
|
60
|
+
for (let index = 0; index < position.count; index += 1) {
|
|
61
|
+
minX = Math.min(minX, position.getX(index));
|
|
62
|
+
minY = Math.min(minY, position.getY(index));
|
|
63
|
+
minZ = Math.min(minZ, position.getZ(index));
|
|
64
|
+
}
|
|
65
|
+
for (let index = 0; index < position.count; index += 1) {
|
|
66
|
+
const px = position.getX(index);
|
|
67
|
+
const py = position.getY(index);
|
|
68
|
+
const pz = position.getZ(index);
|
|
69
|
+
const nx = Math.abs(normal.getX(index));
|
|
70
|
+
const ny = Math.abs(normal.getY(index));
|
|
71
|
+
const nz = Math.abs(normal.getZ(index));
|
|
72
|
+
let u = 0;
|
|
73
|
+
let v = 0;
|
|
74
|
+
if (ny >= nx && ny >= nz) {
|
|
75
|
+
u = px - minX;
|
|
76
|
+
v = pz - minZ;
|
|
77
|
+
}
|
|
78
|
+
else if (nx >= nz) {
|
|
79
|
+
u = pz - minZ;
|
|
80
|
+
v = py - minY;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
u = px - minX;
|
|
84
|
+
v = py - minY;
|
|
85
|
+
}
|
|
86
|
+
uvs[index * 2] = u;
|
|
87
|
+
uvs[index * 2 + 1] = v;
|
|
88
|
+
}
|
|
89
|
+
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
|
|
90
|
+
geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(uvs.slice(), 2));
|
|
91
|
+
}
|
|
92
|
+
function getStyleDefaults(style) {
|
|
93
|
+
if (style === 'privacy') {
|
|
94
|
+
return { spacingFactor: 0.42, postFactor: 1.35, baseFactor: 1.2, topFactor: 1.2 };
|
|
95
|
+
}
|
|
96
|
+
if (style === 'rail') {
|
|
97
|
+
return { spacingFactor: 0.68, postFactor: 0.8, baseFactor: 0.85, topFactor: 0.85 };
|
|
98
|
+
}
|
|
99
|
+
return { spacingFactor: 0.3, postFactor: 0.55, baseFactor: 1, topFactor: 0.75 };
|
|
100
|
+
}
|
|
101
|
+
function createFenceParts(fence) {
|
|
102
|
+
const parts = [];
|
|
103
|
+
const length = Math.max(getWallCurveLength(fence), 0.01);
|
|
104
|
+
const panelDepth = Math.max(fence.thickness, 0.03);
|
|
105
|
+
const clearance = Math.max(fence.groundClearance, 0);
|
|
106
|
+
const styleDefaults = getStyleDefaults(fence.style);
|
|
107
|
+
const baseHeight = Math.max(fence.baseHeight * styleDefaults.baseFactor, 0.04);
|
|
108
|
+
const topRailHeight = Math.max(fence.topRailHeight * styleDefaults.topFactor, 0.01);
|
|
109
|
+
const verticalHeight = Math.max(fence.height - baseHeight - topRailHeight, 0.08);
|
|
110
|
+
const postWidth = Math.max(fence.postSize * styleDefaults.postFactor, 0.01);
|
|
111
|
+
const spacing = Math.max(fence.postSpacing * styleDefaults.spacingFactor, postWidth * 1.2);
|
|
112
|
+
const edgeInset = Math.max(fence.edgeInset ?? 0.015, 0.005);
|
|
113
|
+
const isFloating = fence.baseStyle === 'floating';
|
|
114
|
+
const baseY = isFloating ? clearance : 0;
|
|
115
|
+
const effectiveBaseHeight = baseHeight;
|
|
116
|
+
const startInsetT = Math.min(0.499, edgeInset / length);
|
|
117
|
+
const endInsetT = Math.max(0.501, 1 - edgeInset / length);
|
|
118
|
+
if (!isFloating) {
|
|
119
|
+
parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight / 2, effectiveBaseHeight, panelDepth * 1.05));
|
|
120
|
+
parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + verticalHeight * 0.15, topRailHeight * 0.8, panelDepth * 0.35));
|
|
121
|
+
}
|
|
122
|
+
const count = Math.max(2, Math.floor((length - edgeInset * 2) / spacing) + 1);
|
|
123
|
+
const verticalY = baseY + effectiveBaseHeight + verticalHeight / 2;
|
|
124
|
+
for (let index = 0; index < count; index += 1) {
|
|
125
|
+
const t = count === 1 ? 0.5 : startInsetT + (endInsetT - startInsetT) * (index / (count - 1));
|
|
126
|
+
const frame = getFencePointAt(fence, t);
|
|
127
|
+
const isEdgePost = index === 0 || index === count - 1;
|
|
128
|
+
const postHeight = isFloating && isEdgePost
|
|
129
|
+
? effectiveBaseHeight + verticalHeight + topRailHeight + clearance
|
|
130
|
+
: verticalHeight;
|
|
131
|
+
const postY = isFloating && isEdgePost ? postHeight / 2 : verticalY;
|
|
132
|
+
parts.push({
|
|
133
|
+
position: [frame.point.x, postY, frame.point.y],
|
|
134
|
+
rotationY: -frame.tangentAngle,
|
|
135
|
+
scale: [postWidth, postHeight, Math.max(panelDepth * 0.35, 0.012)],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + verticalHeight + topRailHeight / 2, topRailHeight, Math.max(panelDepth * 0.55, 0.018)));
|
|
139
|
+
if (isFloating) {
|
|
140
|
+
parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + topRailHeight / 2, topRailHeight, Math.max(panelDepth * 0.55, 0.018)));
|
|
141
|
+
}
|
|
142
|
+
return parts;
|
|
143
|
+
}
|
|
144
|
+
function generateFenceGeometry(fence) {
|
|
145
|
+
const parts = createFenceParts(fence);
|
|
146
|
+
const geometries = parts.map(createFencePartGeometry);
|
|
147
|
+
const merged = mergeGeometries(geometries, false) ?? new THREE.BufferGeometry();
|
|
148
|
+
geometries.forEach((geometry) => geometry.dispose());
|
|
149
|
+
const mergedUv = merged.getAttribute('uv');
|
|
150
|
+
if (mergedUv) {
|
|
151
|
+
merged.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(mergedUv.array), 2));
|
|
152
|
+
}
|
|
153
|
+
merged.computeVertexNormals();
|
|
154
|
+
return merged;
|
|
155
|
+
}
|
|
156
|
+
function updateFenceGeometry(fenceId) {
|
|
157
|
+
const node = useScene.getState().nodes[fenceId];
|
|
158
|
+
if (!node || node.type !== 'fence')
|
|
159
|
+
return;
|
|
160
|
+
const mesh = sceneRegistry.nodes.get(fenceId);
|
|
161
|
+
if (!mesh)
|
|
162
|
+
return;
|
|
163
|
+
const newGeometry = generateFenceGeometry(node);
|
|
164
|
+
mesh.geometry.dispose();
|
|
165
|
+
mesh.geometry = newGeometry;
|
|
166
|
+
mesh.position.set(0, 0, 0);
|
|
167
|
+
mesh.rotation.set(0, 0, 0);
|
|
168
|
+
}
|
|
169
|
+
export const FenceSystem = () => {
|
|
170
|
+
const dirtyNodes = useScene((state) => state.dirtyNodes);
|
|
171
|
+
const clearDirty = useScene((state) => state.clearDirty);
|
|
172
|
+
useFrame(() => {
|
|
173
|
+
if (dirtyNodes.size === 0)
|
|
174
|
+
return;
|
|
175
|
+
const nodes = useScene.getState().nodes;
|
|
176
|
+
dirtyNodes.forEach((id) => {
|
|
177
|
+
const node = nodes[id];
|
|
178
|
+
if (!node || node.type !== 'fence')
|
|
179
|
+
return;
|
|
180
|
+
updateFenceGeometry(id);
|
|
181
|
+
clearDirty(id);
|
|
182
|
+
});
|
|
183
|
+
}, 4);
|
|
184
|
+
return null;
|
|
185
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"item-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item/item-system.tsx"],"names":[],"mappings":"AAiBA,eAAO,MAAM,UAAU,YA6CtB,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useFrame } from '@react-three/fiber';
|
|
2
|
+
import { getScaledDimensions, resolveLevelId, sceneRegistry, spatialGridManager, useScene, } from '@pascal-app/core';
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// ITEM SYSTEM
|
|
5
|
+
// ============================================================================
|
|
6
|
+
export const ItemSystem = () => {
|
|
7
|
+
const dirtyNodes = useScene((state) => state.dirtyNodes);
|
|
8
|
+
const clearDirty = useScene((state) => state.clearDirty);
|
|
9
|
+
useFrame(() => {
|
|
10
|
+
if (dirtyNodes.size === 0)
|
|
11
|
+
return;
|
|
12
|
+
const nodes = useScene.getState().nodes;
|
|
13
|
+
dirtyNodes.forEach((id) => {
|
|
14
|
+
const node = nodes[id];
|
|
15
|
+
if (!node || node.type !== 'item')
|
|
16
|
+
return;
|
|
17
|
+
const item = node;
|
|
18
|
+
const mesh = sceneRegistry.nodes.get(id);
|
|
19
|
+
if (!mesh)
|
|
20
|
+
return;
|
|
21
|
+
if (item.asset.attachTo === 'wall-side') {
|
|
22
|
+
// Wall-attached item: offset Z by half the parent wall's thickness
|
|
23
|
+
const parentWall = item.parentId ? nodes[item.parentId] : undefined;
|
|
24
|
+
if (parentWall && parentWall.type === 'wall') {
|
|
25
|
+
const wallThickness = parentWall.thickness ?? 0.1;
|
|
26
|
+
const side = item.side === 'front' ? 1 : -1;
|
|
27
|
+
mesh.position.z = (wallThickness / 2) * side;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else if (!item.asset.attachTo) {
|
|
31
|
+
// If parented to another item (surface placement), R3F handles positioning via the hierarchy
|
|
32
|
+
const parentNode = item.parentId ? nodes[item.parentId] : undefined;
|
|
33
|
+
if (parentNode?.type !== 'item') {
|
|
34
|
+
// Floor item: elevate by slab height (using full footprint overlap)
|
|
35
|
+
const levelId = resolveLevelId(item, nodes);
|
|
36
|
+
const slabElevation = spatialGridManager.getSlabElevationForItem(levelId, item.position, getScaledDimensions(item), item.rotation);
|
|
37
|
+
mesh.position.y = slabElevation + item.position[1];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
clearDirty(id);
|
|
41
|
+
});
|
|
42
|
+
}, 2);
|
|
43
|
+
return null;
|
|
44
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Object3D } from 'three';
|
|
2
|
+
type Point = {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
};
|
|
6
|
+
export type MeshLocalBounds = {
|
|
7
|
+
min: [number, number, number];
|
|
8
|
+
max: [number, number, number];
|
|
9
|
+
};
|
|
10
|
+
/** Plan footprint in the item root's horizontal (x, z) plane — stored as floorplan polygon. */
|
|
11
|
+
export declare function computePlanFootprintPolygonLocal(object: Object3D): Point[];
|
|
12
|
+
export declare function computeMeshLocalBoundsFromObject(object: Object3D): MeshLocalBounds | null;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=compute-item-mesh-metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute-item-mesh-metadata.d.ts","sourceRoot":"","sources":["../../../src/systems/item-mesh-metadata/compute-item-mesh-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAGrC,KAAK,KAAK,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAErC,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAC9B,CAAA;AAED,+FAA+F;AAC/F,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,QAAQ,GAAG,KAAK,EAAE,CA0E1E;AAED,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,QAAQ,GAAG,eAAe,GAAG,IAAI,CAmDzF"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Box3, Matrix4, Vector3 } from 'three';
|
|
2
|
+
/** Plan footprint in the item root's horizontal (x, z) plane — stored as floorplan polygon. */
|
|
3
|
+
export function computePlanFootprintPolygonLocal(object) {
|
|
4
|
+
object.updateWorldMatrix(true, true);
|
|
5
|
+
const inverseRootMatrix = new Matrix4().copy(object.matrixWorld).invert();
|
|
6
|
+
const localMatrix = new Matrix4();
|
|
7
|
+
const scratchBounds = new Box3();
|
|
8
|
+
const scratchPosition = new Vector3();
|
|
9
|
+
const footprintPoints = [];
|
|
10
|
+
const collectPoints = (child) => {
|
|
11
|
+
const mesh = child;
|
|
12
|
+
if (mesh.isMesh && mesh.name !== 'cutout' && mesh.geometry) {
|
|
13
|
+
if (!mesh.geometry.boundingBox && mesh.geometry.computeBoundingBox) {
|
|
14
|
+
mesh.geometry.computeBoundingBox();
|
|
15
|
+
}
|
|
16
|
+
localMatrix.copy(inverseRootMatrix).multiply(mesh.matrixWorld);
|
|
17
|
+
const vertexPositions = mesh.geometry.attributes?.position;
|
|
18
|
+
if (vertexPositions && vertexPositions.count > 0) {
|
|
19
|
+
for (let index = 0; index < vertexPositions.count; index += 1) {
|
|
20
|
+
scratchPosition
|
|
21
|
+
.set(vertexPositions.getX(index), vertexPositions.getY(index), vertexPositions.getZ(index))
|
|
22
|
+
.applyMatrix4(localMatrix);
|
|
23
|
+
if (Number.isFinite(scratchPosition.x) && Number.isFinite(scratchPosition.z)) {
|
|
24
|
+
footprintPoints.push({ x: scratchPosition.x, y: scratchPosition.z });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else if (mesh.geometry.boundingBox) {
|
|
29
|
+
scratchBounds.copy(mesh.geometry.boundingBox);
|
|
30
|
+
scratchBounds.applyMatrix4(localMatrix);
|
|
31
|
+
if (Number.isFinite(scratchBounds.min.x) && Number.isFinite(scratchBounds.max.x)) {
|
|
32
|
+
footprintPoints.push({ x: scratchBounds.min.x, y: scratchBounds.min.z }, { x: scratchBounds.max.x, y: scratchBounds.min.z }, { x: scratchBounds.max.x, y: scratchBounds.max.z }, { x: scratchBounds.min.x, y: scratchBounds.max.z });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const grandchild of child.children) {
|
|
37
|
+
collectPoints(grandchild);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
for (const child of object.children) {
|
|
41
|
+
collectPoints(child);
|
|
42
|
+
}
|
|
43
|
+
return getMinimumAreaBoundingRect(footprintPoints) ?? [];
|
|
44
|
+
}
|
|
45
|
+
export function computeMeshLocalBoundsFromObject(object) {
|
|
46
|
+
object.updateWorldMatrix(true, true);
|
|
47
|
+
const inverseRootMatrix = new Matrix4().copy(object.matrixWorld).invert();
|
|
48
|
+
const localMatrix = new Matrix4();
|
|
49
|
+
const localBounds = new Box3();
|
|
50
|
+
const scratchBounds = new Box3();
|
|
51
|
+
let hasBounds = false;
|
|
52
|
+
const expandBounds = (child) => {
|
|
53
|
+
const mesh = child;
|
|
54
|
+
if (mesh.isMesh && mesh.name !== 'cutout' && mesh.geometry) {
|
|
55
|
+
if (!mesh.geometry.boundingBox && mesh.geometry.computeBoundingBox) {
|
|
56
|
+
mesh.geometry.computeBoundingBox();
|
|
57
|
+
}
|
|
58
|
+
if (mesh.geometry.boundingBox) {
|
|
59
|
+
localMatrix.copy(inverseRootMatrix).multiply(mesh.matrixWorld);
|
|
60
|
+
scratchBounds.copy(mesh.geometry.boundingBox).applyMatrix4(localMatrix);
|
|
61
|
+
if (!hasBounds) {
|
|
62
|
+
localBounds.copy(scratchBounds);
|
|
63
|
+
hasBounds = true;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
localBounds.union(scratchBounds);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const grandchild of child.children) {
|
|
71
|
+
expandBounds(grandchild);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
for (const child of object.children) {
|
|
75
|
+
expandBounds(child);
|
|
76
|
+
}
|
|
77
|
+
if (!hasBounds)
|
|
78
|
+
return null;
|
|
79
|
+
return {
|
|
80
|
+
min: [localBounds.min.x, localBounds.min.y, localBounds.min.z],
|
|
81
|
+
max: [localBounds.max.x, localBounds.max.y, localBounds.max.z],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function getMinimumAreaBoundingRect(points) {
|
|
85
|
+
if (points.length === 0)
|
|
86
|
+
return null;
|
|
87
|
+
if (points.length < 3)
|
|
88
|
+
return points;
|
|
89
|
+
const hull = getConvexHull(points);
|
|
90
|
+
if (hull.length < 3)
|
|
91
|
+
return hull;
|
|
92
|
+
let bestArea = Number.POSITIVE_INFINITY;
|
|
93
|
+
let bestRect = null;
|
|
94
|
+
for (let index = 0; index < hull.length; index += 1) {
|
|
95
|
+
const nextIndex = (index + 1) % hull.length;
|
|
96
|
+
const current = hull[index];
|
|
97
|
+
const next = hull[nextIndex];
|
|
98
|
+
const angle = Math.atan2(next.y - current.y, next.x - current.x);
|
|
99
|
+
const cos = Math.cos(-angle);
|
|
100
|
+
const sin = Math.sin(-angle);
|
|
101
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
102
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
103
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
104
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
105
|
+
for (const point of hull) {
|
|
106
|
+
const rx = point.x * cos - point.y * sin;
|
|
107
|
+
const ry = point.x * sin + point.y * cos;
|
|
108
|
+
minX = Math.min(minX, rx);
|
|
109
|
+
maxX = Math.max(maxX, rx);
|
|
110
|
+
minY = Math.min(minY, ry);
|
|
111
|
+
maxY = Math.max(maxY, ry);
|
|
112
|
+
}
|
|
113
|
+
const area = (maxX - minX) * (maxY - minY);
|
|
114
|
+
if (area >= bestArea)
|
|
115
|
+
continue;
|
|
116
|
+
bestArea = area;
|
|
117
|
+
const unrotate = (x, y) => ({
|
|
118
|
+
x: x * Math.cos(angle) - y * Math.sin(angle),
|
|
119
|
+
y: x * Math.sin(angle) + y * Math.cos(angle),
|
|
120
|
+
});
|
|
121
|
+
bestRect = [
|
|
122
|
+
unrotate(minX, minY),
|
|
123
|
+
unrotate(maxX, minY),
|
|
124
|
+
unrotate(maxX, maxY),
|
|
125
|
+
unrotate(minX, maxY),
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
return bestRect;
|
|
129
|
+
}
|
|
130
|
+
function getConvexHull(points) {
|
|
131
|
+
if (points.length <= 1)
|
|
132
|
+
return points;
|
|
133
|
+
const sorted = [...points].sort((a, b) => (a.x === b.x ? a.y - b.y : a.x - b.x));
|
|
134
|
+
const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
|
135
|
+
const lower = [];
|
|
136
|
+
for (const point of sorted) {
|
|
137
|
+
while (lower.length >= 2 &&
|
|
138
|
+
cross(lower[lower.length - 2], lower[lower.length - 1], point) <= 0) {
|
|
139
|
+
lower.pop();
|
|
140
|
+
}
|
|
141
|
+
lower.push(point);
|
|
142
|
+
}
|
|
143
|
+
const upper = [];
|
|
144
|
+
for (let index = sorted.length - 1; index >= 0; index -= 1) {
|
|
145
|
+
const point = sorted[index];
|
|
146
|
+
while (upper.length >= 2 &&
|
|
147
|
+
cross(upper[upper.length - 2], upper[upper.length - 1], point) <= 0) {
|
|
148
|
+
upper.pop();
|
|
149
|
+
}
|
|
150
|
+
upper.push(point);
|
|
151
|
+
}
|
|
152
|
+
lower.pop();
|
|
153
|
+
upper.pop();
|
|
154
|
+
return [...lower, ...upper];
|
|
155
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writes `meshLocalPlanPolygon` / `meshLocalBounds` from loaded item meshes.
|
|
3
|
+
* ModelRenderer requests sync via `requestItemMeshMetadataSync` when GLTF is ready.
|
|
4
|
+
*/
|
|
5
|
+
export declare function ItemMeshMetadataSystem(): null;
|
|
6
|
+
//# sourceMappingURL=item-mesh-metadata-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"item-mesh-metadata-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item-mesh-metadata/item-mesh-metadata-system.tsx"],"names":[],"mappings":"AAoFA;;;GAGG;AACH,wBAAgB,sBAAsB,SAYrC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { sceneRegistry, useScene } from '@pascal-app/core';
|
|
3
|
+
import { useFrame } from '@react-three/fiber';
|
|
4
|
+
import { computeMeshLocalBoundsFromObject, computePlanFootprintPolygonLocal, } from './compute-item-mesh-metadata';
|
|
5
|
+
import { drainItemMeshMetadataSyncRequests, getItemMeshMetadataSourceRoot } from './sync-request';
|
|
6
|
+
function isMetadataUnchanged(nextPolygon, nextBounds, metadata) {
|
|
7
|
+
const currentPolygon = metadata.meshLocalPlanPolygon;
|
|
8
|
+
const currentBounds = typeof metadata.meshLocalBounds === 'object' &&
|
|
9
|
+
metadata.meshLocalBounds !== null &&
|
|
10
|
+
!Array.isArray(metadata.meshLocalBounds)
|
|
11
|
+
? metadata.meshLocalBounds
|
|
12
|
+
: null;
|
|
13
|
+
const polygonUnchanged = (nextPolygon === null &&
|
|
14
|
+
(currentPolygon === undefined || currentPolygon === null || currentPolygon === false)) ||
|
|
15
|
+
(Array.isArray(currentPolygon) &&
|
|
16
|
+
nextPolygon !== null &&
|
|
17
|
+
currentPolygon.length === nextPolygon.length &&
|
|
18
|
+
currentPolygon.every((point, index) => Array.isArray(point) &&
|
|
19
|
+
point[0] === nextPolygon[index]?.[0] &&
|
|
20
|
+
point[1] === nextPolygon[index]?.[1]));
|
|
21
|
+
const boundsUnchanged = (nextBounds === null && (currentBounds === undefined || currentBounds === null)) ||
|
|
22
|
+
(nextBounds !== null &&
|
|
23
|
+
Array.isArray(currentBounds?.min) &&
|
|
24
|
+
Array.isArray(currentBounds?.max) &&
|
|
25
|
+
currentBounds.min[0] === nextBounds.min[0] &&
|
|
26
|
+
currentBounds.min[1] === nextBounds.min[1] &&
|
|
27
|
+
currentBounds.min[2] === nextBounds.min[2] &&
|
|
28
|
+
currentBounds.max[0] === nextBounds.max[0] &&
|
|
29
|
+
currentBounds.max[1] === nextBounds.max[1] &&
|
|
30
|
+
currentBounds.max[2] === nextBounds.max[2]);
|
|
31
|
+
return polygonUnchanged && boundsUnchanged;
|
|
32
|
+
}
|
|
33
|
+
function trySyncItemMeshMetadata(itemId, nodes) {
|
|
34
|
+
const node = nodes[itemId];
|
|
35
|
+
if (!node || node.type !== 'item')
|
|
36
|
+
return;
|
|
37
|
+
const root = getItemMeshMetadataSourceRoot(itemId) ??
|
|
38
|
+
sceneRegistry.nodes.get(itemId);
|
|
39
|
+
if (!root)
|
|
40
|
+
return;
|
|
41
|
+
const polygon = computePlanFootprintPolygonLocal(root);
|
|
42
|
+
const bounds = computeMeshLocalBoundsFromObject(root);
|
|
43
|
+
if (polygon.length < 3 && !bounds)
|
|
44
|
+
return;
|
|
45
|
+
const nextPolygon = polygon.length >= 3 ? polygon.map(({ x, y }) => [x, y]) : null;
|
|
46
|
+
const nextBounds = bounds ? { min: bounds.min, max: bounds.max } : null;
|
|
47
|
+
const metadata = typeof node.metadata === 'object' && node.metadata !== null && !Array.isArray(node.metadata)
|
|
48
|
+
? node.metadata
|
|
49
|
+
: {};
|
|
50
|
+
if (isMetadataUnchanged(nextPolygon, nextBounds, metadata))
|
|
51
|
+
return;
|
|
52
|
+
useScene.getState().updateNode(itemId, {
|
|
53
|
+
metadata: {
|
|
54
|
+
...metadata,
|
|
55
|
+
...(nextPolygon ? { meshLocalPlanPolygon: nextPolygon } : {}),
|
|
56
|
+
...(nextBounds ? { meshLocalBounds: nextBounds } : {}),
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Writes `meshLocalPlanPolygon` / `meshLocalBounds` from loaded item meshes.
|
|
62
|
+
* ModelRenderer requests sync via `requestItemMeshMetadataSync` when GLTF is ready.
|
|
63
|
+
*/
|
|
64
|
+
export function ItemMeshMetadataSystem() {
|
|
65
|
+
useFrame(() => {
|
|
66
|
+
const ids = drainItemMeshMetadataSyncRequests();
|
|
67
|
+
if (ids.length === 0)
|
|
68
|
+
return;
|
|
69
|
+
const nodes = useScene.getState().nodes;
|
|
70
|
+
for (const id of ids) {
|
|
71
|
+
trySyncItemMeshMetadata(id, nodes);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Object3D } from 'three';
|
|
2
|
+
/** Called when an item's loaded GLTF (or metadata driving footprint) may need re-syncing. */
|
|
3
|
+
export declare function requestItemMeshMetadataSync(itemId: string): void;
|
|
4
|
+
export declare function setItemMeshMetadataSourceRoot(itemId: string, root: Object3D | null): void;
|
|
5
|
+
export declare function getItemMeshMetadataSourceRoot(itemId: string): Object3D | undefined;
|
|
6
|
+
export declare function drainItemMeshMetadataSyncRequests(): string[];
|
|
7
|
+
//# sourceMappingURL=sync-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-request.d.ts","sourceRoot":"","sources":["../../../src/systems/item-mesh-metadata/sync-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAMrC,6FAA6F;AAC7F,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,QAEzD;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,QAMlF;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAElF;AAED,wBAAgB,iCAAiC,IAAI,MAAM,EAAE,CAK5D"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const pendingIds = new Set();
|
|
2
|
+
/** Preferred root for footprint math (Clone root). Falls back to sceneRegistry item root. */
|
|
3
|
+
const sourceRoots = new Map();
|
|
4
|
+
/** Called when an item's loaded GLTF (or metadata driving footprint) may need re-syncing. */
|
|
5
|
+
export function requestItemMeshMetadataSync(itemId) {
|
|
6
|
+
pendingIds.add(itemId);
|
|
7
|
+
}
|
|
8
|
+
export function setItemMeshMetadataSourceRoot(itemId, root) {
|
|
9
|
+
if (root) {
|
|
10
|
+
sourceRoots.set(itemId, root);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
sourceRoots.delete(itemId);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function getItemMeshMetadataSourceRoot(itemId) {
|
|
17
|
+
return sourceRoots.get(itemId);
|
|
18
|
+
}
|
|
19
|
+
export function drainItemMeshMetadataSyncRequests() {
|
|
20
|
+
if (pendingIds.size === 0)
|
|
21
|
+
return [];
|
|
22
|
+
const ids = [...pendingIds];
|
|
23
|
+
pendingIds.clear();
|
|
24
|
+
return ids;
|
|
25
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roof-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"roof-materials.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AA4BhG,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,iBAAiB,GAAG,IAAI,CA8B7E"}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { getEffectiveRoofSurfaceMaterial, } from '@pascal-app/core';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
import { createMaterial, createMaterialFromPresetRef } from '../../lib/materials';
|
|
4
|
+
const roofMaterialArrayCache = new Map();
|
|
5
|
+
function getSurfaceMaterialSignature(spec) {
|
|
6
|
+
return JSON.stringify({
|
|
7
|
+
material: spec.material ?? null,
|
|
8
|
+
materialPreset: spec.materialPreset ?? null,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
4
11
|
function createResolvedMaterial(material, materialPreset) {
|
|
5
12
|
if (materialPreset) {
|
|
6
13
|
return createMaterialFromPresetRef(materialPreset);
|
|
@@ -14,16 +21,26 @@ export function getRoofMaterialArray(node) {
|
|
|
14
21
|
const top = getEffectiveRoofSurfaceMaterial(node, 'top');
|
|
15
22
|
const edge = getEffectiveRoofSurfaceMaterial(node, 'edge');
|
|
16
23
|
const wall = getEffectiveRoofSurfaceMaterial(node, 'wall');
|
|
24
|
+
const cacheKey = JSON.stringify({
|
|
25
|
+
top: getSurfaceMaterialSignature(top),
|
|
26
|
+
edge: getSurfaceMaterialSignature(edge),
|
|
27
|
+
wall: getSurfaceMaterialSignature(wall),
|
|
28
|
+
});
|
|
29
|
+
const cached = roofMaterialArrayCache.get(cacheKey);
|
|
30
|
+
if (cached)
|
|
31
|
+
return cached;
|
|
17
32
|
const topMaterial = createResolvedMaterial(top.material, top.materialPreset);
|
|
18
33
|
const edgeMaterial = createResolvedMaterial(edge.material, edge.materialPreset);
|
|
19
34
|
const wallMaterial = createResolvedMaterial(wall.material, wall.materialPreset);
|
|
20
35
|
if (!(topMaterial || edgeMaterial || wallMaterial)) {
|
|
21
36
|
return null;
|
|
22
37
|
}
|
|
23
|
-
|
|
38
|
+
const materialArray = [
|
|
24
39
|
edgeMaterial ?? wallMaterial ?? topMaterial ?? new THREE.MeshStandardMaterial(),
|
|
25
40
|
wallMaterial ?? edgeMaterial ?? topMaterial ?? new THREE.MeshStandardMaterial(),
|
|
26
41
|
wallMaterial ?? edgeMaterial ?? topMaterial ?? new THREE.MeshStandardMaterial(),
|
|
27
42
|
topMaterial ?? wallMaterial ?? edgeMaterial ?? new THREE.MeshStandardMaterial(),
|
|
28
43
|
];
|
|
44
|
+
roofMaterialArrayCache.set(cacheKey, materialArray);
|
|
45
|
+
return materialArray;
|
|
29
46
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type RoofSegmentNode } from '@pascal-app/core';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
import { Brush } from 'three-bvh-csg';
|
|
4
|
+
export declare const RoofSystem: () => null;
|
|
5
|
+
/**
|
|
6
|
+
* Generate complete hollow-shell geometry for a roof segment.
|
|
7
|
+
* Ports the prototype's CSG approach using three-bvh-csg.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getRoofSegmentBrushes(node: RoofSegmentNode): {
|
|
10
|
+
deckSlab: Brush;
|
|
11
|
+
shinSlab: Brush;
|
|
12
|
+
wallBrush: Brush;
|
|
13
|
+
innerBrush: Brush;
|
|
14
|
+
} | null;
|
|
15
|
+
export declare function generateRoofSegmentGeometry(node: RoofSegmentNode): THREE.BufferGeometry;
|
|
16
|
+
//# sourceMappingURL=roof-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roof-system.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-system.tsx"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,eAAe,EAIrB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAA0B,MAAM,eAAe,CAAA;AAmCvE,eAAO,MAAM,UAAU,YAsFtB,CAAA;AAuLD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,eAAe,GACpB;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,KAAK,CAAA;CAAE,GAAG,IAAI,CAqRlF;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,eAAe,GAAG,KAAK,CAAC,cAAc,CAqDvF"}
|