@pascal-app/viewer 0.5.1 → 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.
Files changed (121) hide show
  1. package/dist/components/error-boundary.d.ts +7 -3
  2. package/dist/components/error-boundary.d.ts.map +1 -1
  3. package/dist/components/error-boundary.js +3 -1
  4. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -1
  5. package/dist/components/renderers/ceiling/ceiling-renderer.js +21 -4
  6. package/dist/components/renderers/column/column-renderer.d.ts +5 -0
  7. package/dist/components/renderers/column/column-renderer.d.ts.map +1 -0
  8. package/dist/components/renderers/column/column-renderer.js +594 -0
  9. package/dist/components/renderers/door/door-renderer.d.ts.map +1 -1
  10. package/dist/components/renderers/door/door-renderer.js +8 -10
  11. package/dist/components/renderers/fence/fence-renderer.d.ts.map +1 -1
  12. package/dist/components/renderers/fence/fence-renderer.js +16 -2
  13. package/dist/components/renderers/guide/guide-renderer.js +1 -1
  14. package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
  15. package/dist/components/renderers/item/item-renderer.js +2 -1
  16. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  17. package/dist/components/renderers/node-renderer.js +3 -1
  18. package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
  19. package/dist/components/renderers/roof/roof-renderer.js +19 -9
  20. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -1
  21. package/dist/components/renderers/roof-segment/roof-segment-renderer.js +27 -8
  22. package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
  23. package/dist/components/renderers/site/site-renderer.js +7 -1
  24. package/dist/components/renderers/slab/slab-renderer.d.ts.map +1 -1
  25. package/dist/components/renderers/slab/slab-renderer.js +48 -7
  26. package/dist/components/renderers/spawn/spawn-renderer.d.ts +5 -0
  27. package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +1 -0
  28. package/dist/components/renderers/spawn/spawn-renderer.js +32 -0
  29. package/dist/components/renderers/stair/stair-renderer.d.ts.map +1 -1
  30. package/dist/components/renderers/stair/stair-renderer.js +154 -39
  31. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +1 -1
  32. package/dist/components/renderers/stair-segment/stair-segment-renderer.js +22 -10
  33. package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -1
  34. package/dist/components/renderers/wall/wall-renderer.js +4 -9
  35. package/dist/components/renderers/window/window-renderer.d.ts.map +1 -1
  36. package/dist/components/renderers/window/window-renderer.js +5 -2
  37. package/dist/components/viewer/frame-limiter.d.ts +6 -0
  38. package/dist/components/viewer/frame-limiter.d.ts.map +1 -0
  39. package/dist/components/viewer/frame-limiter.js +31 -0
  40. package/dist/components/viewer/ground-occluder.d.ts.map +1 -1
  41. package/dist/components/viewer/ground-occluder.js +5 -1
  42. package/dist/components/viewer/index.d.ts +2 -0
  43. package/dist/components/viewer/index.d.ts.map +1 -1
  44. package/dist/components/viewer/index.js +90 -20
  45. package/dist/components/viewer/post-processing.d.ts +13 -1
  46. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  47. package/dist/components/viewer/post-processing.js +98 -44
  48. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  49. package/dist/components/viewer/selection-manager.js +31 -3
  50. package/dist/hooks/use-node-events.d.ts +9 -1
  51. package/dist/hooks/use-node-events.d.ts.map +1 -1
  52. package/dist/hooks/use-node-events.js +2 -0
  53. package/dist/index.d.ts +6 -2
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +5 -2
  56. package/dist/lib/materials.d.ts +7 -1
  57. package/dist/lib/materials.d.ts.map +1 -1
  58. package/dist/lib/materials.js +192 -2
  59. package/dist/lib/merged-outline-node.d.ts +2 -0
  60. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  61. package/dist/lib/merged-outline-node.js +36 -9
  62. package/dist/store/use-viewer.d.ts +6 -6
  63. package/dist/store/use-viewer.d.ts.map +1 -1
  64. package/dist/store/use-viewer.js +2 -2
  65. package/dist/systems/ceiling/ceiling-system.d.ts +8 -0
  66. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -0
  67. package/dist/systems/ceiling/ceiling-system.js +91 -0
  68. package/dist/systems/column/column-geometry.d.ts +22 -0
  69. package/dist/systems/column/column-geometry.d.ts.map +1 -0
  70. package/dist/systems/column/column-geometry.js +108 -0
  71. package/dist/systems/door/door-animation-system.d.ts +2 -0
  72. package/dist/systems/door/door-animation-system.d.ts.map +1 -0
  73. package/dist/systems/door/door-animation-system.js +53 -0
  74. package/dist/systems/door/door-system.d.ts +2 -0
  75. package/dist/systems/door/door-system.d.ts.map +1 -0
  76. package/dist/systems/door/door-system.js +867 -0
  77. package/dist/systems/fence/fence-system.d.ts +2 -0
  78. package/dist/systems/fence/fence-system.d.ts.map +1 -0
  79. package/dist/systems/fence/fence-system.js +185 -0
  80. package/dist/systems/item/item-system.d.ts +2 -0
  81. package/dist/systems/item/item-system.d.ts.map +1 -0
  82. package/dist/systems/item/item-system.js +44 -0
  83. package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts +14 -0
  84. package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts.map +1 -0
  85. package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.js +155 -0
  86. package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts +6 -0
  87. package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts.map +1 -0
  88. package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.js +75 -0
  89. package/dist/systems/item-mesh-metadata/sync-request.d.ts +7 -0
  90. package/dist/systems/item-mesh-metadata/sync-request.d.ts.map +1 -0
  91. package/dist/systems/item-mesh-metadata/sync-request.js +25 -0
  92. package/dist/systems/roof/roof-materials.d.ts +5 -0
  93. package/dist/systems/roof/roof-materials.d.ts.map +1 -0
  94. package/dist/systems/roof/roof-materials.js +46 -0
  95. package/dist/systems/roof/roof-system.d.ts +16 -0
  96. package/dist/systems/roof/roof-system.d.ts.map +1 -0
  97. package/dist/systems/roof/roof-system.js +797 -0
  98. package/dist/systems/slab/slab-system.d.ts +8 -0
  99. package/dist/systems/slab/slab-system.d.ts.map +1 -0
  100. package/dist/systems/slab/slab-system.js +155 -0
  101. package/dist/systems/stair/stair-materials.d.ts +7 -0
  102. package/dist/systems/stair/stair-materials.d.ts.map +1 -0
  103. package/dist/systems/stair/stair-materials.js +56 -0
  104. package/dist/systems/stair/stair-system.d.ts +2 -0
  105. package/dist/systems/stair/stair-system.d.ts.map +1 -0
  106. package/dist/systems/stair/stair-system.js +772 -0
  107. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  108. package/dist/systems/wall/wall-cutout.js +4 -123
  109. package/dist/systems/wall/wall-materials.d.ts +16 -0
  110. package/dist/systems/wall/wall-materials.d.ts.map +1 -0
  111. package/dist/systems/wall/wall-materials.js +148 -0
  112. package/dist/systems/wall/wall-system.d.ts +11 -0
  113. package/dist/systems/wall/wall-system.d.ts.map +1 -0
  114. package/dist/systems/wall/wall-system.js +592 -0
  115. package/dist/systems/window/window-animation-system.d.ts +2 -0
  116. package/dist/systems/window/window-animation-system.d.ts.map +1 -0
  117. package/dist/systems/window/window-animation-system.js +135 -0
  118. package/dist/systems/window/window-system.d.ts +12 -0
  119. package/dist/systems/window/window-system.d.ts.map +1 -0
  120. package/dist/systems/window/window-system.js +1430 -0
  121. package/package.json +6 -4
