@pascal-app/viewer 0.7.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 (216) 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/frame-limiter.d.ts.map +1 -1
  10. package/dist/components/viewer/frame-limiter.js +5 -1
  11. package/dist/components/viewer/ground-occluder.d.ts +1 -1
  12. package/dist/components/viewer/ground-occluder.d.ts.map +1 -1
  13. package/dist/components/viewer/ground-occluder.js +12 -24
  14. package/dist/components/viewer/index.d.ts +38 -1
  15. package/dist/components/viewer/index.d.ts.map +1 -1
  16. package/dist/components/viewer/index.js +183 -60
  17. package/dist/components/viewer/lights.d.ts +1 -1
  18. package/dist/components/viewer/lights.d.ts.map +1 -1
  19. package/dist/components/viewer/lights.js +170 -43
  20. package/dist/components/viewer/perf-monitor.d.ts +1 -1
  21. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  22. package/dist/components/viewer/perf-monitor.js +99 -11
  23. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  24. package/dist/components/viewer/post-processing.js +217 -61
  25. package/dist/components/viewer/registered-systems.d.ts +12 -0
  26. package/dist/components/viewer/registered-systems.d.ts.map +1 -0
  27. package/dist/components/viewer/registered-systems.js +47 -0
  28. package/dist/components/viewer/scene-bvh.d.ts +17 -0
  29. package/dist/components/viewer/scene-bvh.d.ts.map +1 -0
  30. package/dist/components/viewer/scene-bvh.js +94 -0
  31. package/dist/components/viewer/selection-manager.d.ts +1 -1
  32. package/dist/components/viewer/selection-manager.d.ts.map +1 -1
  33. package/dist/components/viewer/selection-manager.js +20 -5
  34. package/dist/components/viewer/viewer-camera.d.ts +1 -1
  35. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  36. package/dist/components/viewer/walkthrough-controls.d.ts +1 -1
  37. package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -1
  38. package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
  39. package/dist/hooks/use-node-events.d.ts +5 -73
  40. package/dist/hooks/use-node-events.d.ts.map +1 -1
  41. package/dist/hooks/use-node-events.js +19 -7
  42. package/dist/index.d.ts +36 -4
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +67 -3
  45. package/dist/lib/csg-utils.d.ts +16 -0
  46. package/dist/lib/csg-utils.d.ts.map +1 -0
  47. package/dist/lib/csg-utils.js +33 -0
  48. package/dist/lib/drawable-geometry.d.ts +21 -0
  49. package/dist/lib/drawable-geometry.d.ts.map +1 -0
  50. package/dist/lib/drawable-geometry.js +22 -0
  51. package/dist/lib/edge-style.d.ts +3 -0
  52. package/dist/lib/edge-style.d.ts.map +1 -0
  53. package/dist/lib/edge-style.js +11 -0
  54. package/dist/lib/gpu-perf.d.ts +4 -0
  55. package/dist/lib/gpu-perf.d.ts.map +1 -0
  56. package/dist/lib/gpu-perf.js +23 -0
  57. package/dist/lib/ink-edges.d.ts +9 -0
  58. package/dist/lib/ink-edges.d.ts.map +1 -0
  59. package/dist/lib/ink-edges.js +43 -0
  60. package/dist/lib/isolation.d.ts +39 -0
  61. package/dist/lib/isolation.d.ts.map +1 -0
  62. package/dist/lib/isolation.js +103 -0
  63. package/dist/lib/layers.d.ts +18 -0
  64. package/dist/lib/layers.d.ts.map +1 -1
  65. package/dist/lib/layers.js +18 -0
  66. package/dist/lib/materials.d.ts +25 -15
  67. package/dist/lib/materials.d.ts.map +1 -1
  68. package/dist/lib/materials.js +277 -63
  69. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  70. package/dist/lib/merged-outline-node.js +11 -11
  71. package/dist/lib/polygon-union.d.ts +4 -0
  72. package/dist/lib/polygon-union.d.ts.map +1 -0
  73. package/dist/lib/polygon-union.js +289 -0
  74. package/dist/lib/polygon-union.test.d.ts +2 -0
  75. package/dist/lib/polygon-union.test.d.ts.map +1 -0
  76. package/dist/lib/polygon-union.test.js +105 -0
  77. package/dist/lib/scene-themes.d.ts +29 -0
  78. package/dist/lib/scene-themes.d.ts.map +1 -0
  79. package/dist/lib/scene-themes.js +193 -0
  80. package/dist/store/use-viewer.d.ts +42 -4
  81. package/dist/store/use-viewer.d.ts.map +1 -1
  82. package/dist/store/use-viewer.js +26 -3
  83. package/dist/systems/ceiling/ceiling-system.d.ts +6 -2
  84. package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
  85. package/dist/systems/ceiling/ceiling-system.js +62 -12
  86. package/dist/systems/door/door-system.d.ts.map +1 -1
  87. package/dist/systems/door/door-system.js +373 -22
  88. package/dist/systems/elevator/elevator-interaction-system.d.ts +2 -0
  89. package/dist/systems/elevator/elevator-interaction-system.d.ts.map +1 -0
  90. package/dist/systems/elevator/elevator-interaction-system.js +62 -0
  91. package/dist/systems/fence/fence-system.d.ts +3 -0
  92. package/dist/systems/fence/fence-system.d.ts.map +1 -1
  93. package/dist/systems/fence/fence-system.js +10 -6
  94. package/dist/systems/floor-elevation/floor-elevation-system.d.ts +20 -0
  95. package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -0
  96. package/dist/systems/floor-elevation/floor-elevation-system.js +78 -0
  97. package/dist/systems/geometry/geometry-system.d.ts +34 -0
  98. package/dist/systems/geometry/geometry-system.d.ts.map +1 -0
  99. package/dist/systems/geometry/geometry-system.js +279 -0
  100. package/dist/systems/guide/guide-system.d.ts.map +1 -1
  101. package/dist/systems/guide/guide-system.js +1 -26
  102. package/dist/systems/interactive/interactive-system.d.ts +1 -1
  103. package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
  104. package/dist/systems/item/item-system.d.ts +7 -0
  105. package/dist/systems/item/item-system.d.ts.map +1 -1
  106. package/dist/systems/item/item-system.js +8 -11
  107. package/dist/systems/item-light/item-light-system.d.ts +1 -1
  108. package/dist/systems/item-light/item-light-system.d.ts.map +1 -1
  109. package/dist/systems/item-light/item-light-system.js +18 -5
  110. package/dist/systems/roof/roof-materials.d.ts +3 -2
  111. package/dist/systems/roof/roof-materials.d.ts.map +1 -1
  112. package/dist/systems/roof/roof-materials.js +38 -14
  113. package/dist/systems/roof/roof-system.d.ts +27 -0
  114. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  115. package/dist/systems/roof/roof-system.js +411 -71
  116. package/dist/systems/scan/scan-system.d.ts.map +1 -1
  117. package/dist/systems/scan/scan-system.js +1 -26
  118. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  119. package/dist/systems/slab/slab-system.js +167 -49
  120. package/dist/systems/slab/slab-system.test.d.ts +2 -0
  121. package/dist/systems/slab/slab-system.test.d.ts.map +1 -0
  122. package/dist/systems/slab/slab-system.test.js +62 -0
  123. package/dist/systems/stair/stair-materials.d.ts +4 -3
  124. package/dist/systems/stair/stair-materials.d.ts.map +1 -1
  125. package/dist/systems/stair/stair-materials.js +30 -15
  126. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  127. package/dist/systems/stair/stair-system.js +58 -71
  128. package/dist/systems/surface-hole-geometry.d.ts +3 -0
  129. package/dist/systems/surface-hole-geometry.d.ts.map +1 -0
  130. package/dist/systems/surface-hole-geometry.js +4 -0
  131. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  132. package/dist/systems/wall/wall-cutout.js +18 -2
  133. package/dist/systems/wall/wall-materials.d.ts +4 -3
  134. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  135. package/dist/systems/wall/wall-materials.js +52 -28
  136. package/dist/systems/wall/wall-system.d.ts +1 -1
  137. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  138. package/dist/systems/wall/wall-system.js +179 -31
  139. package/dist/systems/window/window-system.d.ts.map +1 -1
  140. package/dist/systems/window/window-system.js +71 -7
  141. package/package.json +4 -5
  142. package/dist/components/renderers/building/building-renderer.d.ts +0 -5
  143. package/dist/components/renderers/building/building-renderer.d.ts.map +0 -1
  144. package/dist/components/renderers/building/building-renderer.js +0 -11
  145. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts +0 -5
  146. package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +0 -1
  147. package/dist/components/renderers/ceiling/ceiling-renderer.js +0 -58
  148. package/dist/components/renderers/column/column-renderer.d.ts +0 -5
  149. package/dist/components/renderers/column/column-renderer.d.ts.map +0 -1
  150. package/dist/components/renderers/column/column-renderer.js +0 -594
  151. package/dist/components/renderers/door/door-renderer.d.ts +0 -5
  152. package/dist/components/renderers/door/door-renderer.d.ts.map +0 -1
  153. package/dist/components/renderers/door/door-renderer.js +0 -16
  154. package/dist/components/renderers/fence/fence-renderer.d.ts +0 -5
  155. package/dist/components/renderers/fence/fence-renderer.d.ts.map +0 -1
  156. package/dist/components/renderers/fence/fence-renderer.js +0 -29
  157. package/dist/components/renderers/guide/guide-renderer.d.ts +0 -5
  158. package/dist/components/renderers/guide/guide-renderer.d.ts.map +0 -1
  159. package/dist/components/renderers/guide/guide-renderer.js +0 -38
  160. package/dist/components/renderers/item/item-renderer.d.ts +0 -5
  161. package/dist/components/renderers/item/item-renderer.d.ts.map +0 -1
  162. package/dist/components/renderers/item/item-renderer.js +0 -172
  163. package/dist/components/renderers/level/level-renderer.d.ts +0 -5
  164. package/dist/components/renderers/level/level-renderer.d.ts.map +0 -1
  165. package/dist/components/renderers/level/level-renderer.js +0 -11
  166. package/dist/components/renderers/roof/roof-materials.d.ts +0 -4
  167. package/dist/components/renderers/roof/roof-materials.d.ts.map +0 -1
  168. package/dist/components/renderers/roof/roof-materials.js +0 -16
  169. package/dist/components/renderers/roof/roof-renderer.d.ts +0 -5
  170. package/dist/components/renderers/roof/roof-renderer.d.ts.map +0 -1
  171. package/dist/components/renderers/roof/roof-renderer.js +0 -32
  172. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts +0 -5
  173. package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +0 -1
  174. package/dist/components/renderers/roof-segment/roof-segment-renderer.js +0 -40
  175. package/dist/components/renderers/scan/scan-renderer.d.ts +0 -5
  176. package/dist/components/renderers/scan/scan-renderer.d.ts.map +0 -1
  177. package/dist/components/renderers/scan/scan-renderer.js +0 -54
  178. package/dist/components/renderers/site/site-renderer.d.ts +0 -5
  179. package/dist/components/renderers/site/site-renderer.d.ts.map +0 -1
  180. package/dist/components/renderers/site/site-renderer.js +0 -106
  181. package/dist/components/renderers/slab/slab-renderer.d.ts +0 -5
  182. package/dist/components/renderers/slab/slab-renderer.d.ts.map +0 -1
  183. package/dist/components/renderers/slab/slab-renderer.js +0 -58
  184. package/dist/components/renderers/spawn/spawn-renderer.d.ts +0 -5
  185. package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +0 -1
  186. package/dist/components/renderers/spawn/spawn-renderer.js +0 -32
  187. package/dist/components/renderers/stair/stair-renderer.d.ts +0 -5
  188. package/dist/components/renderers/stair/stair-renderer.d.ts.map +0 -1
  189. package/dist/components/renderers/stair/stair-renderer.js +0 -603
  190. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts +0 -5
  191. package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +0 -1
  192. package/dist/components/renderers/stair-segment/stair-segment-renderer.js +0 -32
  193. package/dist/components/renderers/wall/wall-renderer.d.ts +0 -5
  194. package/dist/components/renderers/wall/wall-renderer.d.ts.map +0 -1
  195. package/dist/components/renderers/wall/wall-renderer.js +0 -16
  196. package/dist/components/renderers/window/window-renderer.d.ts +0 -5
  197. package/dist/components/renderers/window/window-renderer.d.ts.map +0 -1
  198. package/dist/components/renderers/window/window-renderer.js +0 -21
  199. package/dist/components/renderers/zone/zone-renderer.d.ts +0 -5
  200. package/dist/components/renderers/zone/zone-renderer.d.ts.map +0 -1
  201. package/dist/components/renderers/zone/zone-renderer.js +0 -181
  202. package/dist/lib/suppress-three-clock-warning.d.ts +0 -25
  203. package/dist/lib/suppress-three-clock-warning.d.ts.map +0 -1
  204. package/dist/lib/suppress-three-clock-warning.js +0 -51
  205. package/dist/systems/export/export-system.d.ts +0 -2
  206. package/dist/systems/export/export-system.d.ts.map +0 -1
  207. package/dist/systems/export/export-system.js +0 -60
  208. package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts +0 -14
  209. package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts.map +0 -1
  210. package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.js +0 -155
  211. package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts +0 -6
  212. package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts.map +0 -1
  213. package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.js +0 -75
  214. package/dist/systems/item-mesh-metadata/sync-request.d.ts +0 -7
  215. package/dist/systems/item-mesh-metadata/sync-request.d.ts.map +0 -1
  216. package/dist/systems/item-mesh-metadata/sync-request.js +0 -25
