@pascal-app/viewer 0.5.1 → 0.6.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/renderers/ceiling/ceiling-renderer.d.ts.map +1 -1
- package/dist/components/renderers/ceiling/ceiling-renderer.js +4 -3
- package/dist/components/renderers/door/door-renderer.d.ts.map +1 -1
- package/dist/components/renderers/door/door-renderer.js +5 -2
- package/dist/components/renderers/fence/fence-renderer.d.ts.map +1 -1
- package/dist/components/renderers/fence/fence-renderer.js +16 -2
- package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
- package/dist/components/renderers/roof/roof-renderer.js +31 -9
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -1
- package/dist/components/renderers/roof-segment/roof-segment-renderer.js +42 -8
- package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
- package/dist/components/renderers/site/site-renderer.js +7 -1
- package/dist/components/renderers/slab/slab-renderer.d.ts.map +1 -1
- package/dist/components/renderers/slab/slab-renderer.js +37 -8
- package/dist/components/renderers/stair/stair-renderer.d.ts.map +1 -1
- package/dist/components/renderers/stair/stair-renderer.js +172 -39
- package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +1 -1
- package/dist/components/renderers/stair-segment/stair-segment-renderer.js +37 -8
- package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -1
- package/dist/components/renderers/wall/wall-renderer.js +4 -9
- package/dist/components/renderers/window/window-renderer.d.ts.map +1 -1
- package/dist/components/renderers/window/window-renderer.js +5 -2
- package/dist/components/viewer/frame-limiter.d.ts +6 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -0
- package/dist/components/viewer/frame-limiter.js +31 -0
- package/dist/components/viewer/ground-occluder.d.ts.map +1 -1
- package/dist/components/viewer/ground-occluder.js +5 -1
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +11 -11
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +33 -36
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +12 -1
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +2 -0
- package/dist/lib/materials.d.ts +4 -1
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +178 -2
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +11 -4
- package/dist/store/use-viewer.d.ts +2 -2
- package/dist/systems/roof/roof-materials.d.ts +5 -0
- package/dist/systems/roof/roof-materials.d.ts.map +1 -0
- package/dist/systems/roof/roof-materials.js +29 -0
- package/dist/systems/stair/stair-materials.d.ts +7 -0
- package/dist/systems/stair/stair-materials.d.ts.map +1 -0
- package/dist/systems/stair/stair-materials.js +33 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +4 -123
- package/dist/systems/wall/wall-materials.d.ts +16 -0
- package/dist/systems/wall/wall-materials.d.ts.map +1 -0
- package/dist/systems/wall/wall-materials.js +148 -0
- package/package.json +4 -4
|
@@ -1,24 +1,67 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useRegistry, useScene } from '@pascal-app/core';
|
|
3
|
-
import { useLayoutEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { useRegistry, useScene, } from '@pascal-app/core';
|
|
3
|
+
import { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
|
|
4
4
|
import * as THREE from 'three';
|
|
5
5
|
import { useNodeEvents } from '../../../hooks/use-node-events';
|
|
6
|
-
import { createMaterial, DEFAULT_STAIR_MATERIAL } from '../../../lib/materials';
|
|
6
|
+
import { createMaterial, createMaterialFromPresetRef, DEFAULT_STAIR_MATERIAL } from '../../../lib/materials';
|
|
7
|
+
import { getStairRailingMaterial, getStairBodyMaterials, } from '../../../systems/stair/stair-materials';
|
|
7
8
|
import { NodeRenderer } from '../node-renderer';
|
|
8
9
|
export const StairRenderer = ({ node }) => {
|
|
9
10
|
const ref = useRef(null);
|
|
11
|
+
const isSegmentBasedStair = node.stairType === 'straight';
|
|
10
12
|
useRegistry(node.id, 'stair', ref);
|
|
11
13
|
useLayoutEffect(() => {
|
|
12
14
|
useScene.getState().markDirty(node.id);
|
|
13
15
|
}, [node.id]);
|
|
14
16
|
const handlers = useNodeEvents(node, 'stair');
|
|
15
17
|
const material = useMemo(() => {
|
|
18
|
+
const presetMaterial = createMaterialFromPresetRef(node.materialPreset);
|
|
19
|
+
if (presetMaterial)
|
|
20
|
+
return presetMaterial;
|
|
16
21
|
const mat = node.material;
|
|
17
22
|
if (!mat)
|
|
18
23
|
return DEFAULT_STAIR_MATERIAL;
|
|
19
24
|
return createMaterial(mat);
|
|
20
|
-
}, [
|
|
21
|
-
|
|
25
|
+
}, [
|
|
26
|
+
node.materialPreset,
|
|
27
|
+
node.material,
|
|
28
|
+
node.material?.preset,
|
|
29
|
+
node.material?.properties,
|
|
30
|
+
node.material?.texture,
|
|
31
|
+
]);
|
|
32
|
+
const straightBodyMaterials = useMemo(() => getStairBodyMaterials(node), [
|
|
33
|
+
node.material,
|
|
34
|
+
node.materialPreset,
|
|
35
|
+
node.railingMaterial,
|
|
36
|
+
node.railingMaterialPreset,
|
|
37
|
+
node.sideMaterial,
|
|
38
|
+
node.sideMaterialPreset,
|
|
39
|
+
node.treadMaterial,
|
|
40
|
+
node.treadMaterialPreset,
|
|
41
|
+
]);
|
|
42
|
+
const railingMaterial = useMemo(() => getStairRailingMaterial(node), [
|
|
43
|
+
node.material,
|
|
44
|
+
node.materialPreset,
|
|
45
|
+
node.railingMaterial,
|
|
46
|
+
node.railingMaterialPreset,
|
|
47
|
+
node.sideMaterial,
|
|
48
|
+
node.sideMaterialPreset,
|
|
49
|
+
node.treadMaterial,
|
|
50
|
+
node.treadMaterialPreset,
|
|
51
|
+
]);
|
|
52
|
+
const straightPlaceholderGeometry = useMemo(() => {
|
|
53
|
+
const geometry = new THREE.BufferGeometry();
|
|
54
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3));
|
|
55
|
+
geometry.addGroup(0, 0, 0);
|
|
56
|
+
geometry.addGroup(0, 0, 1);
|
|
57
|
+
return geometry;
|
|
58
|
+
}, []);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
return () => {
|
|
61
|
+
straightPlaceholderGeometry.dispose();
|
|
62
|
+
};
|
|
63
|
+
}, [straightPlaceholderGeometry]);
|
|
64
|
+
return (_jsxs("group", { "position-x": node.position[0], "position-z": node.position[2], ref: ref, "rotation-y": node.rotation, visible: node.visible, ...handlers, children: [isSegmentBasedStair ? (_jsx("mesh", { castShadow: true, geometry: straightPlaceholderGeometry, material: straightBodyMaterials, name: "merged-stair", receiveShadow: true })) : null, !isSegmentBasedStair ? _jsx(CurvedStairBody, { bodyMaterials: straightBodyMaterials, stair: node }) : null, _jsx(StairRailings, { material: railingMaterial, stair: node }), isSegmentBasedStair ? (_jsx("group", { name: "segments-wrapper", visible: false, children: (node.children ?? []).map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId))) })) : null] }));
|
|
22
65
|
};
|
|
23
66
|
function StairRailings({ stair, material }) {
|
|
24
67
|
const nodes = useScene((state) => state.nodes);
|
|
@@ -60,7 +103,7 @@ function StairRailings({ stair, material }) {
|
|
|
60
103
|
Math.sin(angle) * radius,
|
|
61
104
|
];
|
|
62
105
|
});
|
|
63
|
-
return (_jsxs("group", { children: [sidePoints.map((point, pointIndex) => (_jsx("mesh", { castShadow: true, geometry: BALUSTER_GEOMETRY, material: material, position: [point[0], point[1] + railHeight / 2, point[2]], receiveShadow: true, scale: [balusterRadius, railHeight, balusterRadius] }, `${stair.id}-curved-baluster-${sideIndex}-${pointIndex}`))), sidePoints.slice(0, -1).map((point, pointIndex) => {
|
|
106
|
+
return (_jsxs("group", { children: [sidePoints.map((point, pointIndex) => (_jsx("mesh", { castShadow: true, geometry: BALUSTER_GEOMETRY, material: material, name: "stair-railing-baluster", position: [point[0], point[1] + railHeight / 2, point[2]], receiveShadow: true, scale: [balusterRadius, railHeight, balusterRadius] }, `${stair.id}-curved-baluster-${sideIndex}-${pointIndex}`))), sidePoints.slice(0, -1).map((point, pointIndex) => {
|
|
64
107
|
const nextPoint = sidePoints[pointIndex + 1];
|
|
65
108
|
if (!nextPoint)
|
|
66
109
|
return null;
|
|
@@ -71,7 +114,11 @@ function StairRailings({ stair, material }) {
|
|
|
71
114
|
if (railPaths.length === 0) {
|
|
72
115
|
return null;
|
|
73
116
|
}
|
|
74
|
-
return (_jsxs("group", { name: "stair-railing", children: [railPaths.map((segmentPath, index) => (_jsx("group", { position: [
|
|
117
|
+
return (_jsxs("group", { name: "stair-railing", children: [railPaths.map((segmentPath, index) => (_jsx("group", { position: [
|
|
118
|
+
segmentPath.layout.center[0],
|
|
119
|
+
segmentPath.layout.elevation,
|
|
120
|
+
segmentPath.layout.center[1],
|
|
121
|
+
], "rotation-y": segmentPath.layout.rotation, children: segmentPath.sidePaths.map((sidePath, sideIndex) => (_jsxs("group", { children: [sidePath.points.map((point, pointIndex) => (_jsx("mesh", { castShadow: true, geometry: BALUSTER_GEOMETRY, material: material, name: "stair-railing-baluster", position: [point[2], point[1] + railHeight / 2, point[0]], receiveShadow: true, scale: [balusterRadius, railHeight, balusterRadius] }, `${segmentPath.layout.segment.id}-${sidePath.side}-baluster-${pointIndex}`))), sidePath.points.slice(0, -1).map((point, pointIndex) => {
|
|
75
122
|
const nextPoint = sidePath.points[pointIndex + 1];
|
|
76
123
|
if (!nextPoint)
|
|
77
124
|
return null;
|
|
@@ -94,23 +141,43 @@ function StairRailings({ stair, material }) {
|
|
|
94
141
|
const lastPoint = entry.points[entry.points.length - 1];
|
|
95
142
|
return {
|
|
96
143
|
entry,
|
|
97
|
-
distance: lastPoint
|
|
144
|
+
distance: lastPoint
|
|
145
|
+
? distance3(toWorldRailPoint(previousPath.layout, lastPoint), currentWorldPoint)
|
|
146
|
+
: Number.POSITIVE_INFINITY,
|
|
98
147
|
};
|
|
99
148
|
})
|
|
100
149
|
.sort((left, right) => left.distance - right.distance)[0]?.entry;
|
|
101
|
-
const previousPoint = previousSidePath
|
|
150
|
+
const previousPoint = previousSidePath?.points.length
|
|
102
151
|
? previousSidePath.points[previousSidePath.points.length - 1]
|
|
103
152
|
: null;
|
|
104
153
|
if (!(previousPoint && currentPoint)) {
|
|
105
154
|
return null;
|
|
106
155
|
}
|
|
107
156
|
const previousWorldPoint = toWorldRailPoint(previousPath.layout, previousPoint);
|
|
108
|
-
return (_jsxs("group", { children: [_jsx(RailSegment, { end: [
|
|
157
|
+
return (_jsxs("group", { children: [_jsx(RailSegment, { end: [
|
|
158
|
+
currentWorldPoint[0],
|
|
159
|
+
currentWorldPoint[1] + railHeight,
|
|
160
|
+
currentWorldPoint[2],
|
|
161
|
+
], material: material, radius: railRadius, start: [
|
|
162
|
+
previousWorldPoint[0],
|
|
163
|
+
previousWorldPoint[1] + railHeight,
|
|
164
|
+
previousWorldPoint[2],
|
|
165
|
+
] }), _jsx(RailSegment, { end: [
|
|
166
|
+
currentWorldPoint[0],
|
|
167
|
+
currentWorldPoint[1] + midRailHeight,
|
|
168
|
+
currentWorldPoint[2],
|
|
169
|
+
], material: material, radius: railRadius * 0.8, start: [
|
|
170
|
+
previousWorldPoint[0],
|
|
171
|
+
previousWorldPoint[1] + midRailHeight,
|
|
172
|
+
previousWorldPoint[2],
|
|
173
|
+
] })] }, `${previousPath.layout.segment.id}-${segmentPath.layout.segment.id}-${sideIndex}`));
|
|
109
174
|
});
|
|
110
175
|
})] }));
|
|
111
176
|
}
|
|
112
177
|
const BALUSTER_GEOMETRY = new THREE.CylinderGeometry(1, 1, 1, 8);
|
|
113
178
|
const RAIL_GEOMETRY = new THREE.CylinderGeometry(1, 1, 1, 8);
|
|
179
|
+
const STAIR_TREAD_MATERIAL_INDEX = 0;
|
|
180
|
+
const STAIR_SIDE_MATERIAL_INDEX = 1;
|
|
114
181
|
function RailSegment({ start, end, radius, material, }) {
|
|
115
182
|
const startVector = useMemo(() => new THREE.Vector3(...start), [start]);
|
|
116
183
|
const endVector = useMemo(() => new THREE.Vector3(...end), [end]);
|
|
@@ -118,9 +185,10 @@ function RailSegment({ start, end, radius, material, }) {
|
|
|
118
185
|
const length = Math.max(direction.length(), 0.01);
|
|
119
186
|
const quaternion = useMemo(() => new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction.clone().normalize()), [direction]);
|
|
120
187
|
const midpoint = useMemo(() => startVector.clone().add(endVector).multiplyScalar(0.5), [endVector, startVector]);
|
|
121
|
-
return (_jsx("mesh", { castShadow: true, geometry: RAIL_GEOMETRY, material: material, position: [midpoint.x, midpoint.y, midpoint.z], quaternion: quaternion, receiveShadow: true, scale: [Math.max(radius, 0.01), length, Math.max(radius, 0.01)] }));
|
|
188
|
+
return (_jsx("mesh", { castShadow: true, geometry: RAIL_GEOMETRY, material: material, name: "stair-railing-rail", position: [midpoint.x, midpoint.y, midpoint.z], quaternion: quaternion, receiveShadow: true, scale: [Math.max(radius, 0.01), length, Math.max(radius, 0.01)] }));
|
|
122
189
|
}
|
|
123
|
-
function CurvedStairBody({ stair,
|
|
190
|
+
function CurvedStairBody({ stair, bodyMaterials, }) {
|
|
191
|
+
const sideMaterial = bodyMaterials[1];
|
|
124
192
|
const stepCount = Math.max(2, Math.round(stair.stepCount ?? 10));
|
|
125
193
|
const totalRise = Math.max(stair.totalRise ?? 2.5, 0.1);
|
|
126
194
|
const stepHeight = totalRise / stepCount;
|
|
@@ -135,34 +203,47 @@ function CurvedStairBody({ stair, material }) {
|
|
|
135
203
|
const spiralColumnHeight = totalRise + thickness;
|
|
136
204
|
const spiralLandingDepth = Math.max(0.3, stair.topLandingDepth ?? Math.max((stair.width ?? 1) * 0.9, 0.8));
|
|
137
205
|
const spiralLandingSweep = isSpiral && (stair.topLandingMode ?? 'none') === 'integrated'
|
|
138
|
-
? Math.min(Math.PI * 0.75, spiralLandingDepth / Math.max(innerRadius + (stair.width ?? 1) / 2, 0.1)) *
|
|
139
|
-
Math.sign(sweepAngle || 1)
|
|
206
|
+
? Math.min(Math.PI * 0.75, spiralLandingDepth / Math.max(innerRadius + (stair.width ?? 1) / 2, 0.1)) * Math.sign(sweepAngle || 1)
|
|
140
207
|
: 0;
|
|
141
208
|
const spiralLastStepTop = stepHeight * Math.max(stepCount - 1, 0) + thickness;
|
|
142
209
|
const spiralLandingThickness = isSpiral && (stair.topLandingMode ?? 'none') === 'integrated'
|
|
143
210
|
? Math.max(0.02, totalRise - spiralLastStepTop)
|
|
144
211
|
: 0;
|
|
145
|
-
return (_jsxs("group", { name: isSpiral ? 'spiral-stair' : 'curved-stair', children: [isSpiral && (stair.showCenterColumn ?? true) ? (_jsx("mesh", { castShadow: true, receiveShadow: true, material:
|
|
212
|
+
return (_jsxs("group", { name: isSpiral ? 'spiral-stair' : 'curved-stair', children: [isSpiral && (stair.showCenterColumn ?? true) ? (_jsx("mesh", { castShadow: true, receiveShadow: true, material: sideMaterial, name: "stair-side", position: [0, spiralColumnHeight / 2, 0], children: _jsx("cylinderGeometry", { args: [spiralColumnRadius, spiralColumnRadius, spiralColumnHeight, 10] }) })) : null, Array.from({ length: stepCount }).map((_, index) => {
|
|
146
213
|
const currentHeight = stepHeight * (index + 1);
|
|
147
|
-
const actualStepHeight = isSpiral
|
|
214
|
+
const actualStepHeight = isSpiral
|
|
215
|
+
? thickness
|
|
216
|
+
: fillToFloor
|
|
217
|
+
? Math.max(currentHeight, thickness)
|
|
218
|
+
: thickness;
|
|
148
219
|
const startAngle = -sweepAngle / 2 + stepSweep * index;
|
|
149
220
|
const endAngle = startAngle + stepSweep;
|
|
150
|
-
const stepY = isSpiral
|
|
221
|
+
const stepY = isSpiral
|
|
222
|
+
? stepHeight * index
|
|
223
|
+
: fillToFloor
|
|
224
|
+
? 0
|
|
225
|
+
: Math.max(currentHeight - thickness, 0);
|
|
151
226
|
const midAngle = startAngle + stepSweep / 2;
|
|
152
|
-
return (_jsxs("group", { "position-y": stepY, children: [isSpiral && (stair.showStepSupports ?? true) ? (_jsx("mesh", { castShadow: true, material:
|
|
153
|
-
Math.cos(midAngle) *
|
|
227
|
+
return (_jsxs("group", { "position-y": stepY, children: [isSpiral && (stair.showStepSupports ?? true) ? (_jsx("mesh", { castShadow: true, material: sideMaterial, name: "stair-side", position: [
|
|
228
|
+
Math.cos(midAngle) *
|
|
229
|
+
(spiralColumnRadius +
|
|
230
|
+
Math.max(0.04, innerRadius - spiralColumnRadius + 0.04) / 2 -
|
|
231
|
+
0.02),
|
|
154
232
|
Math.max(thickness * 0.55, 0.025) / 2,
|
|
155
|
-
Math.sin(midAngle) *
|
|
233
|
+
Math.sin(midAngle) *
|
|
234
|
+
(spiralColumnRadius +
|
|
235
|
+
Math.max(0.04, innerRadius - spiralColumnRadius + 0.04) / 2 -
|
|
236
|
+
0.02),
|
|
156
237
|
], receiveShadow: true, "rotation-y": -midAngle, children: _jsx("boxGeometry", { args: [
|
|
157
238
|
Math.max(0.04, innerRadius - spiralColumnRadius + 0.04),
|
|
158
239
|
Math.max(thickness * 0.55, 0.025),
|
|
159
240
|
Math.max(0.04, Math.min(0.12, Math.max(thickness * 0.55, 0.025) * 1.5)),
|
|
160
|
-
] }) })) : null, _jsx(CurvedStepMesh, { endAngle: endAngle, innerRadius: innerRadius, material:
|
|
161
|
-
}), isSpiral && (stair.topLandingMode ?? 'none') === 'integrated' ? (_jsx(CurvedStepMesh, { endAngle: sweepAngle / 2 + spiralLandingSweep, innerRadius: innerRadius, material:
|
|
241
|
+
] }) })) : null, _jsx(CurvedStepMesh, { endAngle: endAngle, innerRadius: innerRadius, material: bodyMaterials, outerRadius: outerRadius, positionY: 0, startAngle: startAngle, stepHeight: actualStepHeight, thickness: thickness })] }, `${stair.id}-${isSpiral ? 'spiral' : 'curved'}-step-${index}`));
|
|
242
|
+
}), isSpiral && (stair.topLandingMode ?? 'none') === 'integrated' ? (_jsx(CurvedStepMesh, { endAngle: sweepAngle / 2 + spiralLandingSweep, innerRadius: innerRadius, material: bodyMaterials, outerRadius: outerRadius, positionY: spiralLastStepTop, startAngle: sweepAngle / 2, stepHeight: spiralLandingThickness, thickness: spiralLandingThickness })) : null] }));
|
|
162
243
|
}
|
|
163
244
|
function CurvedStepMesh({ innerRadius, outerRadius, startAngle, endAngle, stepHeight, thickness, positionY, material, }) {
|
|
164
245
|
const geometry = useMemo(() => buildCurvedStepGeometry(innerRadius, outerRadius, startAngle, endAngle, Math.max(stepHeight, thickness)), [endAngle, innerRadius, outerRadius, startAngle, stepHeight, thickness]);
|
|
165
|
-
return _jsx("mesh", { castShadow: true, geometry: geometry, material: material, "position-y": positionY, receiveShadow: true });
|
|
246
|
+
return (_jsx("mesh", { castShadow: true, geometry: geometry, material: material, "position-y": positionY, receiveShadow: true }));
|
|
166
247
|
}
|
|
167
248
|
function buildCurvedStepGeometry(innerRadius, outerRadius, startAngle, endAngle, height) {
|
|
168
249
|
const clampedHeight = Math.max(height, 0.02);
|
|
@@ -173,8 +254,30 @@ function buildCurvedStepGeometry(innerRadius, outerRadius, startAngle, endAngle,
|
|
|
173
254
|
const segmentCount = Math.max(4, Math.min(24, Math.ceil(Math.abs(sweepAngle) / (Math.PI / 18) + Math.max(0, (outerRadius - innerRadius) * 3))));
|
|
174
255
|
const positions = [];
|
|
175
256
|
const normals = [];
|
|
257
|
+
const uvs = [];
|
|
258
|
+
const triangleMaterialIndices = [];
|
|
176
259
|
const pointOnArc = (radius, angle, y) => new THREE.Vector3(Math.cos(angle) * radius, y, Math.sin(angle) * radius);
|
|
177
|
-
const
|
|
260
|
+
const pushUv = (point, normal, materialIndex) => {
|
|
261
|
+
if (materialIndex === STAIR_TREAD_MATERIAL_INDEX) {
|
|
262
|
+
const angle = Math.atan2(point.z, point.x);
|
|
263
|
+
const arcOffset = (angle - startAngle) * Math.max((innerRadius + outerRadius) * 0.5, 0.01);
|
|
264
|
+
uvs.push(arcOffset, Math.sqrt(point.x * point.x + point.z * point.z) - innerRadius);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const absX = Math.abs(normal.x);
|
|
268
|
+
const absY = Math.abs(normal.y);
|
|
269
|
+
const absZ = Math.abs(normal.z);
|
|
270
|
+
if (absY >= absX && absY >= absZ) {
|
|
271
|
+
uvs.push(point.x, point.z);
|
|
272
|
+
}
|
|
273
|
+
else if (absX >= absZ) {
|
|
274
|
+
uvs.push(point.z, point.y);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
uvs.push(point.x, point.y);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const pushTriangle = (a, b, c, normal, materialIndex) => {
|
|
178
281
|
const edgeAB = b.clone().sub(a);
|
|
179
282
|
const edgeAC = c.clone().sub(a);
|
|
180
283
|
const faceNormal = edgeAB.cross(edgeAC);
|
|
@@ -182,11 +285,13 @@ function buildCurvedStepGeometry(innerRadius, outerRadius, startAngle, endAngle,
|
|
|
182
285
|
for (const point of ordered) {
|
|
183
286
|
positions.push(point.x, point.y, point.z);
|
|
184
287
|
normals.push(normal.x, normal.y, normal.z);
|
|
288
|
+
pushUv(point, normal, materialIndex);
|
|
185
289
|
}
|
|
290
|
+
triangleMaterialIndices.push(materialIndex);
|
|
186
291
|
};
|
|
187
|
-
const pushQuad = (a, b, c, d, normal) => {
|
|
188
|
-
pushTriangle(a, b, c, normal);
|
|
189
|
-
pushTriangle(a, c, d, normal);
|
|
292
|
+
const pushQuad = (a, b, c, d, normal, materialIndex) => {
|
|
293
|
+
pushTriangle(a, b, c, normal, materialIndex);
|
|
294
|
+
pushTriangle(a, c, d, normal, materialIndex);
|
|
190
295
|
};
|
|
191
296
|
const upNormal = new THREE.Vector3(0, 1, 0);
|
|
192
297
|
const downNormal = new THREE.Vector3(0, -1, 0);
|
|
@@ -206,10 +311,10 @@ function buildCurvedStepGeometry(innerRadius, outerRadius, startAngle, endAngle,
|
|
|
206
311
|
const outerEndTop = pointOnArc(outerRadius, segEnd, y1);
|
|
207
312
|
const outerNormal = new THREE.Vector3(Math.cos(midAngle), 0, Math.sin(midAngle)).normalize();
|
|
208
313
|
const innerNormal = new THREE.Vector3(-Math.cos(midAngle), 0, -Math.sin(midAngle)).normalize();
|
|
209
|
-
pushQuad(innerStartTop, outerStartTop, outerEndTop, innerEndTop, upNormal);
|
|
210
|
-
pushQuad(innerStartBottom, innerEndBottom, outerEndBottom, outerStartBottom, downNormal);
|
|
211
|
-
pushQuad(innerStartBottom, innerStartTop, innerEndTop, innerEndBottom, innerNormal);
|
|
212
|
-
pushQuad(outerStartBottom, outerEndBottom, outerEndTop, outerStartTop, outerNormal);
|
|
314
|
+
pushQuad(innerStartTop, outerStartTop, outerEndTop, innerEndTop, upNormal, STAIR_TREAD_MATERIAL_INDEX);
|
|
315
|
+
pushQuad(innerStartBottom, innerEndBottom, outerEndBottom, outerStartBottom, downNormal, STAIR_SIDE_MATERIAL_INDEX);
|
|
316
|
+
pushQuad(innerStartBottom, innerStartTop, innerEndTop, innerEndBottom, innerNormal, STAIR_SIDE_MATERIAL_INDEX);
|
|
317
|
+
pushQuad(outerStartBottom, outerEndBottom, outerEndTop, outerStartTop, outerNormal, STAIR_SIDE_MATERIAL_INDEX);
|
|
213
318
|
}
|
|
214
319
|
const startInnerBottom = pointOnArc(innerRadius, startAngle, y0);
|
|
215
320
|
const startOuterBottom = pointOnArc(outerRadius, startAngle, y0);
|
|
@@ -221,11 +326,27 @@ function buildCurvedStepGeometry(innerRadius, outerRadius, startAngle, endAngle,
|
|
|
221
326
|
const endOuterTop = pointOnArc(outerRadius, endAngle, y1);
|
|
222
327
|
const startNormal = new THREE.Vector3(sweepDirection * Math.sin(startAngle), 0, -sweepDirection * Math.cos(startAngle)).normalize();
|
|
223
328
|
const endNormal = new THREE.Vector3(-sweepDirection * Math.sin(endAngle), 0, sweepDirection * Math.cos(endAngle)).normalize();
|
|
224
|
-
pushQuad(startInnerBottom, startOuterBottom, startOuterTop, startInnerTop, startNormal);
|
|
225
|
-
pushQuad(endInnerBottom, endInnerTop, endOuterTop, endOuterBottom, endNormal);
|
|
329
|
+
pushQuad(startInnerBottom, startOuterBottom, startOuterTop, startInnerTop, startNormal, STAIR_SIDE_MATERIAL_INDEX);
|
|
330
|
+
pushQuad(endInnerBottom, endInnerTop, endOuterTop, endOuterBottom, endNormal, STAIR_SIDE_MATERIAL_INDEX);
|
|
226
331
|
const geometry = new THREE.BufferGeometry();
|
|
227
332
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
228
333
|
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
|
|
334
|
+
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
|
|
335
|
+
geometry.clearGroups();
|
|
336
|
+
let currentMaterial = triangleMaterialIndices[0];
|
|
337
|
+
let groupStart = 0;
|
|
338
|
+
for (let triangleIndex = 1; triangleIndex < triangleMaterialIndices.length; triangleIndex++) {
|
|
339
|
+
const materialIndex = triangleMaterialIndices[triangleIndex];
|
|
340
|
+
if (materialIndex === currentMaterial)
|
|
341
|
+
continue;
|
|
342
|
+
geometry.addGroup(groupStart * 3, (triangleIndex - groupStart) * 3, currentMaterial);
|
|
343
|
+
groupStart = triangleIndex;
|
|
344
|
+
currentMaterial = materialIndex;
|
|
345
|
+
}
|
|
346
|
+
if (triangleMaterialIndices.length > 0) {
|
|
347
|
+
geometry.addGroup(groupStart * 3, (triangleMaterialIndices.length - groupStart) * 3, currentMaterial ?? STAIR_SIDE_MATERIAL_INDEX);
|
|
348
|
+
}
|
|
349
|
+
geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(uvs.slice(), 2));
|
|
229
350
|
geometry.computeVertexNormals();
|
|
230
351
|
return geometry;
|
|
231
352
|
}
|
|
@@ -281,7 +402,8 @@ function buildStairRailPaths(segments, railingMode) {
|
|
|
281
402
|
? ['left', 'right'].map((side) => buildSegmentRailPath(layouts, index, side, landingInset))
|
|
282
403
|
: sideCandidates.map((side) => buildSegmentRailPath(layouts, index, side, landingInset)),
|
|
283
404
|
connectFromPrevious: index > 0 &&
|
|
284
|
-
!(previousLayout?.segment.segmentType === 'landing' &&
|
|
405
|
+
!(previousLayout?.segment.segmentType === 'landing' &&
|
|
406
|
+
layout.segment.segmentType === 'landing'),
|
|
285
407
|
};
|
|
286
408
|
});
|
|
287
409
|
}
|
|
@@ -310,7 +432,9 @@ function buildStairRailPaths(segments, railingMode) {
|
|
|
310
432
|
nextAttachmentSide === railingMode) ||
|
|
311
433
|
suppressMiddleLandingOnPreferredTurnSide;
|
|
312
434
|
const landingContinuesOnPreferredSide = layout.segment.segmentType === 'landing'
|
|
313
|
-
? nextAttachmentSide == null ||
|
|
435
|
+
? nextAttachmentSide == null ||
|
|
436
|
+
nextAttachmentSide === 'front' ||
|
|
437
|
+
nextAttachmentSide === railingMode
|
|
314
438
|
: true;
|
|
315
439
|
const sideCandidates = suppressLandingRailing
|
|
316
440
|
? []
|
|
@@ -395,8 +519,12 @@ function buildSegmentRailPath(layouts, layoutIndex, side, landingInset) {
|
|
|
395
519
|
const stepDepth = segment.length / steps;
|
|
396
520
|
const stepHeight = segment.segmentType === 'landing' ? 0 : segment.height / steps;
|
|
397
521
|
const flightSideOffset = side === 'left' ? segment.width / 2 - 0.045 : -segment.width / 2 + 0.045;
|
|
398
|
-
const flightStartX = previousLayout?.segment.segmentType === 'landing'
|
|
399
|
-
|
|
522
|
+
const flightStartX = previousLayout?.segment.segmentType === 'landing'
|
|
523
|
+
? -segment.length / 2 + landingInset
|
|
524
|
+
: -segment.length / 2;
|
|
525
|
+
const flightEndX = nextLayout?.segment.segmentType === 'landing'
|
|
526
|
+
? segment.length / 2 - landingInset
|
|
527
|
+
: segment.length / 2;
|
|
400
528
|
const landingFrontX = previousLayout?.segment.segmentType === 'stair' &&
|
|
401
529
|
segment.attachmentSide &&
|
|
402
530
|
segment.attachmentSide !== 'front'
|
|
@@ -428,7 +556,9 @@ function buildSegmentRailPath(layouts, layoutIndex, side, landingInset) {
|
|
|
428
556
|
return {
|
|
429
557
|
side,
|
|
430
558
|
points: [
|
|
431
|
-
...(previousLayout?.segment.segmentType === 'landing'
|
|
559
|
+
...(previousLayout?.segment.segmentType === 'landing'
|
|
560
|
+
? []
|
|
561
|
+
: [[flightStartX, stepHeight > 0 ? stepHeight : 0, flightSideOffset]]),
|
|
432
562
|
...Array.from({ length: steps }).map((_, index) => [
|
|
433
563
|
-segment.length / 2 + stepDepth * index + stepDepth / 2,
|
|
434
564
|
stepHeight * (index + 1),
|
|
@@ -452,7 +582,10 @@ function computeSegmentTransforms(segments) {
|
|
|
452
582
|
for (let i = 0; i < segments.length; i++) {
|
|
453
583
|
const segment = segments[i];
|
|
454
584
|
if (i === 0) {
|
|
455
|
-
transforms.push({
|
|
585
|
+
transforms.push({
|
|
586
|
+
position: [currentPos.x, currentPos.y, currentPos.z],
|
|
587
|
+
rotation: currentRot,
|
|
588
|
+
});
|
|
456
589
|
continue;
|
|
457
590
|
}
|
|
458
591
|
const prev = segments[i - 1];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stair-segment-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/stair-segment/stair-segment-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"stair-segment-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/stair-segment/stair-segment-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,gBAAgB,EAAyB,MAAM,kBAAkB,CAAA;AAM/G,eAAO,MAAM,oBAAoB,GAAI,UAAU;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,4CA4DxE,CAAA"}
|
|
@@ -1,20 +1,49 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useRegistry, useScene } from '@pascal-app/core';
|
|
3
|
-
import { useLayoutEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
|
|
4
|
+
import * as THREE from 'three';
|
|
4
5
|
import { useNodeEvents } from '../../../hooks/use-node-events';
|
|
5
|
-
import {
|
|
6
|
+
import { getStraightStairSegmentBodyMaterials } from '../../../systems/stair/stair-materials';
|
|
6
7
|
export const StairSegmentRenderer = ({ node }) => {
|
|
7
8
|
const ref = useRef(null);
|
|
9
|
+
const nodes = useScene((state) => state.nodes);
|
|
8
10
|
useRegistry(node.id, 'stair-segment', ref);
|
|
9
11
|
useLayoutEffect(() => {
|
|
10
12
|
useScene.getState().markDirty(node.id);
|
|
11
13
|
}, [node.id]);
|
|
12
14
|
const handlers = useNodeEvents(node, 'stair-segment');
|
|
15
|
+
const parentNode = node.parentId ? nodes[node.parentId] : undefined;
|
|
13
16
|
const material = useMemo(() => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
return getStraightStairSegmentBodyMaterials(node, parentNode);
|
|
18
|
+
}, [
|
|
19
|
+
node.materialPreset,
|
|
20
|
+
node.material,
|
|
21
|
+
node.material?.preset,
|
|
22
|
+
node.material?.properties,
|
|
23
|
+
node.material?.texture,
|
|
24
|
+
parentNode?.materialPreset,
|
|
25
|
+
parentNode?.material,
|
|
26
|
+
parentNode?.material?.preset,
|
|
27
|
+
parentNode?.material?.properties,
|
|
28
|
+
parentNode?.material?.texture,
|
|
29
|
+
parentNode?.railingMaterialPreset,
|
|
30
|
+
parentNode?.railingMaterial,
|
|
31
|
+
parentNode?.sideMaterialPreset,
|
|
32
|
+
parentNode?.sideMaterial,
|
|
33
|
+
parentNode?.treadMaterialPreset,
|
|
34
|
+
parentNode?.treadMaterial,
|
|
35
|
+
]);
|
|
36
|
+
const placeholderGeometry = useMemo(() => {
|
|
37
|
+
const geometry = new THREE.BufferGeometry();
|
|
38
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3));
|
|
39
|
+
geometry.addGroup(0, 0, 0);
|
|
40
|
+
geometry.addGroup(0, 0, 1);
|
|
41
|
+
return geometry;
|
|
42
|
+
}, []);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
return () => {
|
|
45
|
+
placeholderGeometry.dispose();
|
|
46
|
+
};
|
|
47
|
+
}, [placeholderGeometry]);
|
|
48
|
+
return (_jsx("mesh", { geometry: placeholderGeometry, material: material, position: node.position, ref: ref, "rotation-y": node.rotation, visible: node.visible, ...handlers }));
|
|
20
49
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wall-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/wall/wall-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAOvE,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"wall-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/wall/wall-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAOvE,eAAO,MAAM,YAAY,GAAI,UAAU;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,4CAwBxD,CAAA"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useRegistry, useScene } from '@pascal-app/core';
|
|
3
|
-
import { useLayoutEffect,
|
|
3
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
4
4
|
import { useNodeEvents } from '../../../hooks/use-node-events';
|
|
5
|
-
import {
|
|
5
|
+
import { getVisibleWallMaterials } from '../../../systems/wall/wall-materials';
|
|
6
6
|
import { NodeRenderer } from '../node-renderer';
|
|
7
7
|
export const WallRenderer = ({ node }) => {
|
|
8
8
|
const ref = useRef(null);
|
|
@@ -11,11 +11,6 @@ export const WallRenderer = ({ node }) => {
|
|
|
11
11
|
useScene.getState().markDirty(node.id);
|
|
12
12
|
}, [node.id]);
|
|
13
13
|
const handlers = useNodeEvents(node, 'wall');
|
|
14
|
-
const material =
|
|
15
|
-
|
|
16
|
-
if (!mat)
|
|
17
|
-
return DEFAULT_WALL_MATERIAL;
|
|
18
|
-
return createMaterial(mat);
|
|
19
|
-
}, [node.material, node.material?.preset, node.material?.properties, node.material?.texture]);
|
|
20
|
-
return (_jsxs("mesh", { castShadow: true, material: material, receiveShadow: true, ref: ref, visible: node.visible, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("mesh", { name: "collision-mesh", visible: false, ...handlers, children: _jsx("boxGeometry", { args: [0, 0, 0] }) }), node.children.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId)))] }));
|
|
14
|
+
const material = getVisibleWallMaterials(node);
|
|
15
|
+
return (_jsxs("mesh", { castShadow: true, material: material, receiveShadow: true, ref: ref, visible: node.visible, children: [_jsx("boxGeometry", { args: [0, 0, 0] }), _jsx("mesh", { name: "collision-mesh", visible: false, ...handlers, children: _jsx("boxGeometry", { args: [0, 0, 0] }) }), node.children.map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, `${node.id}:${childId}`)))] }));
|
|
21
16
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"window-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/window/window-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"window-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/window/window-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAMzE,eAAO,MAAM,cAAc,GAAI,UAAU;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,4CA8B5D,CAAA"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useRegistry } from '@pascal-app/core';
|
|
3
|
-
import { useMemo, useRef } from 'react';
|
|
2
|
+
import { useRegistry, useScene } from '@pascal-app/core';
|
|
3
|
+
import { useLayoutEffect, useMemo, useRef } from 'react';
|
|
4
4
|
import { useNodeEvents } from '../../../hooks/use-node-events';
|
|
5
5
|
import { createMaterial, DEFAULT_WINDOW_MATERIAL } from '../../../lib/materials';
|
|
6
6
|
export const WindowRenderer = ({ node }) => {
|
|
7
7
|
const ref = useRef(null);
|
|
8
8
|
useRegistry(node.id, 'window', ref);
|
|
9
|
+
useLayoutEffect(() => {
|
|
10
|
+
useScene.getState().markDirty(node.id);
|
|
11
|
+
}, [node.id]);
|
|
9
12
|
const handlers = useNodeEvents(node, 'window');
|
|
10
13
|
const isTransient = !!node.metadata?.isTransient;
|
|
11
14
|
const material = useMemo(() => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-limiter.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/frame-limiter.tsx"],"names":[],"mappings":"AAGA,KAAK,iBAAiB,GAAG;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAgC7C,CAAA;AAED,eAAe,YAAY,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useThree } from '@react-three/fiber';
|
|
2
|
+
import { useLayoutEffect } from 'react';
|
|
3
|
+
const FrameLimiter = ({ fps = 50 }) => {
|
|
4
|
+
const { advance, set, frameloop: initFrameloop } = useThree();
|
|
5
|
+
useLayoutEffect(() => {
|
|
6
|
+
let elapsed = 0;
|
|
7
|
+
let then = 0;
|
|
8
|
+
let i = 0;
|
|
9
|
+
let raf = null;
|
|
10
|
+
const interval = 1000 / fps;
|
|
11
|
+
function tick(t) {
|
|
12
|
+
raf = requestAnimationFrame(tick);
|
|
13
|
+
elapsed = t - then;
|
|
14
|
+
if (elapsed > interval) {
|
|
15
|
+
advance(i);
|
|
16
|
+
i += elapsed / 1000 - (elapsed % interval) / 1000;
|
|
17
|
+
then = t - (elapsed % interval);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
set({ frameloop: 'never' });
|
|
21
|
+
raf = requestAnimationFrame(tick);
|
|
22
|
+
return () => {
|
|
23
|
+
if (raf) {
|
|
24
|
+
cancelAnimationFrame(raf);
|
|
25
|
+
}
|
|
26
|
+
set({ frameloop: initFrameloop });
|
|
27
|
+
};
|
|
28
|
+
}, [fps, advance, set, initFrameloop]);
|
|
29
|
+
return null;
|
|
30
|
+
};
|
|
31
|
+
export default FrameLimiter;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ground-occluder.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/ground-occluder.tsx"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc,+
|
|
1
|
+
{"version":3,"file":"ground-occluder.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/ground-occluder.tsx"],"names":[],"mappings":"AAMA,eAAO,MAAM,cAAc,+CAuG1B,CAAA"}
|
|
@@ -31,7 +31,11 @@ export const GroundOccluder = () => {
|
|
|
31
31
|
// reveal their footprint on the level-zero ground material.
|
|
32
32
|
const polygons = [];
|
|
33
33
|
Object.values(nodes).forEach((node) => {
|
|
34
|
-
if (!(node.type === 'slab' &&
|
|
34
|
+
if (!(node.type === 'slab' &&
|
|
35
|
+
node.visible &&
|
|
36
|
+
node.polygon.length >= 3 &&
|
|
37
|
+
// Only recessed slabs should punch through the ground plane.
|
|
38
|
+
(node.elevation ?? 0.05) < 0)) {
|
|
35
39
|
return;
|
|
36
40
|
}
|
|
37
41
|
if (Number.isFinite(lowestLevelIndex)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAkB,KAAK,kBAAkB,EAAsB,MAAM,oBAAoB,CAAA;AAEhG,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAkB,KAAK,kBAAkB,EAAsB,MAAM,oBAAoB,CAAA;AAEhG,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AA2CrC,OAAO,QAAQ,oBAAoB,CAAC;IAClC,UAAU,aAAc,SAAQ,kBAAkB,CAAC,OAAO,KAAK,CAAC;KAAG;CACpE;AAwCD,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,gBAAgB,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IACvC,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAsEjC,CAAA;AASD,eAAe,MAAM,CAAA"}
|
|
@@ -13,6 +13,7 @@ import { ScanSystem } from '../../systems/scan/scan-system';
|
|
|
13
13
|
import { WallCutout } from '../../systems/wall/wall-cutout';
|
|
14
14
|
import { ZoneSystem } from '../../systems/zone/zone-system';
|
|
15
15
|
import { SceneRenderer } from '../renderers/scene-renderer';
|
|
16
|
+
import FrameLimiter from './frame-limiter';
|
|
16
17
|
import { Lights } from './lights';
|
|
17
18
|
import { PerfMonitor } from './perf-monitor';
|
|
18
19
|
import PostProcessing from './post-processing';
|
|
@@ -40,13 +41,6 @@ function AnimatedBackground({ isDark }) {
|
|
|
40
41
|
return null;
|
|
41
42
|
}
|
|
42
43
|
extend(THREE);
|
|
43
|
-
/**
|
|
44
|
-
* Monitors the WebGPU device for loss events and logs them.
|
|
45
|
-
* WebGPU device loss can happen when:
|
|
46
|
-
* - Tab is backgrounded and OS reclaims GPU
|
|
47
|
-
* - Driver crash or GPU reset
|
|
48
|
-
* - Browser security policy kills the context
|
|
49
|
-
*/
|
|
50
44
|
function GPUDeviceWatcher() {
|
|
51
45
|
const gl = useThree((s) => s.gl);
|
|
52
46
|
useEffect(() => {
|
|
@@ -55,7 +49,7 @@ function GPUDeviceWatcher() {
|
|
|
55
49
|
if (!device)
|
|
56
50
|
return;
|
|
57
51
|
device.lost.then((info) => {
|
|
58
|
-
console.error(`[viewer] WebGPU device lost: reason="${info.reason}", message="${info.message}". ` +
|
|
52
|
+
console.error(`[viewer] WebGPU device lost: reason="${info.reason ?? 'unknown'}", message="${info.message ?? ''}". ` +
|
|
59
53
|
'The page must be reloaded to recover the GPU context.');
|
|
60
54
|
});
|
|
61
55
|
}, [gl]);
|
|
@@ -63,18 +57,24 @@ function GPUDeviceWatcher() {
|
|
|
63
57
|
}
|
|
64
58
|
const Viewer = ({ children, selectionManager = 'default', perf = false, }) => {
|
|
65
59
|
const theme = useViewer((state) => state.theme);
|
|
66
|
-
return (_jsxs(Canvas, { camera: { position: [50, 50, 50], fov: 50 }, className: `transition-colors duration-700 ${theme === 'dark' ? 'bg-[#1f2433]' : 'bg-[#fafafa]'}`, dpr: [1, 1.5], gl: (props) => {
|
|
60
|
+
return (_jsxs(Canvas, { camera: { position: [50, 50, 50], fov: 50 }, className: `transition-colors duration-700 ${theme === 'dark' ? 'bg-[#1f2433]' : 'bg-[#fafafa]'}`, dpr: [1, 1.5], frameloop: "never", gl: async (props) => {
|
|
67
61
|
const renderer = new THREE.WebGPURenderer(props);
|
|
68
62
|
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
69
63
|
renderer.toneMappingExposure = 0.9;
|
|
70
|
-
//
|
|
64
|
+
// Awaiting init() is required when the browser falls back to the
|
|
65
|
+
// WebGL2 backend (Safari without the WebGPU flag, older Chrome on
|
|
66
|
+
// machines without a WebGPU device). In native WebGPU mode the
|
|
67
|
+
// init resolves almost instantly. Without this await, the first
|
|
68
|
+
// render throws "Renderer: .render() called before the backend is
|
|
69
|
+
// initialized" from the post-processing fallback path.
|
|
70
|
+
await renderer.init();
|
|
71
71
|
return renderer;
|
|
72
72
|
}, resize: {
|
|
73
73
|
debounce: 100,
|
|
74
74
|
}, shadows: {
|
|
75
75
|
type: THREE.PCFShadowMap,
|
|
76
76
|
enabled: true,
|
|
77
|
-
}, children: [_jsx(ViewerCamera, {}), _jsx(Lights, {}), _jsx(Bvh, { children: _jsx(SceneRenderer, {}) }), _jsx(LevelSystem, {}), _jsx(GuideSystem, {}), _jsx(ScanSystem, {}), _jsx(WallCutout, {}), _jsx(CeilingSystem, {}), _jsx(DoorSystem, {}), _jsx(FenceSystem, {}), _jsx(ItemSystem, {}), _jsx(RoofSystem, {}), _jsx(SlabSystem, {}), _jsx(StairSystem, {}), _jsx(WallSystem, {}), _jsx(WindowSystem, {}), _jsx(ZoneSystem, {}), _jsx(PostProcessing, {}), _jsx(GPUDeviceWatcher, {}), _jsx(ItemLightSystem, {}), selectionManager === 'default' && _jsx(SelectionManager, {}), perf && _jsx(PerfMonitor, {}), children] }));
|
|
77
|
+
}, children: [_jsx(FrameLimiter, { fps: 50 }), _jsx(ViewerCamera, {}), _jsx(Lights, {}), _jsx(Bvh, { children: _jsx(SceneRenderer, {}) }), _jsx(LevelSystem, {}), _jsx(GuideSystem, {}), _jsx(ScanSystem, {}), _jsx(WallCutout, {}), _jsx(CeilingSystem, {}), _jsx(DoorSystem, {}), _jsx(FenceSystem, {}), _jsx(ItemSystem, {}), _jsx(RoofSystem, {}), _jsx(SlabSystem, {}), _jsx(StairSystem, {}), _jsx(WallSystem, {}), _jsx(WindowSystem, {}), _jsx(ZoneSystem, {}), _jsx(PostProcessing, {}), _jsx(GPUDeviceWatcher, {}), _jsx(ItemLightSystem, {}), selectionManager === 'default' && _jsx(SelectionManager, {}), perf && _jsx(PerfMonitor, {}), children] }));
|
|
78
78
|
};
|
|
79
79
|
const DebugRenderer = () => {
|
|
80
80
|
useFrame(({ gl, scene, camera }) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AA4BA,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;
|
|
1
|
+
{"version":3,"file":"post-processing.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/post-processing.tsx"],"names":[],"mappings":"AA4BA,eAAO,MAAM,WAAW;;;;;;;;;;;;;CAavB,CAAA;AAuBD,QAAA,MAAM,oBAAoB,YAoSzB,CAAA;AAED,eAAe,oBAAoB,CAAA"}
|