@@ -1,24 +1,49 @@
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 { getStairBodyMaterials, getStairRailingMaterial, } 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
- }, [node.material, node.material?.preset, node.material?.properties, node.material?.texture]);
21
- return (_jsxs("group", { "position-x": node.position[0], "position-z": node.position[2], ref: ref, "rotation-y": node.rotation, visible: node.visible, ...handlers, children: [_jsx("mesh", { castShadow: true, material: material, name: "merged-stair", receiveShadow: true, children: _jsx("boxGeometry", { args: [0, 0, 0] }) }), node.stairType === 'curved' || node.stairType === 'spiral' ? (_jsx(CurvedStairBody, { material: material, stair: node })) : null, _jsx(StairRailings, { material: material, stair: node }), _jsx("group", { name: "segments-wrapper", visible: false, children: (node.children ?? []).map((childId) => (_jsx(NodeRenderer, { nodeId: childId }, childId))) })] }));
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), [node]);
33
+ const railingMaterial = useMemo(() => getStairRailingMaterial(node), [node]);
34
+ const straightPlaceholderGeometry = useMemo(() => {
35
+ const geometry = new THREE.BufferGeometry();
36
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3));
37
+ geometry.addGroup(0, 0, 0);
38
+ geometry.addGroup(0, 0, 1);
39
+ return geometry;
40
+ }, []);
41
+ useEffect(() => {
42
+ return () => {
43
+ straightPlaceholderGeometry.dispose();
44
+ };
45
+ }, [straightPlaceholderGeometry]);
46
+ 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
47
  };