@@ -0,0 +1,289 @@
1
+ const EPSILON = 1e-7;
2
+ const KEY_SCALE = 1e6;
3
+ function pointsEqual(a, b, tolerance = EPSILON) {
4
+ return Math.hypot(a[0] - b[0], a[1] - b[1]) <= tolerance;
5
+ }
6
+ function pointKey(point) {
7
+ return `${Math.round(point[0] * KEY_SCALE)}:${Math.round(point[1] * KEY_SCALE)}`;
8
+ }
9
+ function interpolate(a, b, t) {
10
+ return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
11
+ }
12
+ function cross(ax, ay, bx, by) {
13
+ return ax * by - ay * bx;
14
+ }
15
+ function polygonArea(points) {
16
+ let area = 0;
17
+ for (let i = 0; i < points.length; i++) {
18
+ const current = points[i];
19
+ const next = points[(i + 1) % points.length];
20
+ area += current[0] * next[1] - next[0] * current[1];
21
+ }
22
+ return area / 2;
23
+ }
24
+ function pointOnSegment(point, start, end) {
25
+ const dx = end[0] - start[0];
26
+ const dz = end[1] - start[1];
27
+ const crossValue = cross(point[0] - start[0], point[1] - start[1], dx, dz);
28
+ if (Math.abs(crossValue) > EPSILON)
29
+ return false;
30
+ const dot = (point[0] - start[0]) * (point[0] - end[0]) + (point[1] - start[1]) * (point[1] - end[1]);
31
+ return dot <= EPSILON;
32
+ }
33
+ function pointInPolygonOrOnBoundary(point, polygon) {
34
+ if (polygon.some((start, index) => pointOnSegment(point, start, polygon[(index + 1) % polygon.length]))) {
35
+ return true;
36
+ }
37
+ return pointInPolygon(point, polygon);
38
+ }
39
+ function pointInPolygon(point, polygon) {
40
+ let inside = false;
41
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
42
+ const pi = polygon[i];
43
+ const pj = polygon[j];
44
+ if (pointOnSegment(point, pj, pi))
45
+ return false;
46
+ const intersects = pi[1] > point[1] !== pj[1] > point[1] &&
47
+ point[0] < ((pj[0] - pi[0]) * (point[1] - pi[1])) / (pj[1] - pi[1]) + pi[0];
48
+ if (intersects)
49
+ inside = !inside;
50
+ }
51
+ return inside;
52
+ }
53
+ function normalizeRing(ring) {
54
+ const normalized = [];
55
+ for (const [x, z] of ring) {
56
+ if (!Number.isFinite(x) || !Number.isFinite(z))
57
+ continue;
58
+ const point = [x, z];
59
+ const previous = normalized[normalized.length - 1];
60
+ if (!previous || !pointsEqual(previous, point)) {
61
+ normalized.push(point);
62
+ }
63
+ }
64
+ const first = normalized[0];
65
+ const last = normalized[normalized.length - 1];
66
+ if (first && last && pointsEqual(first, last)) {
67
+ normalized.pop();
68
+ }
69
+ if (normalized.length < 3 || Math.abs(polygonArea(normalized)) <= EPSILON)
70
+ return [];
71
+ return polygonArea(normalized) < 0 ? [...normalized].reverse() : normalized;
72
+ }
73
+ function addSplit(edge, t) {
74
+ if (t < -EPSILON || t > 1 + EPSILON)
75
+ return;
76
+ const clamped = Math.max(0, Math.min(1, t));
77
+ if (edge.splits.some((split) => Math.abs(split - clamped) <= EPSILON))
78
+ return;
79
+ edge.splits.push(clamped);
80
+ }
81
+ function parameterOnEdge(point, edge) {
82
+ const dx = edge.end[0] - edge.start[0];
83
+ const dz = edge.end[1] - edge.start[1];
84
+ const lengthSquared = dx * dx + dz * dz;
85
+ if (lengthSquared <= EPSILON)
86
+ return 0;
87
+ return ((point[0] - edge.start[0]) * dx + (point[1] - edge.start[1]) * dz) / lengthSquared;
88
+ }
89
+ function addIntersectionSplits(left, right) {
90
+ const rx = left.end[0] - left.start[0];
91
+ const rz = left.end[1] - left.start[1];
92
+ const sx = right.end[0] - right.start[0];
93
+ const sz = right.end[1] - right.start[1];
94
+ const qpx = right.start[0] - left.start[0];
95
+ const qpz = right.start[1] - left.start[1];
96
+ const denominator = cross(rx, rz, sx, sz);
97
+ const numerator = cross(qpx, qpz, rx, rz);
98
+ if (Math.abs(denominator) <= EPSILON) {
99
+ if (Math.abs(numerator) > EPSILON)
100
+ return;
101
+ for (const point of [left.start, left.end, right.start, right.end]) {
102
+ if (pointOnSegment(point, left.start, left.end) &&
103
+ pointOnSegment(point, right.start, right.end)) {
104
+ addSplit(left, parameterOnEdge(point, left));
105
+ addSplit(right, parameterOnEdge(point, right));
106
+ }
107
+ }
108
+ return;
109
+ }
110
+ const t = cross(qpx, qpz, sx, sz) / denominator;
111
+ const u = cross(qpx, qpz, rx, rz) / denominator;
112
+ if (t < -EPSILON || t > 1 + EPSILON || u < -EPSILON || u > 1 + EPSILON)
113
+ return;
114
+ addSplit(left, t);
115
+ addSplit(right, u);
116
+ }
117
+ function buildEdges(polygons) {
118
+ const edges = [];
119
+ polygons.forEach((polygon, polygonIndex) => {
120
+ for (let i = 0; i < polygon.length; i++) {
121
+ edges.push({
122
+ start: polygon[i],
123
+ end: polygon[(i + 1) % polygon.length],
124
+ polygonIndex,
125
+ splits: [0, 1],
126
+ });
127
+ }
128
+ });
129
+ for (let i = 0; i < edges.length; i++) {
130
+ for (let j = i + 1; j < edges.length; j++) {
131
+ const left = edges[i];
132
+ const right = edges[j];
133
+ if (left.polygonIndex === right.polygonIndex)
134
+ continue;
135
+ addIntersectionSplits(left, right);
136
+ }
137
+ }
138
+ return edges;
139
+ }
140
+ function buildBoundarySegments(edges, polygons) {
141
+ const segments = [];
142
+ for (const edge of edges) {
143
+ const splits = [...edge.splits].sort((a, b) => a - b);
144
+ for (let i = 0; i < splits.length - 1; i++) {
145
+ const startT = splits[i];
146
+ const endT = splits[i + 1];
147
+ if (endT - startT <= EPSILON)
148
+ continue;
149
+ const start = interpolate(edge.start, edge.end, startT);
150
+ const end = interpolate(edge.start, edge.end, endT);
151
+ const mid = interpolate(edge.start, edge.end, (startT + endT) / 2);
152
+ const insideAnother = polygons.some((polygon, index) => index !== edge.polygonIndex && pointInPolygon(mid, polygon));
153
+ if (!insideAnother) {
154
+ segments.push({ start, end, used: false });
155
+ }
156
+ }
157
+ }
158
+ return removeDuplicateInteriorSegments(segments);
159
+ }
160
+ function segmentKey(segment) {
161
+ const start = pointKey(segment.start);
162
+ const end = pointKey(segment.end);
163
+ return start < end ? `${start}|${end}` : `${end}|${start}`;
164
+ }
165
+ function removeDuplicateInteriorSegments(segments) {
166
+ const groups = new Map();
167
+ for (const segment of segments) {
168
+ const key = segmentKey(segment);
169
+ const group = groups.get(key);
170
+ if (group) {
171
+ group.push(segment);
172
+ }
173
+ else {
174
+ groups.set(key, [segment]);
175
+ }
176
+ }
177
+ const result = [];
178
+ for (const group of groups.values()) {
179
+ if (group.length === 1) {
180
+ result.push(group[0]);
181
+ continue;
182
+ }
183
+ const firstStart = pointKey(group[0].start);
184
+ const firstEnd = pointKey(group[0].end);
185
+ const hasOppositeDirection = group.some((segment) => pointKey(segment.start) === firstEnd && pointKey(segment.end) === firstStart);
186
+ if (!hasOppositeDirection) {
187
+ result.push(group[0]);
188
+ }
189
+ }
190
+ return result;
191
+ }
192
+ function assembleRings(segments) {
193
+ const byStart = new Map();
194
+ for (const segment of segments) {
195
+ const key = pointKey(segment.start);
196
+ const group = byStart.get(key);
197
+ if (group) {
198
+ group.push(segment);
199
+ }
200
+ else {
201
+ byStart.set(key, [segment]);
202
+ }
203
+ }
204
+ const rings = [];
205
+ for (const firstSegment of segments) {
206
+ if (firstSegment.used)
207
+ continue;
208
+ firstSegment.used = true;
209
+ const ring = [firstSegment.start, firstSegment.end];
210
+ const startKey = pointKey(firstSegment.start);
211
+ let currentKey = pointKey(firstSegment.end);
212
+ while (currentKey !== startKey) {
213
+ const next = byStart.get(currentKey)?.find((segment) => !segment.used);
214
+ if (!next)
215
+ break;
216
+ next.used = true;
217
+ ring.push(next.end);
218
+ currentKey = pointKey(next.end);
219
+ }
220
+ if (currentKey !== startKey)
221
+ continue;
222
+ const last = ring[ring.length - 1];
223
+ if (last && pointsEqual(ring[0], last)) {
224
+ ring.pop();
225
+ }
226
+ const normalized = normalizeRing(ring);
227
+ if (normalized.length >= 3) {
228
+ rings.push(normalized);
229
+ }
230
+ }
231
+ return rings;
232
+ }
233
+ export function unionPolygons(polygons) {
234
+ const validPolygons = polygons.map(normalizeRing).filter((polygon) => polygon.length >= 3);
235
+ if (validPolygons.length <= 1)
236
+ return validPolygons;
237
+ const edges = buildEdges(validPolygons);
238
+ const segments = buildBoundarySegments(edges, validPolygons);
239
+ const rings = assembleRings(segments);
240
+ return rings.length > 0 ? rings : validPolygons;
241
+ }
242
+ function buildDifferenceBoundarySegments(edges, polygons) {
243
+ const subject = polygons[0];
244
+ const cutters = polygons.slice(1);
245
+ if (!subject)
246
+ return [];
247
+ const segments = [];
248
+ for (const edge of edges) {
249
+ const splits = [...edge.splits].sort((a, b) => a - b);
250
+ for (let i = 0; i < splits.length - 1; i++) {
251
+ const startT = splits[i];
252
+ const endT = splits[i + 1];
253
+ if (endT - startT <= EPSILON)
254
+ continue;
255
+ const start = interpolate(edge.start, edge.end, startT);
256
+ const end = interpolate(edge.start, edge.end, endT);
257
+ const mid = interpolate(edge.start, edge.end, (startT + endT) / 2);
258
+ if (edge.polygonIndex === 0) {
259
+ const insideCutter = cutters.some((cutter) => pointInPolygonOrOnBoundary(mid, cutter));
260
+ if (!insideCutter) {
261
+ segments.push({ start, end, used: false });
262
+ }
263
+ continue;
264
+ }
265
+ const insideSubject = pointInPolygon(mid, subject);
266
+ const insideAnotherCutter = cutters.some((cutter, cutterIndex) => cutterIndex !== edge.polygonIndex - 1 && pointInPolygonOrOnBoundary(mid, cutter));
267
+ if (insideSubject && !insideAnotherCutter) {
268
+ segments.push({ start: end, end: start, used: false });
269
+ }
270
+ }
271
+ }
272
+ return removeDuplicateInteriorSegments(segments);
273
+ }
274
+ export function subtractPolygonsFromPolygon(subject, cutters) {
275
+ const validSubject = normalizeRing(subject);
276
+ if (validSubject.length < 3)
277
+ return [];
278
+ const validCutters = cutters.map(normalizeRing).filter((polygon) => polygon.length >= 3);
279
+ if (validCutters.length === 0)
280
+ return [validSubject];
281
+ const polygons = [validSubject, ...validCutters];
282
+ const edges = buildEdges(polygons);
283
+ const segments = buildDifferenceBoundarySegments(edges, polygons);
284
+ const rings = assembleRings(segments);
285
+ if (rings.length > 0)
286
+ return rings;
287
+ const fullyCovered = validSubject.every((point) => validCutters.some((cutter) => pointInPolygonOrOnBoundary(point, cutter)));
288
+ return fullyCovered ? [] : [validSubject];
289
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=polygon-union.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polygon-union.test.d.ts","sourceRoot":"","sources":["../../src/lib/polygon-union.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,105 @@
1
+ // @ts-expect-error — bun:test is provided by the Bun runtime; viewer does not
2
+ // depend on @types/bun so the import type is unresolved at compile time.
3
+ import { describe, expect, test } from 'bun:test';
4
+ import { subtractPolygonsFromPolygon, unionPolygons } from './polygon-union';
5
+ function polygonArea(points) {
6
+ let area = 0;
7
+ for (let i = 0; i < points.length; i++) {
8
+ const current = points[i];
9
+ const next = points[(i + 1) % points.length];
10
+ area += current[0] * next[1] - next[0] * current[1];
11
+ }
12
+ return Math.abs(area / 2);
13
+ }
14
+ describe('unionPolygons', () => {
15
+ test('collapses a contained polygon into the containing polygon', () => {
16
+ const small = [
17
+ [0, 0],
18
+ [1, 0],
19
+ [1, 1],
20
+ [0, 1],
21
+ ];
22
+ const large = [
23
+ [-1, -1],
24
+ [2, -1],
25
+ [2, 2],
26
+ [-1, 2],
27
+ ];
28
+ const result = unionPolygons([small, large]);
29
+ expect(result).toHaveLength(1);
30
+ expect(polygonArea(result[0])).toBeCloseTo(9);
31
+ });
32
+ test('combines overlapping rectangles into one boundary', () => {
33
+ const left = [
34
+ [0, 0],
35
+ [2, 0],
36
+ [2, 2],
37
+ [0, 2],
38
+ ];
39
+ const right = [
40
+ [1, 1],
41
+ [3, 1],
42
+ [3, 3],
43
+ [1, 3],
44
+ ];
45
+ const result = unionPolygons([left, right]);
46
+ expect(result).toHaveLength(1);
47
+ expect(result[0]).toHaveLength(8);
48
+ expect(polygonArea(result[0])).toBeCloseTo(7);
49
+ });
50
+ test('keeps disjoint polygons as separate boundaries', () => {
51
+ const left = [
52
+ [0, 0],
53
+ [1, 0],
54
+ [1, 1],
55
+ [0, 1],
56
+ ];
57
+ const right = [
58
+ [2, 0],
59
+ [3, 0],
60
+ [3, 1],
61
+ [2, 1],
62
+ ];
63
+ const result = unionPolygons([left, right]);
64
+ expect(result).toHaveLength(2);
65
+ expect(result.map(polygonArea)).toEqual([1, 1]);
66
+ });
67
+ });
68
+ describe('subtractPolygonsFromPolygon', () => {
69
+ test('turns a boundary-overlapping cutter into an indentation', () => {
70
+ const slab = [
71
+ [0, 0],
72
+ [4, 0],
73
+ [4, 3],
74
+ [0, 3],
75
+ ];
76
+ const cutout = [
77
+ [1, -0.5],
78
+ [3, -0.5],
79
+ [3, 1],
80
+ [1, 1],
81
+ ];
82
+ const result = subtractPolygonsFromPolygon(slab, [cutout]);
83
+ expect(result).toHaveLength(1);
84
+ expect(result[0]).toContainEqual([1, 1]);
85
+ expect(result[0]).toContainEqual([3, 1]);
86
+ expect(polygonArea(result[0])).toBeCloseTo(10);
87
+ });
88
+ test('returns separate contours when a cutter splits the subject', () => {
89
+ const slab = [
90
+ [0, 0],
91
+ [4, 0],
92
+ [4, 3],
93
+ [0, 3],
94
+ ];
95
+ const cutout = [
96
+ [1.5, -1],
97
+ [2.5, -1],
98
+ [2.5, 4],
99
+ [1.5, 4],
100
+ ];
101
+ const result = subtractPolygonsFromPolygon(slab, [cutout]);
102
+ expect(result).toHaveLength(2);
103
+ expect(result.map(polygonArea).sort((a, b) => a - b)).toEqual([4.5, 4.5]);
104
+ });
105
+ });
@@ -0,0 +1,29 @@
1
+ import type { SurfaceRole } from '@pascal-app/core';
2
+ export type SceneTheme = {
3
+ id: string;
4
+ name: string;
5
+ appearance: 'light' | 'dark';
6
+ background: string;
7
+ ground: string;
8
+ ambient: {
9
+ color: string;
10
+ intensity: number;
11
+ };
12
+ hemi?: {
13
+ sky: string;
14
+ ground: string;
15
+ intensity: number;
16
+ };
17
+ lights: Array<{
18
+ position: [number, number, number];
19
+ color: string;
20
+ intensity: number;
21
+ castShadow?: boolean;
22
+ }>;
23
+ toneMappingExposure: number;
24
+ clayTints?: Partial<Record<SurfaceRole, string>>;
25
+ };
26
+ export declare const SCENE_THEMES: SceneTheme[];
27
+ export declare const SCENE_THEME_IDS: string[];
28
+ export declare function getSceneTheme(id: string): SceneTheme;
29
+ //# sourceMappingURL=scene-themes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-themes.d.ts","sourceRoot":"","sources":["../../src/lib/scene-themes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEnD,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IAIZ,UAAU,EAAE,OAAO,GAAG,MAAM,CAAA;IAC5B,UAAU,EAAE,MAAM,CAAA;IAIlB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IACzD,MAAM,EAAE,KAAK,CAAC;QACZ,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;QAClC,KAAK,EAAE,MAAM,CAAA;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,OAAO,CAAA;KACrB,CAAC,CAAA;IACF,mBAAmB,EAAE,MAAM,CAAA;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAA;CACjD,CAAA;AAED,eAAO,MAAM,YAAY,EAAE,UAAU,EA2LpC,CAAA;AAED,eAAO,MAAM,eAAe,UAAwC,CAAA;AAIpE,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,CAEpD"}
@@ -0,0 +1,193 @@
1
+ export const SCENE_THEMES = [
2
+ {
3
+ id: 'studio',
4
+ name: 'Studio',
5
+ appearance: 'light',
6
+ background: '#ffffff',
7
+ ground: '#f4f4f2',
8
+ ambient: { color: '#ffffff', intensity: 0.15 },
9
+ hemi: { sky: '#ffffff', ground: '#aaa49a', intensity: 0.6 },
10
+ lights: [
11
+ { position: [10, 10, 10], color: '#ffffff', intensity: 4, castShadow: true },
12
+ { position: [-10, 10, -10], color: '#ffffff', intensity: 0.75 },
13
+ ],
14
+ toneMappingExposure: 0.9,
15
+ clayTints: {
16
+ wall: '#e9e5db',
17
+ floor: '#d8d2c4',
18
+ ceiling: '#f1ede4',
19
+ roof: '#c4bba6',
20
+ glazing: '#cdd8df',
21
+ },
22
+ },
23
+ {
24
+ id: 'paper',
25
+ name: 'Paper',
26
+ appearance: 'light',
27
+ background: '#ede9df',
28
+ ground: '#e7e1d3',
29
+ ambient: { color: '#fff9eb', intensity: 0.55 },
30
+ hemi: { sky: '#fff5d9', ground: '#c2b89c', intensity: 0.35 },
31
+ lights: [
32
+ { position: [16, 22, 12], color: '#fff1c8', intensity: 2.6, castShadow: true },
33
+ { position: [-14, 10, -6], color: '#dde5ff', intensity: 0.35 },
34
+ ],
35
+ toneMappingExposure: 1,
36
+ clayTints: {
37
+ wall: '#efe9da',
38
+ floor: '#ddd4bf',
39
+ ceiling: '#f5efe0',
40
+ roof: '#b9b09a',
41
+ glazing: '#cdd5d8',
42
+ },
43
+ },
44
+ {
45
+ id: 'sunset',
46
+ name: 'Sunset',
47
+ appearance: 'light',
48
+ background: '#f6e8d4',
49
+ ground: '#ecd9bf',
50
+ ambient: { color: '#ffd9a8', intensity: 0.45 },
51
+ hemi: { sky: '#ffd9a8', ground: '#5b4634', intensity: 0.4 },
52
+ lights: [
53
+ { position: [22, 8, 8], color: '#ffb070', intensity: 3.4, castShadow: true },
54
+ { position: [-14, 16, -10], color: '#a4b8ff', intensity: 0.4 },
55
+ ],
56
+ toneMappingExposure: 1,
57
+ clayTints: {
58
+ wall: '#f3e3cf',
59
+ floor: '#e2cdab',
60
+ ceiling: '#f6e7d2',
61
+ roof: '#a6764f',
62
+ glazing: '#e7c9a8',
63
+ },
64
+ },
65
+ {
66
+ id: 'overcast',
67
+ name: 'Overcast',
68
+ appearance: 'light',
69
+ background: '#e6e7e6',
70
+ ground: '#dadcd9',
71
+ ambient: { color: '#eef0ef', intensity: 1.1 },
72
+ hemi: { sky: '#f4f5f3', ground: '#bcbfbb', intensity: 0.9 },
73
+ lights: [{ position: [12, 28, 10], color: '#f4f5f3', intensity: 0.8, castShadow: true }],
74
+ toneMappingExposure: 0.95,
75
+ clayTints: {
76
+ wall: '#dedfdc',
77
+ floor: '#cdcec9',
78
+ ceiling: '#e8e9e6',
79
+ roof: '#a3a49e',
80
+ glazing: '#c6cdd0',
81
+ },
82
+ },
83
+ {
84
+ id: 'blueprint',
85
+ name: 'Blueprint',
86
+ appearance: 'light',
87
+ background: '#dde6ef',
88
+ ground: '#c9d6e6',
89
+ ambient: { color: '#cfdcec', intensity: 0.7 },
90
+ hemi: { sky: '#dfeaf6', ground: '#5b6b80', intensity: 0.55 },
91
+ lights: [
92
+ { position: [16, 24, 12], color: '#e6efff', intensity: 1.8, castShadow: true },
93
+ { position: [-12, 10, -8], color: '#9fb6d8', intensity: 0.4 },
94
+ ],
95
+ toneMappingExposure: 0.95,
96
+ clayTints: {
97
+ wall: '#9fb6d2',
98
+ floor: '#8ba2c2',
99
+ ceiling: '#aec0d8',
100
+ roof: '#5f789b',
101
+ glazing: '#b6d7ea',
102
+ },
103
+ },
104
+ {
105
+ id: 'mediterranean',
106
+ name: 'Mediterranean',
107
+ appearance: 'light',
108
+ background: '#bdd6e8',
109
+ ground: '#ddd2bb',
110
+ ambient: { color: '#d6e6f3', intensity: 0.5 },
111
+ hemi: { sky: '#a8c8e2', ground: '#d8c9a4', intensity: 0.6 },
112
+ lights: [
113
+ { position: [18, 20, 12], color: '#fff4d4', intensity: 3.6, castShadow: true },
114
+ { position: [-12, 8, -8], color: '#8fb3d8', intensity: 0.7 },
115
+ ],
116
+ toneMappingExposure: 0.9,
117
+ clayTints: {
118
+ wall: '#f6f1e6',
119
+ floor: '#e0d6c2',
120
+ ceiling: '#f3ede0',
121
+ roof: '#3e6585',
122
+ glazing: '#bcd3e2',
123
+ },
124
+ },
125
+ {
126
+ id: 'twilight',
127
+ name: 'Twilight',
128
+ appearance: 'dark',
129
+ background: '#3a3550',
130
+ ground: '#4a4566',
131
+ ambient: { color: '#a89cc8', intensity: 0.35 },
132
+ hemi: { sky: '#d8a8c0', ground: '#1c1830', intensity: 0.5 },
133
+ lights: [
134
+ { position: [-14, 22, -10], color: '#a4b6e8', intensity: 1.4, castShadow: true },
135
+ { position: [14, 6, 8], color: '#ffb070', intensity: 0.9 },
136
+ ],
137
+ toneMappingExposure: 1.1,
138
+ clayTints: {
139
+ wall: '#c5b9cf',
140
+ floor: '#ad9fbb',
141
+ ceiling: '#d2c6dc',
142
+ roof: '#5b4f74',
143
+ glazing: '#c3b6d4',
144
+ },
145
+ },
146
+ {
147
+ id: 'night',
148
+ name: 'Night',
149
+ appearance: 'dark',
150
+ background: '#1f2433',
151
+ ground: '#2b3247',
152
+ ambient: { color: '#a0b0ff', intensity: 0.07 },
153
+ hemi: { sky: '#3a4666', ground: '#0e111c', intensity: 0.4 },
154
+ lights: [
155
+ { position: [10, 10, 10], color: '#e0e5ff', intensity: 0.8, castShadow: true },
156
+ { position: [-10, 10, -10], color: '#8090ff', intensity: 0.2 },
157
+ ],
158
+ toneMappingExposure: 0.9,
159
+ clayTints: {
160
+ wall: '#aab3c6',
161
+ floor: '#98a1b5',
162
+ ceiling: '#b7bfd0',
163
+ roof: '#5b6680',
164
+ glazing: '#aebbd0',
165
+ },
166
+ },
167
+ {
168
+ id: 'verdant',
169
+ name: 'Verdant',
170
+ appearance: 'light',
171
+ background: '#d6e4d2',
172
+ ground: '#c7d6b4',
173
+ ambient: { color: '#e3efdd', intensity: 0.5 },
174
+ hemi: { sky: '#cfe6cf', ground: '#8ea06f', intensity: 0.65 },
175
+ lights: [
176
+ { position: [16, 22, 12], color: '#fff6d8', intensity: 3, castShadow: true },
177
+ { position: [-12, 10, -8], color: '#bfe0c2', intensity: 0.5 },
178
+ ],
179
+ toneMappingExposure: 0.95,
180
+ clayTints: {
181
+ wall: '#eef0e6',
182
+ floor: '#d8ddc6',
183
+ ceiling: '#f1f3ea',
184
+ roof: '#6f8a5a',
185
+ glazing: '#c4dcd0',
186
+ },
187
+ },
188
+ ];
189
+ export const SCENE_THEME_IDS = SCENE_THEMES.map((theme) => theme.id);
190
+ const SCENE_THEME_BY_ID = new Map(SCENE_THEMES.map((theme) => [theme.id, theme]));
191
+ export function getSceneTheme(id) {
192
+ return SCENE_THEME_BY_ID.get(id) ?? SCENE_THEMES[0];
193
+ }