@pascal-app/viewer 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/dist/components/renderers/node-renderer.d.ts +1 -1
  2. package/dist/components/renderers/node-renderer.d.ts.map +1 -1
  3. package/dist/components/renderers/node-renderer.js +38 -22
  4. package/dist/components/renderers/parametric-node-renderer.d.ts +5 -0
  5. package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -0
  6. package/dist/components/renderers/parametric-node-renderer.js +34 -0
  7. package/dist/components/renderers/scene-renderer.d.ts +1 -1
  8. package/dist/components/renderers/scene-renderer.d.ts.map +1 -1
  9. package/dist/components/viewer/ground-occluder.d.ts +1 -1
  10. package/dist/components/viewer/ground-occluder.d.ts.map +1 -1
  11. package/dist/components/viewer/ground-occluder.js +12 -24
  12. package/dist/components/viewer/index.d.ts +37 -1
  13. package/dist/components/viewer/index.d.ts.map +1 -1
  14. package/dist/components/viewer/index.js +186 -63
  15. package/dist/components/viewer/lights.d.ts +1 -1
  16. package/dist/components/viewer/lights.d.ts.map +1 -1
  17. package/dist/components/viewer/lights.js +170 -43
  18. package/dist/components/viewer/perf-monitor.d.ts +1 -1
  19. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  20. package/dist/components/viewer/perf-monitor.js +99 -11
  21. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  22. package/dist/components/viewer/post-processing.js +231 -54
  23. package/dist/components/viewer/registered-systems.d.ts +12 -0
  24. package/dist/components/viewer/registered-systems.d.ts.map +1 -0
  25. package/dist/components/viewer/registered-systems.js +47 -0
  26. package/dist/components/viewer/scene-bvh.d.ts +17 -0
  27. package/dist/components/viewer/scene-bvh.d.ts.map +1 -0
  28. package/dist/components/viewer/scene-bvh.js +94 -0
  29. package/dist/components/viewer/selection-manager.d.ts +1 -1
  30. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  31. package/dist/components/viewer/selection-manager.js +20 -5
  32. package/dist/components/viewer/viewer-camera.d.ts +1 -1
  33. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  34. package/dist/components/viewer/walkthrough-controls.d.ts +1 -1
  35. package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -1
  36. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  37. package/dist/hooks/use-node-events.d.ts +5 -73
  38. package/dist/hooks/use-node-events.d.ts.map +1 -1
  39. package/dist/hooks/use-node-events.js +19 -7
  40. package/dist/index.d.ts +36 -4
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +67 -3
  43. package/dist/lib/csg-utils.d.ts +16 -0
  44. package/dist/lib/csg-utils.d.ts.map +1 -0
  45. package/dist/lib/csg-utils.js +33 -0
  46. package/dist/lib/drawable-geometry.d.ts +21 -0
  47. package/dist/lib/drawable-geometry.d.ts.map +1 -0
  48. package/dist/lib/drawable-geometry.js +22 -0
  49. package/dist/lib/edge-style.d.ts +3 -0
  50. package/dist/lib/edge-style.d.ts.map +1 -0
  51. package/dist/lib/edge-style.js +11 -0
  52. package/dist/lib/gpu-perf.d.ts +4 -0
  53. package/dist/lib/gpu-perf.d.ts.map +1 -0
  54. package/dist/lib/gpu-perf.js +23 -0
  55. package/dist/lib/ink-edges.d.ts +9 -0
  56. package/dist/lib/ink-edges.d.ts.map +1 -0
  57. package/dist/lib/ink-edges.js +43 -0
  58. package/dist/lib/isolation.d.ts +39 -0
  59. package/dist/lib/isolation.d.ts.map +1 -0
  60. package/dist/lib/isolation.js +103 -0
  61. package/dist/lib/layers.d.ts +18 -0
  62. package/dist/lib/layers.d.ts.map +1 -1
  63. package/dist/lib/layers.js +18 -0
  64. package/dist/lib/materials.d.ts +25 -15
  65. package/dist/lib/materials.d.ts.map +1 -1
  66. package/dist/lib/materials.js +277 -62
  67. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  68. package/dist/lib/merged-outline-node.js +7 -0
  69. package/dist/lib/polygon-union.d.ts +4 -0
  70. package/dist/lib/polygon-union.d.ts.map +1 -0
  71. package/dist/lib/polygon-union.js +289 -0
  72. package/dist/lib/polygon-union.test.d.ts +2 -0
  73. package/dist/lib/polygon-union.test.d.ts.map +1 -0
  74. package/dist/lib/polygon-union.test.js +105 -0
  75. package/dist/lib/scene-themes.d.ts +29 -0
  76. package/dist/lib/scene-themes.d.ts.map +1 -0
  77. package/dist/lib/scene-themes.js +193 -0
  78. package/dist/store/use-viewer.d.ts +42 -4
  79. package/dist/store/use-viewer.d.ts.map +1 -1
  80. package/dist/store/use-viewer.js +26 -3
  81. package/dist/systems/ceiling/ceiling-system.d.ts +6 -2
  82. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  83. package/dist/systems/ceiling/ceiling-system.js +62 -12
  84. package/dist/systems/door/door-system.d.ts.map +1 -1
  85. package/dist/systems/door/door-system.js +373 -22
  86. package/dist/systems/elevator/elevator-interaction-system.d.ts +2 -0
  87. package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -0
  88. package/dist/systems/elevator/elevator-interaction-system.js +62 -0
  89. package/dist/systems/fence/fence-system.d.ts +3 -0
  90. package/dist/systems/fence/fence-system.d.ts.map +1 -1
  91. package/dist/systems/fence/fence-system.js +9 -5
  92. package/dist/systems/floor-elevation/floor-elevation-system.d.ts +20 -0
  93. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -0
  94. package/dist/systems/floor-elevation/floor-elevation-system.js +78 -0
  95. package/dist/systems/geometry/geometry-system.d.ts +34 -0
  96. package/dist/systems/geometry/geometry-system.d.ts.map +1 -0
  97. package/dist/systems/geometry/geometry-system.js +279 -0
  98. package/dist/systems/interactive/interactive-system.d.ts +1 -1
  99. package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
  100. package/dist/systems/item/item-system.d.ts +7 -0
  101. package/dist/systems/item/item-system.d.ts.map +1 -1
  102. package/dist/systems/item/item-system.js +8 -11
  103. package/dist/systems/item-light/item-light-system.d.ts +1 -1
  104. package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
  105. package/dist/systems/item-light/item-light-system.js +18 -5
  106. package/dist/systems/roof/roof-materials.d.ts +3 -2
  107. package/dist/systems/roof/roof-materials.d.ts.map +1 -1
  108. package/dist/systems/roof/roof-materials.js +38 -14
  109. package/dist/systems/roof/roof-system.d.ts +27 -0
  110. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  111. package/dist/systems/roof/roof-system.js +396 -59
  112. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  113. package/dist/systems/slab/slab-system.js +167 -49
  114. package/dist/systems/slab/slab-system.test.d.ts +2 -0
  115. package/dist/systems/slab/slab-system.test.d.ts.map +1 -0
  116. package/dist/systems/slab/slab-system.test.js +62 -0
  117. package/dist/systems/stair/stair-materials.d.ts +4 -3
  118. package/dist/systems/stair/stair-materials.d.ts.map +1 -1
  119. package/dist/systems/stair/stair-materials.js +30 -15
  120. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  121. package/dist/systems/stair/stair-system.js +50 -61
  122. package/dist/systems/surface-hole-geometry.d.ts +3 -0
  123. package/dist/systems/surface-hole-geometry.d.ts.map +1 -0
  124. package/dist/systems/surface-hole-geometry.js +4 -0
  125. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  126. package/dist/systems/wall/wall-cutout.js +18 -2
  127. package/dist/systems/wall/wall-materials.d.ts +4 -3
  128. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  129. package/dist/systems/wall/wall-materials.js +52 -28
  130. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  131. package/dist/systems/wall/wall-system.js +171 -26
  132. package/dist/systems/window/window-system.d.ts.map +1 -1
  133. package/dist/systems/window/window-system.js +71 -7
  134. package/package.json +3 -4
  135. package/dist/components/renderers/building/building-renderer.d.ts +0 -5
  136. package/dist/components/renderers/building/building-renderer.d.ts.map +0 -1
  137. package/dist/components/renderers/building/building-renderer.js +0 -11
  138. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts +0 -5
  139. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +0 -1
  140. package/dist/components/renderers/ceiling/ceiling-renderer.js +0 -58
  141. package/dist/components/renderers/column/column-renderer.d.ts +0 -5
  142. package/dist/components/renderers/column/column-renderer.d.ts.map +0 -1
  143. package/dist/components/renderers/column/column-renderer.js +0 -597
  144. package/dist/components/renderers/door/door-renderer.d.ts +0 -5
  145. package/dist/components/renderers/door/door-renderer.d.ts.map +0 -1
  146. package/dist/components/renderers/door/door-renderer.js +0 -16
  147. package/dist/components/renderers/fence/fence-renderer.d.ts +0 -5
  148. package/dist/components/renderers/fence/fence-renderer.d.ts.map +0 -1
  149. package/dist/components/renderers/fence/fence-renderer.js +0 -15
  150. package/dist/components/renderers/guide/guide-renderer.d.ts +0 -5
  151. package/dist/components/renderers/guide/guide-renderer.d.ts.map +0 -1
  152. package/dist/components/renderers/guide/guide-renderer.js +0 -38
  153. package/dist/components/renderers/item/item-renderer.d.ts +0 -5
  154. package/dist/components/renderers/item/item-renderer.d.ts.map +0 -1
  155. package/dist/components/renderers/item/item-renderer.js +0 -172
  156. package/dist/components/renderers/level/level-renderer.d.ts +0 -5
  157. package/dist/components/renderers/level/level-renderer.d.ts.map +0 -1
  158. package/dist/components/renderers/level/level-renderer.js +0 -11
  159. package/dist/components/renderers/roof/roof-materials.d.ts +0 -4
  160. package/dist/components/renderers/roof/roof-materials.d.ts.map +0 -1
  161. package/dist/components/renderers/roof/roof-materials.js +0 -16
  162. package/dist/components/renderers/roof/roof-renderer.d.ts +0 -5
  163. package/dist/components/renderers/roof/roof-renderer.d.ts.map +0 -1
  164. package/dist/components/renderers/roof/roof-renderer.js +0 -32
  165. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +0 -5
  166. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +0 -1
  167. package/dist/components/renderers/roof-segment/roof-segment-renderer.js +0 -40
  168. package/dist/components/renderers/scan/scan-renderer.d.ts +0 -5
  169. package/dist/components/renderers/scan/scan-renderer.d.ts.map +0 -1
  170. package/dist/components/renderers/scan/scan-renderer.js +0 -54
  171. package/dist/components/renderers/site/site-renderer.d.ts +0 -5
  172. package/dist/components/renderers/site/site-renderer.d.ts.map +0 -1
  173. package/dist/components/renderers/site/site-renderer.js +0 -106
  174. package/dist/components/renderers/slab/slab-renderer.d.ts +0 -5
  175. package/dist/components/renderers/slab/slab-renderer.d.ts.map +0 -1
  176. package/dist/components/renderers/slab/slab-renderer.js +0 -58
  177. package/dist/components/renderers/spawn/spawn-renderer.d.ts +0 -5
  178. package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +0 -1
  179. package/dist/components/renderers/spawn/spawn-renderer.js +0 -32
  180. package/dist/components/renderers/stair/stair-renderer.d.ts +0 -5
  181. package/dist/components/renderers/stair/stair-renderer.d.ts.map +0 -1
  182. package/dist/components/renderers/stair/stair-renderer.js +0 -603
  183. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts +0 -5
  184. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +0 -1
  185. package/dist/components/renderers/stair-segment/stair-segment-renderer.js +0 -51
  186. package/dist/components/renderers/wall/wall-renderer.d.ts +0 -5
  187. package/dist/components/renderers/wall/wall-renderer.d.ts.map +0 -1
  188. package/dist/components/renderers/wall/wall-renderer.js +0 -16
  189. package/dist/components/renderers/window/window-renderer.d.ts +0 -5
  190. package/dist/components/renderers/window/window-renderer.d.ts.map +0 -1
  191. package/dist/components/renderers/window/window-renderer.js +0 -21
  192. package/dist/components/renderers/zone/zone-renderer.d.ts +0 -5
  193. package/dist/components/renderers/zone/zone-renderer.d.ts.map +0 -1
  194. package/dist/components/renderers/zone/zone-renderer.js +0 -181