23
48
  function StairRailings({ stair, material }) {
24
49
  const nodes = useScene((state) => state.nodes);
@@ -60,7 +85,7 @@ function StairRailings({ stair, material }) {
60
85
  Math.sin(angle) * radius,
61
86
  ];
62
87
  });
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) => {
88
+ 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
89
  const nextPoint = sidePoints[pointIndex + 1];
65
90
  if (!nextPoint)
66
91
  return null;
@@ -71,7 +96,11 @@ function StairRailings({ stair, material }) {
71
96
  if (railPaths.length === 0) {
72
97
  return null;
73
98
  }
74
- return (_jsxs("group", { name: "stair-railing", children: [railPaths.map((segmentPath, index) => (_jsx("group", { position: [segmentPath.layout.center[0], segmentPath.layout.elevation, segmentPath.layout.center[1]], "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, 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) => {
99
+ return (_jsxs("group", { name: "stair-railing", children: [railPaths.map((segmentPath, index) => (_jsx("group", { position: [
100
+ segmentPath.layout.center[0],
101
+ segmentPath.layout.elevation,
102
+ segmentPath.layout.center[1],
103
+ ], "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
104
  const nextPoint = sidePath.points[pointIndex + 1];
76
105
  if (!nextPoint)
77
106
  return null;
@@ -94,23 +123,43 @@ function StairRailings({ stair, material }) {
94
123
  const lastPoint = entry.points[entry.points.length - 1];
95
124
  return {
96
125
  entry,
97
- distance: lastPoint ? distance3(toWorldRailPoint(previousPath.layout, lastPoint), currentWorldPoint) : Number.POSITIVE_INFINITY,
126
+ distance: lastPoint
127
+ ? distance3(toWorldRailPoint(previousPath.layout, lastPoint), currentWorldPoint)
128
+ : Number.POSITIVE_INFINITY,
98
129
  };
99
130
  })
100
131
  .sort((left, right) => left.distance - right.distance)[0]?.entry;
101
- const previousPoint = previousSidePath && previousSidePath.points.length
132
+ const previousPoint = previousSidePath?.points.length
102
133
  ? previousSidePath.points[previousSidePath.points.length - 1]
103
134
  : null;
104
135
  if (!(previousPoint && currentPoint)) {
105
136
  return null;
106
137
  }
107
138
  const previousWorldPoint = toWorldRailPoint(previousPath.layout, previousPoint);
108
- return (_jsxs("group", { children: [_jsx(RailSegment, { end: [currentWorldPoint[0], currentWorldPoint[1] + railHeight, currentWorldPoint[2]], material: material, radius: railRadius, start: [previousWorldPoint[0], previousWorldPoint[1] + railHeight, previousWorldPoint[2]] }), _jsx(RailSegment, { end: [currentWorldPoint[0], currentWorldPoint[1] + midRailHeight, currentWorldPoint[2]], material: material, radius: railRadius * 0.8, start: [previousWorldPoint[0], previousWorldPoint[1] + midRailHeight, previousWorldPoint[2]] })] }, `${previousPath.layout.segment.id}-${segmentPath.layout.segment.id}-${sideIndex}`));
139
+ return (_jsxs("group", { children: [_jsx(RailSegment, { end: [
140
+ currentWorldPoint[0],
141
+ currentWorldPoint[1] + railHeight,
142
+ currentWorldPoint[2],
143
+ ], material: material, radius: railRadius, start: [
144
+ previousWorldPoint[0],
145
+ previousWorldPoint[1] + railHeight,
146
+ previousWorldPoint[2],
147
+ ] }), _jsx(RailSegment, { end: [
148
+ currentWorldPoint[0],
149
+ currentWorldPoint[1] + midRailHeight,
150
+ currentWorldPoint[2],
151
+ ], material: material, radius: railRadius * 0.8, start: [
152
+ previousWorldPoint[0],
153
+ previousWorldPoint[1] + midRailHeight,
154
+ previousWorldPoint[2],
155
+ ] })] }, `${previousPath.layout.segment.id}-${segmentPath.layout.segment.id}-${sideIndex}`));
109
156
  });
110
157
  })] }));
111
158
  }
112
159
  const BALUSTER_GEOMETRY = new THREE.CylinderGeometry(1, 1, 1, 8);
113
160
  const RAIL_GEOMETRY = new THREE.CylinderGeometry(1, 1, 1, 8);
161
+ const STAIR_TREAD_MATERIAL_INDEX = 0;
162
+ const STAIR_SIDE_MATERIAL_INDEX = 1;
114
163
  function RailSegment({ start, end, radius, material, }) {
115
164
  const startVector = useMemo(() => new THREE.Vector3(...start), [start]);
116
165
  const endVector = useMemo(() => new THREE.Vector3(...end), [end]);
@@ -118,9 +167,10 @@ function RailSegment({ start, end, radius, material, }) {
118
167
  const length = Math.max(direction.length(), 0.01);
119
168
  const quaternion = useMemo(() => new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction.clone().normalize()), [direction]);
120
169
  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)] }));
170
+ 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
171
  }
123
- function CurvedStairBody({ stair, material }) {
172
+ function CurvedStairBody({ stair, bodyMaterials, }) {
173
+ const sideMaterial = bodyMaterials[1];
124
174
  const stepCount = Math.max(2, Math.round(stair.stepCount ?? 10));
125
175
  const totalRise = Math.max(stair.totalRise ?? 2.5, 0.1);
126
176
  const stepHeight = totalRise / stepCount;
@@ -135,34 +185,47 @@ function CurvedStairBody({ stair, material }) {
135
185
  const spiralColumnHeight = totalRise + thickness;
136
186
  const spiralLandingDepth = Math.max(0.3, stair.topLandingDepth ?? Math.max((stair.width ?? 1) * 0.9, 0.8));
137
187
  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)
188
+ ? Math.min(Math.PI * 0.75, spiralLandingDepth / Math.max(innerRadius + (stair.width ?? 1) / 2, 0.1)) * Math.sign(sweepAngle || 1)
140
189
  : 0;
141
190
  const spiralLastStepTop = stepHeight * Math.max(stepCount - 1, 0) + thickness;
142
191
  const spiralLandingThickness = isSpiral && (stair.topLandingMode ?? 'none') === 'integrated'
143
192
  ? Math.max(0.02, totalRise - spiralLastStepTop)
144
193
  : 0;
145
- return (_jsxs("group", { name: isSpiral ? 'spiral-stair' : 'curved-stair', children: [isSpiral && (stair.showCenterColumn ?? true) ? (_jsx("mesh", { castShadow: true, receiveShadow: true, material: material, position: [0, spiralColumnHeight / 2, 0], children: _jsx("cylinderGeometry", { args: [spiralColumnRadius, spiralColumnRadius, spiralColumnHeight, 10] }) })) : null, Array.from({ length: stepCount }).map((_, index) => {
194
+ 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
195
  const currentHeight = stepHeight * (index + 1);
147
- const actualStepHeight = isSpiral ? thickness : fillToFloor ? Math.max(currentHeight, thickness) : thickness;
196
+ const actualStepHeight = isSpiral
197
+ ? thickness
198
+ : fillToFloor
199
+ ? Math.max(currentHeight, thickness)
200
+ : thickness;
148
201
  const startAngle = -sweepAngle / 2 + stepSweep * index;
149
202
  const endAngle = startAngle + stepSweep;
150
- const stepY = isSpiral ? stepHeight * index : fillToFloor ? 0 : Math.max(currentHeight - thickness, 0);
203
+ const stepY = isSpiral
204
+ ? stepHeight * index
205
+ : fillToFloor
206
+ ? 0
207
+ : Math.max(currentHeight - thickness, 0);
151
208
  const midAngle = startAngle + stepSweep / 2;
152
- return (_jsxs("group", { "position-y": stepY, children: [isSpiral && (stair.showStepSupports ?? true) ? (_jsx("mesh", { castShadow: true, material: material, position: [
153
- Math.cos(midAngle) * (spiralColumnRadius + Math.max(0.04, innerRadius - spiralColumnRadius + 0.04) / 2 - 0.02),
209
+ return (_jsxs("group", { "position-y": stepY, children: [isSpiral && (stair.showStepSupports ?? true) ? (_jsx("mesh", { castShadow: true, material: sideMaterial, name: "stair-side", position: [
210
+ Math.cos(midAngle) *
211
+ (spiralColumnRadius +
212
+ Math.max(0.04, innerRadius - spiralColumnRadius + 0.04) / 2 -
213
+ 0.02),
154
214
  Math.max(thickness * 0.55, 0.025) / 2,
155
- Math.sin(midAngle) * (spiralColumnRadius + Math.max(0.04, innerRadius - spiralColumnRadius + 0.04) / 2 - 0.02),
215
+ Math.sin(midAngle) *
216
+ (spiralColumnRadius +
217
+ Math.max(0.04, innerRadius - spiralColumnRadius + 0.04) / 2 -
218
+ 0.02),
156
219
  ], receiveShadow: true, "rotation-y": -midAngle, children: _jsx("boxGeometry", { args: [
157
220
  Math.max(0.04, innerRadius - spiralColumnRadius + 0.04),
158
221
  Math.max(thickness * 0.55, 0.025),
159
222
  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: material, outerRadius: outerRadius, positionY: 0, startAngle: startAngle, stepHeight: actualStepHeight, thickness: thickness })] }, `${stair.id}-${isSpiral ? 'spiral' : 'curved'}-step-${index}`));
161
- }), isSpiral && (stair.topLandingMode ?? 'none') === 'integrated' ? (_jsx(CurvedStepMesh, { endAngle: sweepAngle / 2 + spiralLandingSweep, innerRadius: innerRadius, material: material, outerRadius: outerRadius, positionY: spiralLastStepTop, startAngle: sweepAngle / 2, stepHeight: spiralLandingThickness, thickness: spiralLandingThickness })) : null] }));
223
+ ] }) })) : 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}`));
224
+ }), 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
225
  }
163
226
  function CurvedStepMesh({ innerRadius, outerRadius, startAngle, endAngle, stepHeight, thickness, positionY, material, }) {
164
227
  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 });
228
+ return (_jsx("mesh", { castShadow: true, geometry: geometry, material: material, "position-y": positionY, receiveShadow: true }));
166
229
  }
167
230
  function buildCurvedStepGeometry(innerRadius, outerRadius, startAngle, endAngle, height) {
168
231
  const clampedHeight = Math.max(height, 0.02);
@@ -173,8 +236,30 @@ function buildCurvedStepGeometry(innerRadius, outerRadius, startAngle, endAngle,
173
236
  const segmentCount = Math.max(4, Math.min(24, Math.ceil(Math.abs(sweepAngle) / (Math.PI / 18) + Math.max(0, (outerRadius - innerRadius) * 3))));
174
237
  const positions = [];
175
238
  const normals = [];
239
+ const uvs = [];
240
+ const triangleMaterialIndices = [];
176
241
  const pointOnArc = (radius, angle, y) => new THREE.Vector3(Math.cos(angle) * radius, y, Math.sin(angle) * radius);
177
- const pushTriangle = (a, b, c, normal) => {
242
+ const pushUv = (point, normal, materialIndex) => {
243
+ if (materialIndex === STAIR_TREAD_MATERIAL_INDEX) {
244
+ const angle = Math.atan2(point.z, point.x);
245
+ const arcOffset = (angle - startAngle) * Math.max((innerRadius + outerRadius) * 0.5, 0.01);
246
+ uvs.push(arcOffset, Math.sqrt(point.x * point.x + point.z * point.z) - innerRadius);
247
+ return;
248
+ }
249
+ const absX = Math.abs(normal.x);
250
+ const absY = Math.abs(normal.y);
251
+ const absZ = Math.abs(normal.z);
252
+ if (absY >= absX && absY >= absZ) {
253
+ uvs.push(point.x, point.z);
254
+ }
255
+ else if (absX >= absZ) {
256
+ uvs.push(point.z, point.y);
257
+ }
258
+ else {
259
+ uvs.push(point.x, point.y);
260
+ }
261
+ };
262
+ const pushTriangle = (a, b, c, normal, materialIndex) => {
178
263
  const edgeAB = b.clone().sub(a);
179
264
  const edgeAC = c.clone().sub(a);
180
265
  const faceNormal = edgeAB.cross(edgeAC);
@@ -182,11 +267,13 @@ function buildCurvedStepGeometry(innerRadius, outerRadius, startAngle, endAngle,
182
267
  for (const point of ordered) {
183
268
  positions.push(point.x, point.y, point.z);
184
269
  normals.push(normal.x, normal.y, normal.z);
270
+ pushUv(point, normal, materialIndex);
185
271
  }
272
+ triangleMaterialIndices.push(materialIndex);
186
273
  };
187
- const pushQuad = (a, b, c, d, normal) => {
188
- pushTriangle(a, b, c, normal);
189
- pushTriangle(a, c, d, normal);
274
+ const pushQuad = (a, b, c, d, normal, materialIndex) => {
275
+ pushTriangle(a, b, c, normal, materialIndex);
276
+ pushTriangle(a, c, d, normal, materialIndex);
190
277
  };
191
278
  const upNormal = new THREE.Vector3(0, 1, 0);
192
279
  const downNormal = new THREE.Vector3(0, -1, 0);
@@ -206,10 +293,10 @@ function buildCurvedStepGeometry(innerRadius, outerRadius, startAngle, endAngle,
206
293
  const outerEndTop = pointOnArc(outerRadius, segEnd, y1);
207
294
  const outerNormal = new THREE.Vector3(Math.cos(midAngle), 0, Math.sin(midAngle)).normalize();
208
295
  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);
296
+ pushQuad(innerStartTop, outerStartTop, outerEndTop, innerEndTop, upNormal, STAIR_TREAD_MATERIAL_INDEX);
297
+ pushQuad(innerStartBottom, innerEndBottom, outerEndBottom, outerStartBottom, downNormal, STAIR_SIDE_MATERIAL_INDEX);
298
+ pushQuad(innerStartBottom, innerStartTop, innerEndTop, innerEndBottom, innerNormal, STAIR_SIDE_MATERIAL_INDEX);
299
+ pushQuad(outerStartBottom, outerEndBottom, outerEndTop, outerStartTop, outerNormal, STAIR_SIDE_MATERIAL_INDEX);
213
300
  }
214
301
  const startInnerBottom = pointOnArc(innerRadius, startAngle, y0);
215
302
  const startOuterBottom = pointOnArc(outerRadius, startAngle, y0);
@@ -221,11 +308,27 @@ function buildCurvedStepGeometry(innerRadius, outerRadius, startAngle, endAngle,
221
308
  const endOuterTop = pointOnArc(outerRadius, endAngle, y1);
222
309
  const startNormal = new THREE.Vector3(sweepDirection * Math.sin(startAngle), 0, -sweepDirection * Math.cos(startAngle)).normalize();
223
310
  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);
311
+ pushQuad(startInnerBottom, startOuterBottom, startOuterTop, startInnerTop, startNormal, STAIR_SIDE_MATERIAL_INDEX);
312
+ pushQuad(endInnerBottom, endInnerTop, endOuterTop, endOuterBottom, endNormal, STAIR_SIDE_MATERIAL_INDEX);
226
313
  const geometry = new THREE.BufferGeometry();
227
314
  geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
228
315
  geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
316
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
317
+ geometry.clearGroups();
318
+ let currentMaterial = triangleMaterialIndices[0];
319
+ let groupStart = 0;
320
+ for (let triangleIndex = 1; triangleIndex < triangleMaterialIndices.length; triangleIndex++) {
321
+ const materialIndex = triangleMaterialIndices[triangleIndex];
322
+ if (materialIndex === currentMaterial)
323
+ continue;
324
+ geometry.addGroup(groupStart * 3, (triangleIndex - groupStart) * 3, currentMaterial);
325
+ groupStart = triangleIndex;
326
+ currentMaterial = materialIndex;
327
+ }
328
+ if (triangleMaterialIndices.length > 0) {
329
+ geometry.addGroup(groupStart * 3, (triangleMaterialIndices.length - groupStart) * 3, currentMaterial ?? STAIR_SIDE_MATERIAL_INDEX);
330
+ }
331
+ geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(uvs.slice(), 2));
229
332
  geometry.computeVertexNormals();
230
333
  return geometry;
231
334
  }
@@ -281,7 +384,8 @@ function buildStairRailPaths(segments, railingMode) {
281
384
  ? ['left', 'right'].map((side) => buildSegmentRailPath(layouts, index, side, landingInset))
282
385
  : sideCandidates.map((side) => buildSegmentRailPath(layouts, index, side, landingInset)),
283
386
  connectFromPrevious: index > 0 &&
284
- !(previousLayout?.segment.segmentType === 'landing' && layout.segment.segmentType === 'landing'),
387
+ !(previousLayout?.segment.segmentType === 'landing' &&
388
+ layout.segment.segmentType === 'landing'),
285
389
  };
286
390
  });
287
391
  }
@@ -310,7 +414,9 @@ function buildStairRailPaths(segments, railingMode) {
310
414
  nextAttachmentSide === railingMode) ||
311
415
  suppressMiddleLandingOnPreferredTurnSide;
312
416
  const landingContinuesOnPreferredSide = layout.segment.segmentType === 'landing'
313
- ? nextAttachmentSide == null || nextAttachmentSide === 'front' || nextAttachmentSide === railingMode
417
+ ? nextAttachmentSide == null ||
418
+ nextAttachmentSide === 'front' ||
419
+ nextAttachmentSide === railingMode
314
420
  : true;
315
421
  const sideCandidates = suppressLandingRailing
316
422
  ? []
@@ -395,8 +501,12 @@ function buildSegmentRailPath(layouts, layoutIndex, side, landingInset) {
395
501
  const stepDepth = segment.length / steps;
396
502
  const stepHeight = segment.segmentType === 'landing' ? 0 : segment.height / steps;
397
503
  const flightSideOffset = side === 'left' ? segment.width / 2 - 0.045 : -segment.width / 2 + 0.045;
398
- const flightStartX = previousLayout?.segment.segmentType === 'landing' ? -segment.length / 2 + landingInset : -segment.length / 2;
399
- const flightEndX = nextLayout?.segment.segmentType === 'landing' ? segment.length / 2 - landingInset : segment.length / 2;
504
+ const flightStartX = previousLayout?.segment.segmentType === 'landing'
505
+ ? -segment.length / 2 + landingInset
506
+ : -segment.length / 2;
507
+ const flightEndX = nextLayout?.segment.segmentType === 'landing'
508
+ ? segment.length / 2 - landingInset
509
+ : segment.length / 2;
400
510
  const landingFrontX = previousLayout?.segment.segmentType === 'stair' &&
401
511
  segment.attachmentSide &&
402
512
  segment.attachmentSide !== 'front'
@@ -428,7 +538,9 @@ function buildSegmentRailPath(layouts, layoutIndex, side, landingInset) {
428
538
  return {
429
539
  side,
430
540
  points: [
431
- ...(previousLayout?.segment.segmentType === 'landing' ? [] : [[flightStartX, stepHeight > 0 ? stepHeight : 0, flightSideOffset]]),
541
+ ...(previousLayout?.segment.segmentType === 'landing'
542
+ ? []
543
+ : [[flightStartX, stepHeight > 0 ? stepHeight : 0, flightSideOffset]]),
432
544
  ...Array.from({ length: steps }).map((_, index) => [
433
545
  -segment.length / 2 + stepDepth * index + stepDepth / 2,
434
546
  stepHeight * (index + 1),
@@ -452,7 +564,10 @@ function computeSegmentTransforms(segments) {
452
564
  for (let i = 0; i < segments.length; i++) {
453
565
  const segment = segments[i];
454
566
  if (i === 0) {
455
- transforms.push({ position: [currentPos.x, currentPos.y, currentPos.z], rotation: currentRot });
567
+ transforms.push({
568
+ position: [currentPos.x, currentPos.y, currentPos.z],
569
+ rotation: currentRot,
570
+ });
456
571
  continue;
457
572
  }
458
573
  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,EAAE,KAAK,gBAAgB,EAAyB,MAAM,kBAAkB,CAAA;AAM/E,eAAO,MAAM,oBAAoB,GAAI,UAAU;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,4CA8BxE,CAAA"}
1
+ {"version":3,"file":"stair-segment-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/stair-segment/stair-segment-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,gBAAgB,EAGtB,MAAM,kBAAkB,CAAA;AAMzB,eAAO,MAAM,oBAAoB,GAAI,UAAU;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,4CA4CxE,CAAA"}
@@ -1,20 +1,32 @@
1
1
  import { jsx as _jsx } 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
+ import * as THREE from 'three';
4
5
  import { useNodeEvents } from '../../../hooks/use-node-events';
5
- import { createMaterial, DEFAULT_STAIR_MATERIAL } from '../../../lib/materials';
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');
13
- const material = useMemo(() => {
14
- const mat = node.material;
15
- if (!mat)
16
- return DEFAULT_STAIR_MATERIAL;
17
- return createMaterial(mat);
18
- }, [node.material, node.material?.preset, node.material?.properties, node.material?.texture]);
19
- return (_jsx("mesh", { material: material, position: node.position, ref: ref, "rotation-y": node.rotation, visible: node.visible, ...handlers, children: _jsx("boxGeometry", { args: [0, 0, 0] }) }));
15
+ const parentNode = node.parentId
16
+ ? nodes[node.parentId]
17
+ : undefined;
18
+ const material = useMemo(() => getStraightStairSegmentBodyMaterials(node, parentNode), [node, parentNode]);
19
+ const placeholderGeometry = useMemo(() => {
20
+ const geometry = new THREE.BufferGeometry();
21
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3));
22
+ geometry.addGroup(0, 0, 0);
23
+ geometry.addGroup(0, 0, 1);
24
+ return geometry;
25
+ }, []);
26
+ useEffect(() => {
27
+ return () => {
28
+ placeholderGeometry.dispose();
29
+ };
30
+ }, [placeholderGeometry]);
31
+ return (_jsx("mesh", { geometry: placeholderGeometry, material: material, position: node.position, ref: ref, "rotation-y": node.rotation, visible: node.visible, ...handlers }));
20
32
  };
@@ -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,4CA6BxD,CAAA"}
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, useMemo, useRef } from 'react';
3
+ import { useLayoutEffect, useRef } from 'react';
4
4
  import { useNodeEvents } from '../../../hooks/use-node-events';
5
- import { createMaterial, DEFAULT_WALL_MATERIAL } from '../../../lib/materials';
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 = useMemo(() => {
15
- const mat = node.material;
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,EAAe,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAM/D,eAAO,MAAM,cAAc,GAAI,UAAU;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,4CA2B5D,CAAA"}
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,6 @@
1
+ type FrameLimiterProps = {
2
+ fps?: number;
3
+ };
4
+ declare const FrameLimiter: React.FC<FrameLimiterProps>;
5
+ export default FrameLimiter;
6
+ //# sourceMappingURL=frame-limiter.d.ts.map
@@ -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,+CA+F1B,CAAA"}
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' && node.visible && node.polygon.length >= 3)) {
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,11 +1,13 @@
1
1
  import { type ThreeToJSXElements } from '@react-three/fiber';
2
2
  import * as THREE from 'three/webgpu';
3
+ import { type HoverStyles } from './post-processing';
3
4
  declare module '@react-three/fiber' {
4
5
  interface ThreeElements extends ThreeToJSXElements<typeof THREE> {
5
6
  }
6
7
  }
7
8
  interface ViewerProps {
8
9
  children?: React.ReactNode;
10
+ hoverStyles?: HoverStyles;
9
11
  selectionManager?: 'default' | 'custom';
10
12
  perf?: boolean;
11
13
  }
@@ -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;AA0CrC,OAAO,QAAQ,oBAAoB,CAAC;IAClC,UAAU,aAAc,SAAQ,kBAAkB,CAAC,OAAO,KAAK,CAAC;KAAG;CACpE;AA+BD,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,CA8DjC,CAAA;AASD,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/viewer/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,kBAAkB,EAAsB,MAAM,oBAAoB,CAAA;AAEhG,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAwBrC,OAAuB,EAAwB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA+B1F,OAAO,QAAQ,oBAAoB,CAAC;IAClC,UAAU,aAAc,SAAQ,kBAAkB,CAAC,OAAO,KAAK,CAAC;KAAG;CACpE;AAkFD,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,gBAAgB,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;IACvC,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,QAAA,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAmGjC,CAAA;AASD,eAAe,MAAM,CAAA"}