@@ -1,32 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useLiveTransforms, useRegistry } from '@pascal-app/core';
3
- import { useMemo, useRef } from 'react';
4
- import { Color, Shape } from 'three';
5
- import { useNodeEvents } from '../../../hooks/use-node-events';
6
- import useViewer from '../../../store/use-viewer';
7
- const SPAWN_COLOR = new Color('#22c55e');
8
- export const SpawnRenderer = ({ node }) => {
9
- const ref = useRef(null);
10
- const handlers = useNodeEvents(node, 'spawn');
11
- const liveTransform = useLiveTransforms((state) => state.get(node.id));
12
- const walkthroughMode = useViewer((state) => state.walkthroughMode);
13
- useRegistry(node.id, 'spawn', ref);
14
- const materialProps = useMemo(() => ({
15
- color: SPAWN_COLOR,
16
- emissive: SPAWN_COLOR,
17
- emissiveIntensity: 0.08,
18
- metalness: 0.03,
19
- roughness: 0.42,
20
- }), []);
21
- const arrowShape = useMemo(() => {
22
- const shape = new Shape();
23
- // Positive local Y becomes negative world Z after the -90deg X rotation below,
24
- // so this tip points "forward" for the player/spawn direction.
25
- shape.moveTo(0, 0.24);
26
- shape.lineTo(-0.18, -0.14);
27
- shape.lineTo(0.18, -0.14);
28
- shape.closePath();
29
- return shape;
30
- }, []);
31
- return (_jsxs("group", { position: liveTransform?.position ?? node.position, ref: ref, rotation: [0, liveTransform?.rotation ?? node.rotation, 0], visible: !walkthroughMode, children: [_jsxs("mesh", { position: [0, 0.09, 0], rotation: [-Math.PI / 2, 0, 0], ...handlers, children: [_jsx("ringGeometry", { args: [0.34, 0.48, 48] }), _jsx("meshStandardMaterial", { ...materialProps })] }), _jsxs("mesh", { position: [0, 0.1, -0.52], rotation: [-Math.PI / 2, 0, 0], ...handlers, children: [_jsx("shapeGeometry", { args: [arrowShape] }), _jsx("meshStandardMaterial", { ...materialProps })] }), _jsxs("mesh", { position: [0, 0.41, 0], ...handlers, children: [_jsx("boxGeometry", { args: [0.3, 0.54, 0.16] }), _jsx("meshStandardMaterial", { ...materialProps })] }), _jsxs("mesh", { position: [0, 0.83, 0], ...handlers, children: [_jsx("boxGeometry", { args: [0.18, 0.18, 0.18] }), _jsx("meshStandardMaterial", { ...materialProps })] })] }));
32
- };
@@ -1,5 +0,0 @@
1
- import { type StairNode } from '@pascal-app/core';
2
- export declare const StairRenderer: ({ node }: {
3
- node: StairNode;
4
- }) => import("react/jsx-runtime").JSX.Element;
5
- //# sourceMappingURL=stair-renderer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"stair-renderer.d.ts","sourceRoot":"","sources":["../../../../src/components/renderers/stair/stair-renderer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EAIf,MAAM,kBAAkB,CAAA;AA8CzB,eAAO,MAAM,aAAa,GAAI,UAAU;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,4CA2E1D,CAAA"}
@@ -1,603 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRegistry, useScene, } from '@pascal-app/core';
3
- import { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
4
- import * as THREE from 'three';
5
- import { useNodeEvents } from '../../../hooks/use-node-events';
6
- import { createMaterial, createMaterialFromPresetRef, DEFAULT_STAIR_MATERIAL, } from '../../../lib/materials';
7
- import { getStairBodyMaterials, getStairRailingMaterial, } from '../../../systems/stair/stair-materials';
8
- import { NodeRenderer } from '../node-renderer';
9
- export const StairRenderer = ({ node }) => {
10
- const ref = useRef(null);
11
- const isSegmentBasedStair = node.stairType === 'straight';
12
- useRegistry(node.id, 'stair', ref);
13
- useLayoutEffect(() => {
14
- useScene.getState().markDirty(node.id);
15
- }, [node.id]);
16
- const handlers = useNodeEvents(node, 'stair');
17
- const material = useMemo(() => {
18
- const presetMaterial = createMaterialFromPresetRef(node.materialPreset);
19
- if (presetMaterial)
20
- return presetMaterial;
21
- const mat = node.material;
22
- if (!mat)
23
- return DEFAULT_STAIR_MATERIAL;
24
- return createMaterial(mat);
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 ? null : (_jsx(CurvedStairBody, { bodyMaterials: straightBodyMaterials, stair: node })), _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] }));
47
- };
48
- function StairRailings({ stair, material }) {
49
- const nodes = useScene((state) => state.nodes);
50
- const segments = useMemo(() => (stair.children ?? [])
51
- .map((childId) => nodes[childId])
52
- .filter((node) => node?.type === 'stair-segment' && node.visible !== false), [nodes, stair.children]);
53
- const railPaths = useMemo(() => buildStairRailPaths(segments, stair.railingMode ?? 'none'), [segments, stair.railingMode]);
54
- const railHeight = stair.railingHeight ?? 0.92;
55
- const midRailHeight = Math.max(railHeight * 0.45, 0.35);
56
- const railRadius = 0.022;
57
- const balusterRadius = 0.018;
58
- if ((stair.railingMode ?? 'none') === 'none') {
59
- return null;
60
- }
61
- if (stair.stairType === 'curved' || stair.stairType === 'spiral') {
62
- const stepCount = Math.max(2, Math.round(stair.stepCount ?? 10));
63
- const sweepAngle = stair.sweepAngle ?? (stair.stairType === 'spiral' ? Math.PI * 2 : Math.PI / 2);
64
- const stepSweep = sweepAngle / stepCount;
65
- const stepHeight = Math.max(stair.totalRise ?? 2.5, 0.1) / stepCount;
66
- const innerRadius = Math.max(stair.stairType === 'spiral' ? 0.05 : 0.2, stair.innerRadius ?? 0.9);
67
- const outerRadius = innerRadius + Math.max(stair.width ?? 1, 0.4);
68
- const leftRadius = sweepAngle >= 0 ? innerRadius + 0.04 : outerRadius - 0.04;
69
- const rightRadius = sweepAngle >= 0 ? outerRadius - 0.04 : innerRadius + 0.04;
70
- const radii = stair.railingMode === 'both'
71
- ? [leftRadius, rightRadius]
72
- : stair.railingMode === 'left'
73
- ? [leftRadius]
74
- : stair.railingMode === 'right'
75
- ? [rightRadius]
76
- : [];
77
- return (_jsx("group", { name: "stair-railing", children: radii.map((radius, sideIndex) => {
78
- const sidePoints = Array.from({ length: stepCount }).map((_, index) => {
79
- const angle = -sweepAngle / 2 + stepSweep * index + stepSweep / 2;
80
- return [
81
- Math.cos(angle) * radius,
82
- stair.stairType === 'spiral'
83
- ? stepHeight * index + Math.max(stair.thickness ?? 0.25, 0.02)
84
- : stepHeight * (index + 1),
85
- Math.sin(angle) * radius,
86
- ];
87
- });
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) => {
89
- const nextPoint = sidePoints[pointIndex + 1];
90
- if (!nextPoint)
91
- return null;
92
- return (_jsxs("group", { children: [_jsx(RailSegment, { end: [nextPoint[0], nextPoint[1] + railHeight, nextPoint[2]], material: material, radius: railRadius, start: [point[0], point[1] + railHeight, point[2]] }), _jsx(RailSegment, { end: [nextPoint[0], nextPoint[1] + midRailHeight, nextPoint[2]], material: material, radius: railRadius * 0.8, start: [point[0], point[1] + midRailHeight, point[2]] })] }, `${stair.id}-curved-rail-${sideIndex}-${pointIndex}`));
93
- })] }, `${stair.id}-curved-railing-${sideIndex}`));
94
- }) }));
95
- }
96
- if (railPaths.length === 0) {
97
- return null;
98
- }
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) => {
104
- const nextPoint = sidePath.points[pointIndex + 1];
105
- if (!nextPoint)
106
- return null;
107
- return (_jsxs("group", { children: [_jsx(RailSegment, { end: [nextPoint[2], nextPoint[1] + railHeight, nextPoint[0]], material: material, radius: railRadius, start: [point[2], point[1] + railHeight, point[0]] }), _jsx(RailSegment, { end: [nextPoint[2], nextPoint[1] + midRailHeight, nextPoint[0]], material: material, radius: railRadius * 0.8, start: [point[2], point[1] + midRailHeight, point[0]] })] }, `${segmentPath.layout.segment.id}-${sidePath.side}-rail-${pointIndex}`));
108
- })] }, `${segmentPath.layout.segment.id}-${sidePath.side}-${sideIndex}`))) }, `${segmentPath.layout.segment.id}-railing`))), railPaths.slice(1).map((segmentPath, index) => {
109
- const previousPath = railPaths[index];
110
- if (!(previousPath && segmentPath.connectFromPrevious))
111
- return null;
112
- if (previousPath.layout.segment.segmentType === 'landing')
113
- return null;
114
- if (segmentPath.layout.segment.segmentType === 'landing')
115
- return null;
116
- return segmentPath.sidePaths.map((sidePath, sideIndex) => {
117
- const currentPoint = sidePath.points[0];
118
- if (!currentPoint)
119
- return null;
120
- const currentWorldPoint = toWorldRailPoint(segmentPath.layout, currentPoint);
121
- const previousSidePath = [...previousPath.sidePaths]
122
- .map((entry) => {
123
- const lastPoint = entry.points[entry.points.length - 1];
124
- return {
125
- entry,
126
- distance: lastPoint
127
- ? distance3(toWorldRailPoint(previousPath.layout, lastPoint), currentWorldPoint)
128
- : Number.POSITIVE_INFINITY,
129
- };
130
- })
131
- .sort((left, right) => left.distance - right.distance)[0]?.entry;
132
- const previousPoint = previousSidePath?.points.length
133
- ? previousSidePath.points[previousSidePath.points.length - 1]
134
- : null;
135
- if (!(previousPoint && currentPoint)) {
136
- return null;
137
- }
138
- const previousWorldPoint = toWorldRailPoint(previousPath.layout, previousPoint);
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}`));
156
- });
157
- })] }));
158
- }
159
- const BALUSTER_GEOMETRY = new THREE.CylinderGeometry(1, 1, 1, 8);
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;
163
- function RailSegment({ start, end, radius, material, }) {
164
- const startVector = useMemo(() => new THREE.Vector3(...start), [start]);
165
- const endVector = useMemo(() => new THREE.Vector3(...end), [end]);
166
- const direction = useMemo(() => endVector.clone().sub(startVector), [endVector, startVector]);
167
- const length = Math.max(direction.length(), 0.01);
168
- const quaternion = useMemo(() => new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction.clone().normalize()), [direction]);
169
- const midpoint = useMemo(() => startVector.clone().add(endVector).multiplyScalar(0.5), [endVector, startVector]);
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)] }));
171
- }
172
- function CurvedStairBody({ stair, bodyMaterials, }) {
173
- const sideMaterial = bodyMaterials[1];
174
- const stepCount = Math.max(2, Math.round(stair.stepCount ?? 10));
175
- const totalRise = Math.max(stair.totalRise ?? 2.5, 0.1);
176
- const stepHeight = totalRise / stepCount;
177
- const isSpiral = stair.stairType === 'spiral';
178
- const innerRadius = Math.max(isSpiral ? 0.05 : 0.2, stair.innerRadius ?? 0.9);
179
- const outerRadius = innerRadius + Math.max(stair.width ?? 1, 0.4);
180
- const sweepAngle = stair.sweepAngle ?? (isSpiral ? Math.PI * 2 : Math.PI / 2);
181
- const stepSweep = sweepAngle / stepCount;
182
- const thickness = Math.max(stair.thickness ?? 0.25, 0.02);
183
- const fillToFloor = stair.fillToFloor ?? true;
184
- const spiralColumnRadius = Math.max(0.05, Math.min(innerRadius * 0.72, innerRadius - 0.03));
185
- const spiralColumnHeight = totalRise + thickness;
186
- const spiralLandingDepth = Math.max(0.3, stair.topLandingDepth ?? Math.max((stair.width ?? 1) * 0.9, 0.8));
187
- const spiralLandingSweep = isSpiral && (stair.topLandingMode ?? 'none') === 'integrated'
188
- ? Math.min(Math.PI * 0.75, spiralLandingDepth / Math.max(innerRadius + (stair.width ?? 1) / 2, 0.1)) * Math.sign(sweepAngle || 1)
189
- : 0;
190
- const spiralLastStepTop = stepHeight * Math.max(stepCount - 1, 0) + thickness;
191
- const spiralLandingThickness = isSpiral && (stair.topLandingMode ?? 'none') === 'integrated'
192
- ? Math.max(0.02, totalRise - spiralLastStepTop)
193
- : 0;
194
- return (_jsxs("group", { name: isSpiral ? 'spiral-stair' : 'curved-stair', children: [isSpiral && (stair.showCenterColumn ?? true) ? (_jsx("mesh", { castShadow: true, material: sideMaterial, name: "stair-side", position: [0, spiralColumnHeight / 2, 0], receiveShadow: true, children: _jsx("cylinderGeometry", { args: [spiralColumnRadius, spiralColumnRadius, spiralColumnHeight, 10] }) })) : null, Array.from({ length: stepCount }).map((_, index) => {
195
- const currentHeight = stepHeight * (index + 1);
196
- const actualStepHeight = isSpiral
197
- ? thickness
198
- : fillToFloor
199
- ? Math.max(currentHeight, thickness)
200
- : thickness;
201
- const startAngle = -sweepAngle / 2 + stepSweep * index;
202
- const endAngle = startAngle + stepSweep;
203
- const stepY = isSpiral
204
- ? stepHeight * index
205
- : fillToFloor
206
- ? 0
207
- : Math.max(currentHeight - thickness, 0);
208
- const midAngle = startAngle + stepSweep / 2;
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),
214
- Math.max(thickness * 0.55, 0.025) / 2,
215
- Math.sin(midAngle) *
216
- (spiralColumnRadius +
217
- Math.max(0.04, innerRadius - spiralColumnRadius + 0.04) / 2 -
218
- 0.02),
219
- ], receiveShadow: true, "rotation-y": -midAngle, children: _jsx("boxGeometry", { args: [
220
- Math.max(0.04, innerRadius - spiralColumnRadius + 0.04),
221
- Math.max(thickness * 0.55, 0.025),
222
- Math.max(0.04, Math.min(0.12, Math.max(thickness * 0.55, 0.025) * 1.5)),
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] }));
225
- }
226
- function CurvedStepMesh({ innerRadius, outerRadius, startAngle, endAngle, stepHeight, thickness, positionY, material, }) {
227
- const geometry = useMemo(() => buildCurvedStepGeometry(innerRadius, outerRadius, startAngle, endAngle, Math.max(stepHeight, thickness)), [endAngle, innerRadius, outerRadius, startAngle, stepHeight, thickness]);
228
- return (_jsx("mesh", { castShadow: true, geometry: geometry, material: material, "position-y": positionY, receiveShadow: true }));
229
- }
230
- function buildCurvedStepGeometry(innerRadius, outerRadius, startAngle, endAngle, height) {
231
- const clampedHeight = Math.max(height, 0.02);
232
- const y0 = 0;
233
- const y1 = clampedHeight;
234
- const sweepAngle = endAngle - startAngle;
235
- const sweepDirection = Math.sign(sweepAngle) || 1;
236
- const segmentCount = Math.max(4, Math.min(24, Math.ceil(Math.abs(sweepAngle) / (Math.PI / 18) + Math.max(0, (outerRadius - innerRadius) * 3))));
237
- const positions = [];
238
- const normals = [];
239
- const uvs = [];
240
- const triangleMaterialIndices = [];
241
- const pointOnArc = (radius, angle, y) => new THREE.Vector3(Math.cos(angle) * radius, y, Math.sin(angle) * radius);
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) => {
263
- const edgeAB = b.clone().sub(a);
264
- const edgeAC = c.clone().sub(a);
265
- const faceNormal = edgeAB.cross(edgeAC);
266
- const ordered = faceNormal.dot(normal) >= 0 ? [a, b, c] : [a, c, b];
267
- for (const point of ordered) {
268
- positions.push(point.x, point.y, point.z);
269
- normals.push(normal.x, normal.y, normal.z);
270
- pushUv(point, normal, materialIndex);
271
- }
272
- triangleMaterialIndices.push(materialIndex);
273
- };
274
- const pushQuad = (a, b, c, d, normal, materialIndex) => {
275
- pushTriangle(a, b, c, normal, materialIndex);
276
- pushTriangle(a, c, d, normal, materialIndex);
277
- };
278
- const upNormal = new THREE.Vector3(0, 1, 0);
279
- const downNormal = new THREE.Vector3(0, -1, 0);
280
- for (let index = 0; index < segmentCount; index++) {
281
- const t0 = index / segmentCount;
282
- const t1 = (index + 1) / segmentCount;
283
- const segStart = startAngle + sweepAngle * t0;
284
- const segEnd = startAngle + sweepAngle * t1;
285
- const midAngle = (segStart + segEnd) / 2;
286
- const innerStartBottom = pointOnArc(innerRadius, segStart, y0);
287
- const innerEndBottom = pointOnArc(innerRadius, segEnd, y0);
288
- const outerStartBottom = pointOnArc(outerRadius, segStart, y0);
289
- const outerEndBottom = pointOnArc(outerRadius, segEnd, y0);
290
- const innerStartTop = pointOnArc(innerRadius, segStart, y1);
291
- const innerEndTop = pointOnArc(innerRadius, segEnd, y1);
292
- const outerStartTop = pointOnArc(outerRadius, segStart, y1);
293
- const outerEndTop = pointOnArc(outerRadius, segEnd, y1);
294
- const outerNormal = new THREE.Vector3(Math.cos(midAngle), 0, Math.sin(midAngle)).normalize();
295
- const innerNormal = new THREE.Vector3(-Math.cos(midAngle), 0, -Math.sin(midAngle)).normalize();
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);
300
- }
301
- const startInnerBottom = pointOnArc(innerRadius, startAngle, y0);
302
- const startOuterBottom = pointOnArc(outerRadius, startAngle, y0);
303
- const startInnerTop = pointOnArc(innerRadius, startAngle, y1);
304
- const startOuterTop = pointOnArc(outerRadius, startAngle, y1);
305
- const endInnerBottom = pointOnArc(innerRadius, endAngle, y0);
306
- const endOuterBottom = pointOnArc(outerRadius, endAngle, y0);
307
- const endInnerTop = pointOnArc(innerRadius, endAngle, y1);
308
- const endOuterTop = pointOnArc(outerRadius, endAngle, y1);
309
- const startNormal = new THREE.Vector3(sweepDirection * Math.sin(startAngle), 0, -sweepDirection * Math.cos(startAngle)).normalize();
310
- const endNormal = new THREE.Vector3(-sweepDirection * Math.sin(endAngle), 0, sweepDirection * Math.cos(endAngle)).normalize();
311
- pushQuad(startInnerBottom, startOuterBottom, startOuterTop, startInnerTop, startNormal, STAIR_SIDE_MATERIAL_INDEX);
312
- pushQuad(endInnerBottom, endInnerTop, endOuterTop, endOuterBottom, endNormal, STAIR_SIDE_MATERIAL_INDEX);
313
- const geometry = new THREE.BufferGeometry();
314
- geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
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));
332
- geometry.computeVertexNormals();
333
- return geometry;
334
- }
335
- function buildStairRailPaths(segments, railingMode) {
336
- if (!segments.length || railingMode === 'none')
337
- return [];
338
- const layouts = computeStairRailLayouts(segments);
339
- const landingInset = 0.08;
340
- if (railingMode === 'both') {
341
- const isStraightLineDoubleLandingLayout = layouts.length === 4 &&
342
- layouts[0]?.segment.segmentType === 'stair' &&
343
- layouts[1]?.segment.segmentType === 'landing' &&
344
- layouts[2]?.segment.segmentType === 'stair' &&
345
- layouts[2]?.segment.attachmentSide === 'front' &&
346
- layouts[3]?.segment.segmentType === 'landing' &&
347
- layouts[3]?.segment.attachmentSide === 'front';
348
- return layouts.map((layout, index) => {
349
- const previousLayout = index > 0 ? layouts[index - 1] : undefined;
350
- const nextLayout = layouts[index + 1];
351
- const { nextStairLayout, isTerminalLandingBeforeStair } = resolveLandingChainNextStair(layouts, index);
352
- const hideLandingRailing = layout.segment.segmentType === 'landing' &&
353
- previousLayout?.segment.segmentType === 'stair' &&
354
- nextLayout?.segment.segmentType === 'stair';
355
- const visualTurnSide = isTerminalLandingBeforeStair && nextStairLayout?.segment.attachmentSide
356
- ? nextStairLayout.segment.attachmentSide
357
- : nextLayout?.segment.attachmentSide;
358
- const sideCandidates = isTerminalLandingBeforeStair && layout.segment.segmentType === 'landing'
359
- ? visualTurnSide === 'left'
360
- ? ['front', 'right']
361
- : visualTurnSide === 'right'
362
- ? ['front', 'left']
363
- : ['left', 'right']
364
- : hideLandingRailing
365
- ? visualTurnSide === 'left'
366
- ? ['front', 'right']
367
- : visualTurnSide === 'right'
368
- ? ['front', 'left']
369
- : ['left', 'right']
370
- : layout.segment.segmentType === 'landing'
371
- ? nextLayout?.segment.segmentType === 'landing' && visualTurnSide === 'left'
372
- ? ['front', 'right']
373
- : nextLayout?.segment.segmentType === 'landing' && visualTurnSide === 'right'
374
- ? ['front', 'left']
375
- : visualTurnSide === 'left'
376
- ? ['right']
377
- : visualTurnSide === 'right'
378
- ? ['left']
379
- : ['left', 'right']
380
- : ['left', 'right'];
381
- return {
382
- layout,
383
- sidePaths: isStraightLineDoubleLandingLayout && index === 1
384
- ? ['left', 'right'].map((side) => buildSegmentRailPath(layouts, index, side, landingInset))
385
- : sideCandidates.map((side) => buildSegmentRailPath(layouts, index, side, landingInset)),
386
- connectFromPrevious: index > 0 &&
387
- !(previousLayout?.segment.segmentType === 'landing' &&
388
- layout.segment.segmentType === 'landing'),
389
- };
390
- });
391
- }
392
- const isStraightLineDoubleLandingLayout = layouts.length === 4 &&
393
- layouts[0]?.segment.segmentType === 'stair' &&
394
- layouts[1]?.segment.segmentType === 'landing' &&
395
- layouts[2]?.segment.segmentType === 'stair' &&
396
- layouts[2]?.segment.attachmentSide === 'front' &&
397
- layouts[3]?.segment.segmentType === 'landing' &&
398
- layouts[3]?.segment.attachmentSide === 'front';
399
- return layouts.map((layout, index) => {
400
- const previousLayout = index > 0 ? layouts[index - 1] : undefined;
401
- const nextLayout = layouts[index + 1];
402
- const { nextStairLayout, isTerminalLandingBeforeStair } = resolveLandingChainNextStair(layouts, index);
403
- const isMiddleLandingBetweenFlights = layout.segment.segmentType === 'landing' &&
404
- previousLayout?.segment.segmentType === 'stair' &&
405
- nextLayout?.segment.segmentType === 'stair';
406
- const nextAttachmentSide = nextLayout?.segment.attachmentSide;
407
- const terminalNextAttachmentSide = nextStairLayout?.segment.attachmentSide;
408
- const suppressMiddleLandingOnPreferredTurnSide = isMiddleLandingBetweenFlights &&
409
- nextAttachmentSide != null &&
410
- nextAttachmentSide !== 'front' &&
411
- nextAttachmentSide === railingMode;
412
- const suppressLandingRailing = (layout.segment.segmentType === 'landing' &&
413
- nextLayout?.segment.segmentType === 'landing' &&
414
- nextAttachmentSide === railingMode) ||
415
- suppressMiddleLandingOnPreferredTurnSide;
416
- const landingContinuesOnPreferredSide = layout.segment.segmentType === 'landing'
417
- ? nextAttachmentSide == null ||
418
- nextAttachmentSide === 'front' ||
419
- nextAttachmentSide === railingMode
420
- : true;
421
- const sideCandidates = suppressLandingRailing
422
- ? []
423
- : layout.segment.segmentType !== 'landing'
424
- ? [railingMode]
425
- : isTerminalLandingBeforeStair
426
- ? railingMode === 'left'
427
- ? terminalNextAttachmentSide === 'right'
428
- ? ['front', 'left']
429
- : terminalNextAttachmentSide === 'front' || terminalNextAttachmentSide == null
430
- ? ['left']
431
- : []
432
- : railingMode === 'right'
433
- ? terminalNextAttachmentSide === 'left'
434
- ? ['front', 'right']
435
- : terminalNextAttachmentSide === 'front' || terminalNextAttachmentSide == null
436
- ? ['right']
437
- : []
438
- : [railingMode]
439
- : isStraightLineDoubleLandingLayout
440
- ? [railingMode]
441
- : isMiddleLandingBetweenFlights && railingMode === 'left'
442
- ? nextAttachmentSide === 'right'
443
- ? ['front', 'left']
444
- : ['left']
445
- : isMiddleLandingBetweenFlights && railingMode === 'right'
446
- ? nextAttachmentSide === 'left'
447
- ? ['front', 'right']
448
- : ['right']
449
- : nextLayout?.segment.segmentType === 'landing' &&
450
- nextAttachmentSide != null &&
451
- nextAttachmentSide !== 'front' &&
452
- nextAttachmentSide !== railingMode
453
- ? ['front', railingMode]
454
- : [railingMode];
455
- return {
456
- layout,
457
- sidePaths: sideCandidates.map((side) => buildSegmentRailPath(layouts, index, side, landingInset)),
458
- connectFromPrevious: index > 0 &&
459
- !suppressLandingRailing &&
460
- sideCandidates.length > 0 &&
461
- (layout.segment.segmentType === 'landing' ? landingContinuesOnPreferredSide : true),
462
- };
463
- });
464
- }
465
- function resolveLandingChainNextStair(layouts, index) {
466
- const layout = layouts[index];
467
- if (!layout || layout.segment.segmentType !== 'landing') {
468
- return { isTerminalLandingBeforeStair: false };
469
- }
470
- let cursor = index;
471
- while (cursor + 1 < layouts.length && layouts[cursor + 1]?.segment.segmentType === 'landing') {
472
- cursor += 1;
473
- }
474
- const nextStairLayout = cursor + 1 < layouts.length && layouts[cursor + 1]?.segment.segmentType === 'stair'
475
- ? layouts[cursor + 1]
476
- : undefined;
477
- return {
478
- nextStairLayout,
479
- isTerminalLandingBeforeStair: Boolean(nextStairLayout) && cursor === index,
480
- };
481
- }
482
- function computeStairRailLayouts(segments) {
483
- const transforms = computeSegmentTransforms(segments);
484
- return segments.map((segment, index) => {
485
- const transform = transforms[index];
486
- const [centerOffsetX, centerOffsetZ] = rotateXZ(0, segment.length / 2, transform.rotation);
487
- return {
488
- center: [transform.position[0] + centerOffsetX, transform.position[2] + centerOffsetZ],
489
- elevation: transform.position[1],
490
- rotation: transform.rotation,
491
- segment,
492
- };
493
- });
494
- }
495
- function buildSegmentRailPath(layouts, layoutIndex, side, landingInset) {
496
- const layout = layouts[layoutIndex];
497
- const segment = layout.segment;
498
- const previousLayout = layoutIndex > 0 ? layouts[layoutIndex - 1] : undefined;
499
- const nextLayout = layoutIndex >= 0 ? layouts[layoutIndex + 1] : undefined;
500
- const steps = Math.max(1, segment.segmentType === 'landing' ? 1 : segment.stepCount);
501
- const stepDepth = segment.length / steps;
502
- const stepHeight = segment.segmentType === 'landing' ? 0 : segment.height / steps;
503
- const flightSideOffset = side === 'left' ? segment.width / 2 - 0.045 : -segment.width / 2 + 0.045;
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;
510
- const landingFrontX = previousLayout?.segment.segmentType === 'stair' &&
511
- segment.attachmentSide &&
512
- segment.attachmentSide !== 'front'
513
- ? -segment.length / 2 + landingInset
514
- : segment.length / 2 - landingInset;
515
- if (segment.segmentType === 'landing') {
516
- const backX = -segment.length / 2 + landingInset;
517
- const frontX = segment.length / 2 - landingInset;
518
- const leftZ = segment.width / 2 - landingInset;
519
- const rightZ = -segment.width / 2 + landingInset;
520
- return {
521
- side,
522
- points: side === 'left'
523
- ? [
524
- [backX, 0, leftZ],
525
- [frontX, 0, leftZ],
526
- ]
527
- : side === 'right'
528
- ? [
529
- [backX, 0, rightZ],
530
- [frontX, 0, rightZ],
531
- ]
532
- : [
533
- [landingFrontX, 0, leftZ],
534
- [landingFrontX, 0, rightZ],
535
- ],
536
- };
537
- }
538
- return {
539
- side,
540
- points: [
541
- ...(previousLayout?.segment.segmentType === 'landing'
542
- ? []
543
- : [[flightStartX, stepHeight > 0 ? stepHeight : 0, flightSideOffset]]),
544
- ...Array.from({ length: steps }).map((_, index) => [
545
- -segment.length / 2 + stepDepth * index + stepDepth / 2,
546
- stepHeight * (index + 1),
547
- flightSideOffset,
548
- ]),
549
- ...(nextLayout?.segment.segmentType === 'landing'
550
- ? []
551
- : [[flightEndX, segment.height, flightSideOffset]]),
552
- ],
553
- };
554
- }
555
- function toWorldRailPoint(layout, point) {
556
- const [localX, localY, localZ] = point;
557
- const [offsetX, offsetZ] = rotateXZ(localZ, localX, layout.rotation);
558
- return [layout.center[0] + offsetX, layout.elevation + localY, layout.center[1] + offsetZ];
559
- }
560
- function computeSegmentTransforms(segments) {
561
- const transforms = [];
562
- let currentPos = new THREE.Vector3(0, 0, 0);
563
- let currentRot = 0;
564
- for (let i = 0; i < segments.length; i++) {
565
- const segment = segments[i];
566
- if (i === 0) {
567
- transforms.push({
568
- position: [currentPos.x, currentPos.y, currentPos.z],
569
- rotation: currentRot,
570
- });
571
- continue;
572
- }
573
- const prev = segments[i - 1];
574
- const localAttachPos = new THREE.Vector3();
575
- let rotChange = 0;
576
- switch (segment.attachmentSide) {
577
- case 'front':
578
- localAttachPos.set(0, prev.height, prev.length);
579
- break;
580
- case 'left':
581
- localAttachPos.set(prev.width / 2, prev.height, prev.length / 2);
582
- rotChange = Math.PI / 2;
583
- break;
584
- case 'right':
585
- localAttachPos.set(-prev.width / 2, prev.height, prev.length / 2);
586
- rotChange = -Math.PI / 2;
587
- break;
588
- }
589
- localAttachPos.applyAxisAngle(new THREE.Vector3(0, 1, 0), currentRot);
590
- currentPos = currentPos.clone().add(localAttachPos);
591
- currentRot += rotChange;
592
- transforms.push({ position: [currentPos.x, currentPos.y, currentPos.z], rotation: currentRot });
593
- }
594
- return transforms;
595
- }
596
- function rotateXZ(x, z, angle) {
597
- const cos = Math.cos(angle);
598
- const sin = Math.sin(angle);
599
- return [x * cos + z * sin, -x * sin + z * cos];
600
- }
601
- function distance3(a, b) {
602
- return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
603
- }
@@ -1,5 +0,0 @@
1
- import { type StairSegmentNode } from '@pascal-app/core';
2
- export declare const StairSegmentRenderer: ({ node }: {
3
- node: StairSegmentNode;
4
- }) => import("react/jsx-runtime").JSX.Element;
5
- //# sourceMappingURL=stair-segment-renderer.d.ts.map
@@ -1 +0,0 @@
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,4CA6DxE,CAAA"}