@crystaldesign/real-time-viewer-next 26.11.0-beta.24 → 26.11.0-beta.26

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 (87) hide show
  1. package/build/esm/{DeckCutoutSubsystem-4369a7a2.js → DeckCutoutSubsystem-9560be24.js} +258 -174
  2. package/build/esm/{SunLensflare-6d65fd17.js → SunLensflare-5113b471.js} +6 -3
  3. package/build/esm/{WaterSurface-95b630aa.js → WaterSurface-c54c2940.js} +115 -79
  4. package/build/esm/{index-fa3c5a24.js → index-8a26c68f.js} +4348 -2915
  5. package/build/esm/index.js +7 -4
  6. package/build/types/real-time-viewer-next/src/RealTimeRendererNext.d.ts.map +1 -1
  7. package/build/types/real-time-viewer-next/src/config/environment.config.d.ts +2 -0
  8. package/build/types/real-time-viewer-next/src/config/environment.config.d.ts.map +1 -1
  9. package/build/types/real-time-viewer-next/src/core/AssetErrorGate.d.ts +7 -0
  10. package/build/types/real-time-viewer-next/src/core/AssetErrorGate.d.ts.map +1 -0
  11. package/build/types/real-time-viewer-next/src/core/ErrorView.d.ts +16 -0
  12. package/build/types/real-time-viewer-next/src/core/ErrorView.d.ts.map +1 -0
  13. package/build/types/real-time-viewer-next/src/core/GeoInstance.d.ts +3 -1
  14. package/build/types/real-time-viewer-next/src/core/GeoInstance.d.ts.map +1 -1
  15. package/build/types/real-time-viewer-next/src/core/RTVErrorBoundary.d.ts.map +1 -1
  16. package/build/types/real-time-viewer-next/src/core/SceneRenderer.d.ts.map +1 -1
  17. package/build/types/real-time-viewer-next/src/core/SceneTree.d.ts +1 -1
  18. package/build/types/real-time-viewer-next/src/core/SceneTree.d.ts.map +1 -1
  19. package/build/types/real-time-viewer-next/src/core/VariantMeshOverride.d.ts +1 -1
  20. package/build/types/real-time-viewer-next/src/core/VariantMeshOverride.d.ts.map +1 -1
  21. package/build/types/real-time-viewer-next/src/core/dimension-lines/DimensionLines3D.d.ts.map +1 -1
  22. package/build/types/real-time-viewer-next/src/core/environment/EnvironmentSceneLoader.d.ts +5 -0
  23. package/build/types/real-time-viewer-next/src/core/environment/EnvironmentSceneLoader.d.ts.map +1 -1
  24. package/build/types/real-time-viewer-next/src/core/environment/SceneEnvironment.d.ts.map +1 -1
  25. package/build/types/real-time-viewer-next/src/core/environment/SkydomeBackground.d.ts.map +1 -1
  26. package/build/types/real-time-viewer-next/src/core/environment/deckCutout/DeckCutoutArticleSync.d.ts.map +1 -1
  27. package/build/types/real-time-viewer-next/src/core/environment/deckCutout/DeckCutoutCollar.d.ts.map +1 -1
  28. package/build/types/real-time-viewer-next/src/core/environment/deckCutout/articleFootprint.d.ts +3 -1
  29. package/build/types/real-time-viewer-next/src/core/environment/deckCutout/articleFootprint.d.ts.map +1 -1
  30. package/build/types/real-time-viewer-next/src/core/environment/deckCutout/buildCollarGeometry.d.ts.map +1 -1
  31. package/build/types/real-time-viewer-next/src/core/environment/deckCutout/measureDiagnostics.d.ts +2 -1
  32. package/build/types/real-time-viewer-next/src/core/environment/deckCutout/measureDiagnostics.d.ts.map +1 -1
  33. package/build/types/real-time-viewer-next/src/core/environment/useEnvironmentMap.d.ts.map +1 -1
  34. package/build/types/real-time-viewer-next/src/core/interaction/FreeGridLayer.d.ts.map +1 -1
  35. package/build/types/real-time-viewer-next/src/core/interaction/InteractionGuides.d.ts.map +1 -1
  36. package/build/types/real-time-viewer-next/src/core/materials/caches.d.ts.map +1 -1
  37. package/build/types/real-time-viewer-next/src/core/materials/factory.d.ts.map +1 -1
  38. package/build/types/real-time-viewer-next/src/core/materials/materials.config.d.ts +15 -0
  39. package/build/types/real-time-viewer-next/src/core/materials/materials.config.d.ts.map +1 -1
  40. package/build/types/real-time-viewer-next/src/core/materials/textureCache.d.ts +14 -0
  41. package/build/types/real-time-viewer-next/src/core/materials/textureCache.d.ts.map +1 -1
  42. package/build/types/real-time-viewer-next/src/core/meshBVH.d.ts.map +1 -1
  43. package/build/types/real-time-viewer-next/src/core/postprocessing/PostProcessingStack.d.ts.map +1 -1
  44. package/build/types/real-time-viewer-next/src/core/useFrameloopVisibility.d.ts.map +1 -1
  45. package/build/types/real-time-viewer-next/src/core/view2d/Element2DPlane.d.ts.map +1 -1
  46. package/build/types/real-time-viewer-next/src/core/water/CausticReceiver.d.ts +2 -0
  47. package/build/types/real-time-viewer-next/src/core/water/CausticReceiver.d.ts.map +1 -1
  48. package/build/types/real-time-viewer-next/src/core/water/CausticsTexture.d.ts.map +1 -1
  49. package/build/types/real-time-viewer-next/src/core/water/WaterSurface.d.ts.map +1 -1
  50. package/build/types/real-time-viewer-next/src/core/water/WaterSurfaceRenderer.d.ts.map +1 -1
  51. package/build/types/real-time-viewer-next/src/core/water/waterShaders.d.ts.map +1 -1
  52. package/build/types/real-time-viewer-next/src/hooks/useStableEnvSlice.d.ts +9 -0
  53. package/build/types/real-time-viewer-next/src/hooks/useStableEnvSlice.d.ts.map +1 -0
  54. package/build/types/real-time-viewer-next/src/presets/polyfaser-pool.d.ts.map +1 -1
  55. package/build/types/real-time-viewer-next/src/store/index.d.ts.map +1 -1
  56. package/build/types/real-time-viewer-next/src/store/slices/assetErrors.d.ts +15 -0
  57. package/build/types/real-time-viewer-next/src/store/slices/assetErrors.d.ts.map +1 -0
  58. package/build/types/real-time-viewer-next/src/store/slices/glGeneration.d.ts +12 -0
  59. package/build/types/real-time-viewer-next/src/store/slices/glGeneration.d.ts.map +1 -1
  60. package/build/types/real-time-viewer-next/src/store/types.d.ts +2 -1
  61. package/build/types/real-time-viewer-next/src/store/types.d.ts.map +1 -1
  62. package/build/types/real-time-viewer-next/src/utils/equirectCache.d.ts +16 -0
  63. package/build/types/real-time-viewer-next/src/utils/equirectCache.d.ts.map +1 -1
  64. package/build/types/real-time-viewer-next/src/utils/footprintTrace.d.ts.map +1 -1
  65. package/build/types/real-time-viewer-next/src/utils/gltfLoader.d.ts +20 -0
  66. package/build/types/real-time-viewer-next/src/utils/gltfLoader.d.ts.map +1 -1
  67. package/build/types/real-time-viewer-next/src/utils/gltfLoader.preview.test.d.ts +5 -0
  68. package/build/types/real-time-viewer-next/src/utils/gltfLoader.preview.test.d.ts.map +1 -0
  69. package/build/types/real-time-viewer-next/src/utils/gpuMemoryAudit.d.ts +12 -0
  70. package/build/types/real-time-viewer-next/src/utils/gpuMemoryAudit.d.ts.map +1 -0
  71. package/build/types/real-time-viewer-next/src/utils/polygon2d.d.ts.map +1 -1
  72. package/build/types/real-time-viewer-next/src/utils/profiles.d.ts.map +1 -1
  73. package/build/types/real-time-viewer-next/src/utils/rasterMask.d.ts +1 -0
  74. package/build/types/real-time-viewer-next/src/utils/rasterMask.d.ts.map +1 -1
  75. package/build/types/real-time-viewer-next/src/utils/suspense.d.ts +2 -0
  76. package/build/types/real-time-viewer-next/src/utils/suspense.d.ts.map +1 -1
  77. package/build/types/real-time-viewer-next/src/utils/suspense.test.d.ts +2 -0
  78. package/build/types/real-time-viewer-next/src/utils/suspense.test.d.ts.map +1 -0
  79. package/build/types/real-time-viewer-next/src/utils/textureSourceRelease.d.ts +23 -0
  80. package/build/types/real-time-viewer-next/src/utils/textureSourceRelease.d.ts.map +1 -0
  81. package/build/types/real-time-viewer-next/src/utils/textureSourceRelease.test.d.ts +5 -0
  82. package/build/types/real-time-viewer-next/src/utils/textureSourceRelease.test.d.ts.map +1 -0
  83. package/build/umd/699.real-time-viewer-next.umd.min.js +1 -1
  84. package/build/umd/964.real-time-viewer-next.umd.min.js +1 -1
  85. package/build/umd/real-time-viewer-next.umd.min.js +1 -1
  86. package/build/umd/report.html +1 -1
  87. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
- import { p as parseElementKey, u as useRTVStore, b as bootPhase, D as DECK_COLLAR_LOAD_ID } from './index-fa3c5a24.js';
2
- import { useMemo, useRef, useEffect, useState, useCallback } from 'react';
1
+ import { p as parseElementKey, u as useStableEnvSlice, a as useRTVStore, b as bootPhase, D as DECK_COLLAR_LOAD_ID } from './index-8a26c68f.js';
2
+ import { useRef, useEffect, useMemo, useState, useCallback } from 'react';
3
3
  import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
4
4
  import _defineProperty from '@babel/runtime/helpers/defineProperty';
5
5
  import { useThree, useFrame } from '@react-three/fiber';
@@ -9,12 +9,12 @@ import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
9
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
10
  import '@babel/runtime/helpers/asyncToGenerator';
11
11
  import '@babel/runtime/regenerator';
12
- import 'three/examples/jsm/loaders/DRACOLoader.js';
13
- import 'three/examples/jsm/loaders/GLTFLoader.js';
14
12
  import 'zustand';
15
13
  import 'zustand/middleware';
16
14
  import '@babel/runtime/helpers/typeof';
17
15
  import 'postprocessing';
16
+ import 'three/examples/jsm/loaders/DRACOLoader.js';
17
+ import 'three/examples/jsm/loaders/GLTFLoader.js';
18
18
  import '@react-three/drei';
19
19
  import '@babel/runtime/helpers/classCallCheck';
20
20
  import '@babel/runtime/helpers/createClass';
@@ -23,6 +23,9 @@ import '@babel/runtime/helpers/getPrototypeOf';
23
23
  import '@babel/runtime/helpers/inherits';
24
24
  import 'classnames';
25
25
  import 'three-mesh-bvh';
26
+ import 'three/examples/jsm/lines/LineSegments2.js';
27
+ import 'three/examples/jsm/lines/LineSegmentsGeometry.js';
28
+ import 'three/examples/jsm/lines/LineMaterial.js';
26
29
  import '@babel/runtime/helpers/objectWithoutProperties';
27
30
  import 'three/examples/jsm/environments/RoomEnvironment.js';
28
31
  import 'three/examples/jsm/loaders/RGBELoader.js';
@@ -133,9 +136,9 @@ function measureArticle(scene, patterns, elementsInfo) {
133
136
  };
134
137
  }
135
138
 
136
- function _createForOfIteratorHelper$6(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$6(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
137
- function _unsupportedIterableToArray$6(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$6(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$6(r, a) : void 0; } }
138
- function _arrayLikeToArray$6(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
139
+ function _createForOfIteratorHelper$7(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$7(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
140
+ function _unsupportedIterableToArray$7(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$7(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$7(r, a) : void 0; } }
141
+ function _arrayLikeToArray$7(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
139
142
  /*
140
143
  * 2D polygon primitives — area, simplification, self-intersection guard, hull.
141
144
  */
@@ -159,45 +162,54 @@ function polygonSignedArea(points) {
159
162
  return area / 2;
160
163
  }
161
164
 
162
- /** Douglas–Peucker on an open chain (endpoints kept). */
163
- function simplifyChain(points, tolerance, out) {
164
- if (points.length < 3) {
165
- for (var i = 0; i < points.length - 1; i++) out.push(points[i]);
166
- return;
167
- }
168
- var _points$ = _slicedToArray(points[0], 2),
169
- ax = _points$[0],
170
- az = _points$[1];
171
- var _points2 = _slicedToArray(points[points.length - 1], 2),
172
- bx = _points2[0],
173
- bz = _points2[1];
174
- var len = Math.hypot(bx - ax, bz - az) || EPS$2;
175
- var maxDist = -1;
176
- var maxIdx = -1;
177
- for (var _i = 1; _i < points.length - 1; _i++) {
178
- var _points$_i = _slicedToArray(points[_i], 2),
179
- px = _points$_i[0],
180
- pz = _points$_i[1];
181
- var dist = Math.abs((bx - ax) * (az - pz) - (ax - px) * (bz - az)) / len;
182
- if (dist > maxDist) {
183
- maxDist = dist;
184
- maxIdx = _i;
165
+ /**
166
+ * Douglas–Peucker on an open chain (endpoints kept), iterating over [lo, hi] index ranges with an
167
+ * explicit stack. The recursive form sliced a fresh sub-array per level — O(n log n) copied points
168
+ * and matching GC churn on the multi-thousand-point contours a fine raster produces.
169
+ */
170
+ function simplifyChain(points, lo, hi, tolerance, out) {
171
+ // Flat [lo, hi] pairs; the right half is pushed first so the left half pops (and emits) first.
172
+ var ranges = [lo, hi];
173
+ while (ranges.length > 0) {
174
+ var rangeHi = ranges.pop();
175
+ var rangeLo = ranges.pop();
176
+ if (rangeHi - rangeLo < 2) {
177
+ for (var i = rangeLo; i < rangeHi; i++) out.push(points[i]);
178
+ continue;
185
179
  }
180
+ var _points$rangeLo = _slicedToArray(points[rangeLo], 2),
181
+ ax = _points$rangeLo[0],
182
+ az = _points$rangeLo[1];
183
+ var _points$rangeHi = _slicedToArray(points[rangeHi], 2),
184
+ bx = _points$rangeHi[0],
185
+ bz = _points$rangeHi[1];
186
+ var len = Math.hypot(bx - ax, bz - az) || EPS$2;
187
+ var maxDist = -1;
188
+ var maxIdx = -1;
189
+ for (var _i = rangeLo + 1; _i < rangeHi; _i++) {
190
+ var _points$_i = _slicedToArray(points[_i], 2),
191
+ px = _points$_i[0],
192
+ pz = _points$_i[1];
193
+ var dist = Math.abs((bx - ax) * (az - pz) - (ax - px) * (bz - az)) / len;
194
+ if (dist > maxDist) {
195
+ maxDist = dist;
196
+ maxIdx = _i;
197
+ }
198
+ }
199
+ if (maxDist <= tolerance) {
200
+ out.push(points[rangeLo]);
201
+ continue;
202
+ }
203
+ ranges.push(maxIdx, rangeHi, rangeLo, maxIdx);
186
204
  }
187
- if (maxDist <= tolerance) {
188
- out.push(points[0]);
189
- return;
190
- }
191
- simplifyChain(points.slice(0, maxIdx + 1), tolerance, out);
192
- simplifyChain(points.slice(maxIdx), tolerance, out);
193
205
  }
194
206
 
195
207
  /** Douglas–Peucker for a closed polygon: split at the point farthest from the start, simplify both halves. */
196
208
  function simplifyClosedPolygon(points, tolerance) {
197
209
  if (points.length < 4 || tolerance <= 0) return _toConsumableArray(points);
198
- var _points$2 = _slicedToArray(points[0], 2),
199
- sx = _points$2[0],
200
- sz = _points$2[1];
210
+ var _points$ = _slicedToArray(points[0], 2),
211
+ sx = _points$[0],
212
+ sz = _points$[1];
201
213
  var farIdx = 1;
202
214
  var farDist = -1;
203
215
  for (var i = 1; i < points.length; i++) {
@@ -208,8 +220,10 @@ function simplifyClosedPolygon(points, tolerance) {
208
220
  }
209
221
  }
210
222
  var out = [];
211
- simplifyChain(points.slice(0, farIdx + 1), tolerance, out);
212
- simplifyChain([].concat(_toConsumableArray(points.slice(farIdx)), [points[0]]), tolerance, out);
223
+ simplifyChain(points, 0, farIdx, tolerance, out);
224
+ // One wrap-around copy for the second half — the per-recursion slices are gone.
225
+ var wrapped = [].concat(_toConsumableArray(points.slice(farIdx)), [points[0]]);
226
+ simplifyChain(wrapped, 0, wrapped.length - 1, tolerance, out);
213
227
  return out;
214
228
  }
215
229
 
@@ -236,18 +250,18 @@ function isSimplePolygon(points) {
236
250
  var _points$i2 = _slicedToArray(points[i], 2),
237
251
  ax = _points$i2[0],
238
252
  az = _points$i2[1];
239
- var _points3 = _slicedToArray(points[(i + 1) % n], 2),
240
- bx = _points3[0],
241
- bz = _points3[1];
253
+ var _points2 = _slicedToArray(points[(i + 1) % n], 2),
254
+ bx = _points2[0],
255
+ bz = _points2[1];
242
256
  for (var j = i + 1; j < n; j++) {
243
257
  // Skip the shared-endpoint neighbours (j === i+1, and the wrap pair).
244
258
  if (j === i + 1 || i === 0 && j === n - 1) continue;
245
259
  var _points$j = _slicedToArray(points[j], 2),
246
260
  cx = _points$j[0],
247
261
  cz = _points$j[1];
248
- var _points4 = _slicedToArray(points[(j + 1) % n], 2),
249
- dx = _points4[0],
250
- dz = _points4[1];
262
+ var _points3 = _slicedToArray(points[(j + 1) % n], 2),
263
+ dx = _points3[0],
264
+ dz = _points3[1];
251
265
  if (segmentsCross(ax, az, bx, bz, cx, cz, dx, dz)) return false;
252
266
  }
253
267
  }
@@ -265,7 +279,7 @@ function convexHull(points) {
265
279
  };
266
280
  var build = function build(input) {
267
281
  var chain = [];
268
- var _iterator = _createForOfIteratorHelper$6(input),
282
+ var _iterator = _createForOfIteratorHelper$7(input),
269
283
  _step;
270
284
  try {
271
285
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -316,9 +330,9 @@ function ensureSimplePolygon(points) {
316
330
  };
317
331
  }
318
332
 
319
- function _createForOfIteratorHelper$5(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$5(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
320
- function _unsupportedIterableToArray$5(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$5(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$5(r, a) : void 0; } }
321
- function _arrayLikeToArray$5(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
333
+ function _createForOfIteratorHelper$6(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$6(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
334
+ function _unsupportedIterableToArray$6(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$6(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$6(r, a) : void 0; } }
335
+ function _arrayLikeToArray$6(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
322
336
  /*
323
337
  * Binary raster mask operations — morphology (closing, offsetting, enclosed
324
338
  * fill) and marching-squares contouring.
@@ -327,6 +341,21 @@ function _arrayLikeToArray$5(r, a) { (null == a || a > r.length) && (a = r.lengt
327
341
  * centre; `originX/originZ` and `cell` map cell indices back to world units.
328
342
  */
329
343
 
344
+ /**
345
+ * Grow-only pooled trace buffers. Every operation here (and the trace's own mask) used to allocate
346
+ * a fresh multi-MB buffer per article change — churn a tablet pays for in GC pauses. The trace is
347
+ * synchronous and single-threaded, so two slots cover it: slot 0 the mask itself, slot 1 the one
348
+ * morphology/flood scratch alive at any time. Buffers are zeroed on acquire and retained at their
349
+ * high-water mark (bounded by the trace's cell budget).
350
+ */
351
+ var pooledBuffers = [new Uint8Array(0), new Uint8Array(0)];
352
+ function pooledMaskBuffer(slot, size) {
353
+ if (pooledBuffers[slot].length < size) pooledBuffers[slot] = new Uint8Array(size);
354
+ var buffer = pooledBuffers[slot].subarray(0, size);
355
+ buffer.fill(0);
356
+ return buffer;
357
+ }
358
+
330
359
  /**
331
360
  * Morphological closing (dilate then erode) with a square kernel of the given
332
361
  * radius — bridges seams between separately modelled parts without moving the
@@ -340,7 +369,7 @@ function _arrayLikeToArray$5(r, a) { (null == a || a > r.length) && (a = r.lengt
340
369
  */
341
370
  function closeMask(mask, nx, nz, radius) {
342
371
  if (radius < 1) return;
343
- var scratch = new Uint8Array(mask.length);
372
+ var scratch = pooledMaskBuffer(1, mask.length);
344
373
  boxPassX(mask, scratch, nx, nz, radius, true);
345
374
  boxPassZ(scratch, mask, nx, nz, radius, true);
346
375
  boxPassX(mask, scratch, nx, nz, radius, false);
@@ -353,12 +382,15 @@ function closeMask(mask, nx, nz, radius) {
353
382
  * flood could not reach. Returns how many cells were added.
354
383
  */
355
384
  function fillEnclosed(mask, nx, nz) {
356
- var reached = new Uint8Array(mask.length);
357
- var stack = [];
385
+ var reached = pooledMaskBuffer(1, mask.length);
386
+ // A typed stack sized for the worst case: one flat allocation the GC frees without scanning,
387
+ // instead of a `number[]` regrowing through millions of pushes on a large background.
388
+ var stack = new Int32Array(mask.length);
389
+ var stackSize = 0;
358
390
  var visit = function visit(i) {
359
391
  if (mask[i] !== 0 || reached[i] !== 0) return;
360
392
  reached[i] = 1;
361
- stack.push(i);
393
+ stack[stackSize++] = i;
362
394
  };
363
395
  for (var ix = 0; ix < nx; ix++) {
364
396
  visit(ix);
@@ -368,8 +400,8 @@ function fillEnclosed(mask, nx, nz) {
368
400
  visit(iz * nx);
369
401
  visit(iz * nx + nx - 1);
370
402
  }
371
- while (stack.length > 0) {
372
- var i = stack.pop();
403
+ while (stackSize > 0) {
404
+ var i = stack[--stackSize];
373
405
  var _ix = i % nx;
374
406
  var _iz = (i - _ix) / nx;
375
407
  if (_ix > 0) visit(i - 1);
@@ -396,7 +428,7 @@ function fillEnclosed(mask, nx, nz) {
396
428
  * 45° ones.
397
429
  */
398
430
  function offsetMask(mask, nx, nz, radius, dilate) {
399
- var scratch = new Uint8Array(mask.length);
431
+ var scratch = pooledMaskBuffer(1, mask.length);
400
432
  boxPassX(mask, scratch, nx, nz, radius, dilate);
401
433
  boxPassZ(scratch, mask, nx, nz, radius, dilate);
402
434
  }
@@ -485,7 +517,7 @@ function marchingSquaresLoops(mask, nx, nz, originX, originZ, cell) {
485
517
  // Fully inside / fully outside is the overwhelming majority of cells —
486
518
  // returning before the segment lookup keeps the sweep near memory speed.
487
519
  if (c === 0 || c === 15) continue;
488
- var _iterator = _createForOfIteratorHelper$5(MS_SEGMENTS[c]),
520
+ var _iterator = _createForOfIteratorHelper$6(MS_SEGMENTS[c]),
489
521
  _step;
490
522
  try {
491
523
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -503,7 +535,7 @@ function marchingSquaresLoops(mask, nx, nz, originX, originZ, cell) {
503
535
  }
504
536
  var visited = new Set();
505
537
  var loops = [];
506
- var _iterator2 = _createForOfIteratorHelper$5(adjacency.keys()),
538
+ var _iterator2 = _createForOfIteratorHelper$6(adjacency.keys()),
507
539
  _step2;
508
540
  try {
509
541
  var _loop = function _loop() {
@@ -540,13 +572,20 @@ var EPS$1 = 1e-9;
540
572
 
541
573
  /**
542
574
  * Ceiling on the raster's cell count. The extent comes from article geometry, so a mis-scaled
543
- * model (metres authored as millimetres) can ask for a mask no browser can hold — and
544
- * `closeMask` / `fillEnclosed` / `offsetMask` each allocate another buffer this size. 16M cells
545
- * is a 20 m x 20 m article at the default 5 mm cell, far beyond anything real, and refusing is
575
+ * model (metres authored as millimetres) can ask for a mask no browser can hold. 16M cells is a
576
+ * 20 m x 20 m article at the default 5 mm cell, far beyond anything real, and refusing is
546
577
  * graceful: the caller falls back to the measured rect.
547
578
  */
548
579
  var MAX_MASK_CELLS = 16000000;
549
580
 
581
+ /**
582
+ * Cell budget the raster COARSENS toward instead of refusing: above it, the cell size grows just
583
+ * enough to fit (a villa-cliff-sized authored pool at 5 mm is ~2.9M cells and stays untouched).
584
+ * Bounds the pooled mask buffers and the sweep time to what a tablet survives, at the price of a
585
+ * coarser outline for outsized articles — still their shape, not the rectangular fallback.
586
+ */
587
+ var TARGET_MASK_CELLS = 4000000;
588
+
550
589
  /** What a trace did internally — see {@link TraceFootprintOptions.diagnostics}. */
551
590
 
552
591
  /**
@@ -560,16 +599,9 @@ var MAX_MASK_CELLS = 16000000;
560
599
  function traceFootprintFromTriangles(triangleXZ) {
561
600
  var _options$cellSize, _options$simplifyTole, _options$closeGap, _options$offset;
562
601
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
563
- var cell = (_options$cellSize = options.cellSize) !== null && _options$cellSize !== void 0 ? _options$cellSize : 0.005;
564
- var tolerance = (_options$simplifyTole = options.simplifyTolerance) !== null && _options$simplifyTole !== void 0 ? _options$simplifyTole : cell;
565
- // Closing radius in cells, so the sealed gap stays ~`closeGap` at any resolution.
566
- var closeGap = (_options$closeGap = options.closeGap) !== null && _options$closeGap !== void 0 ? _options$closeGap : 0.02;
567
- var closeRadius = closeGap <= 0 ? 0 : Math.max(1, Math.round(closeGap / (cell * 2)));
568
- var offset = (_options$offset = options.offset) !== null && _options$offset !== void 0 ? _options$offset : 0;
569
- var offsetRadius = Math.round(Math.abs(offset) / cell);
570
- var grow = offset > 0;
602
+ var requestedCell = (_options$cellSize = options.cellSize) !== null && _options$cellSize !== void 0 ? _options$cellSize : 0.005;
571
603
  var triCount = Math.floor(triangleXZ.length / 6);
572
- if (triCount < 1 || cell <= 0) return [];
604
+ if (triCount < 1 || requestedCell <= 0) return [];
573
605
  var minX = Infinity;
574
606
  var maxX = -Infinity;
575
607
  var minZ = Infinity;
@@ -582,7 +614,19 @@ function traceFootprintFromTriangles(triangleXZ) {
582
614
  if (z < minZ) minZ = z;
583
615
  if (z > maxZ) maxZ = z;
584
616
  }
585
- if (!Number.isFinite(minX) || maxX - minX < cell || maxZ - minZ < cell) return [];
617
+ if (!Number.isFinite(minX) || maxX - minX < requestedCell || maxZ - minZ < requestedCell) return [];
618
+
619
+ // Coarsen toward the cell budget: everything below derives from the FINAL cell (radii,
620
+ // tolerance, edge-sampling threshold), so the budget check runs before any of them.
621
+ var rawCells = (Math.ceil((maxX - minX) / requestedCell) + 1) * (Math.ceil((maxZ - minZ) / requestedCell) + 1);
622
+ var cell = rawCells > TARGET_MASK_CELLS ? requestedCell * Math.sqrt(rawCells / TARGET_MASK_CELLS) : requestedCell;
623
+ var tolerance = (_options$simplifyTole = options.simplifyTolerance) !== null && _options$simplifyTole !== void 0 ? _options$simplifyTole : cell;
624
+ // Closing radius in cells, so the sealed gap stays ~`closeGap` at any resolution.
625
+ var closeGap = (_options$closeGap = options.closeGap) !== null && _options$closeGap !== void 0 ? _options$closeGap : 0.02;
626
+ var closeRadius = closeGap <= 0 ? 0 : Math.max(1, Math.round(closeGap / (cell * 2)));
627
+ var offset = (_options$offset = options.offset) !== null && _options$offset !== void 0 ? _options$offset : 0;
628
+ var offsetRadius = Math.round(Math.abs(offset) / cell);
629
+ var grow = offset > 0;
586
630
 
587
631
  // Empty border of `closeRadius + 1` cells per side: the closing's dilation
588
632
  // must have room to spread without the erosion then eating the shape's
@@ -593,7 +637,7 @@ function traceFootprintFromTriangles(triangleXZ) {
593
637
  var nx = Math.ceil((maxX - minX) / cell) + 2 * border + 1;
594
638
  var nz = Math.ceil((maxZ - minZ) / cell) + 2 * border + 1;
595
639
  if (nx * nz > MAX_MASK_CELLS) return [];
596
- var mask = new Uint8Array(nx * nz);
640
+ var mask = pooledMaskBuffer(0, nx * nz);
597
641
  var cellOf = function cellOf(v, origin, n) {
598
642
  return Math.min(n - border - 1, Math.max(border, Math.floor((v - origin) / cell)));
599
643
  };
@@ -707,9 +751,9 @@ function traceFootprintFromTriangles(triangleXZ) {
707
751
  return simplified.length >= 3 ? simplified : [];
708
752
  }
709
753
 
710
- function _createForOfIteratorHelper$4(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$4(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
711
- function _unsupportedIterableToArray$4(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$4(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$4(r, a) : void 0; } }
712
- function _arrayLikeToArray$4(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
754
+ function _createForOfIteratorHelper$5(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$5(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
755
+ function _unsupportedIterableToArray$5(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$5(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$5(r, a) : void 0; } }
756
+ function _arrayLikeToArray$5(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
713
757
 
714
758
  /** The subset of a deckCutout article rule the matcher needs. */
715
759
 
@@ -720,7 +764,7 @@ function _arrayLikeToArray$4(r, a) { (null == a || a > r.length) && (a = r.lengt
720
764
  * per-element codexes.
721
765
  */
722
766
  function matchArticleRule(rules, articleCodex) {
723
- var _iterator = _createForOfIteratorHelper$4(rules),
767
+ var _iterator = _createForOfIteratorHelper$5(rules),
724
768
  _step;
725
769
  try {
726
770
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -745,9 +789,9 @@ function matchArticleRule(rules, articleCodex) {
745
789
  return null;
746
790
  }
747
791
 
748
- function _createForOfIteratorHelper$3(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$3(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
749
- function _unsupportedIterableToArray$3(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$3(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$3(r, a) : void 0; } }
750
- function _arrayLikeToArray$3(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
792
+ function _createForOfIteratorHelper$4(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$4(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
793
+ function _unsupportedIterableToArray$4(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$4(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$4(r, a) : void 0; } }
794
+ function _arrayLikeToArray$4(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
751
795
 
752
796
  /**
753
797
  * Raster cell (m) for the geometry-derived footprint. 5 mm follows cast-in
@@ -806,7 +850,7 @@ function pickArticleRule(rules, codexesByArea) {
806
850
  var _codexesByArea$;
807
851
  var primaryCodex = (_codexesByArea$ = codexesByArea[0]) === null || _codexesByArea$ === void 0 ? void 0 : _codexesByArea$.codex;
808
852
  var rule = matchArticleRule(rules, undefined);
809
- var _iterator = _createForOfIteratorHelper$3(codexesByArea),
853
+ var _iterator = _createForOfIteratorHelper$4(codexesByArea),
810
854
  _step;
811
855
  try {
812
856
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -837,10 +881,10 @@ function pickArticleRule(rules, codexesByArea) {
837
881
  * meshes change; anchor moves are pure translations and reuse the cache.
838
882
  * Returns the outline in CURRENT floor-local coordinates.
839
883
  */
840
- function traceArticleFootprint(elements, floorLocal, rect, minLocalY, offset, diagnostics, cache) {
884
+ function traceArticleFootprint(elements, floorLocal, rect, minLocalY, offset, diagnostics, cache, pendingLiftY) {
841
885
  var meshes = [];
842
886
  var sigParts = [];
843
- var _iterator2 = _createForOfIteratorHelper$3(elements),
887
+ var _iterator2 = _createForOfIteratorHelper$4(elements),
844
888
  _step2;
845
889
  try {
846
890
  for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
@@ -863,52 +907,63 @@ function traceArticleFootprint(elements, floorLocal, rect, minLocalY, offset, di
863
907
  var sizeX = rect.maxX - rect.minX;
864
908
  var sizeY = rect.maxY - rect.minY;
865
909
  var sizeZ = rect.maxZ - rect.minZ;
866
- // The article's top belongs in the signature even though the outline is stored
867
- // relative to the bbox: the slice plane is fixed, so moving the article
868
- // VERTICALLY changes which triangles clear it, i.e. the traced shape itself.
869
- // Without it the anchor's lift would keep returning the pre-lift outline.
870
- var signature = "".concat(minLocalY.toFixed(3), "|").concat(rect.maxY.toFixed(3), "|").concat(offset.toFixed(3), "|").concat(sizeX.toFixed(3), "x").concat(sizeY.toFixed(3), "x").concat(sizeZ.toFixed(3), "|").concat(sigParts.join(','));
910
+ // Vertical position decides which triangles clear the fixed slice plane, so the top belongs in
911
+ // the signature. Signed with the POST-lift top: the pre-lift measure then produces the same
912
+ // signature as the post-lift one and the second measure hits the cache instead of re-tracing.
913
+ var signature = "".concat(minLocalY.toFixed(3), "|").concat((rect.maxY + pendingLiftY).toFixed(3), "|").concat(offset.toFixed(3), "|").concat(sizeX.toFixed(3), "x").concat(sizeY.toFixed(3), "x").concat(sizeZ.toFixed(3), "|").concat(sigParts.join(','));
871
914
  if (!cache.current || cache.current.signature !== signature) {
872
915
  var started = performance.now();
873
- var xz = [];
874
- var _triCount = 0;
875
916
  var _vertexCount = 0;
876
- var _iterator3 = _createForOfIteratorHelper$3(meshes),
877
- _step3;
878
- try {
879
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
880
- var mesh = _step3.value;
881
- var geometry = mesh.geometry;
917
+ // Transform every vertex once per mesh; triangles then index into the flat array. Kept per
918
+ // mesh so the count pass below can re-scan triangles without re-transforming.
919
+ var locals = meshes.map(function (mesh) {
920
+ var pos = mesh.geometry.getAttribute('position');
921
+ _vertexCount += pos.count;
922
+ _meshToFloor.multiplyMatrices(floorLocal, mesh.matrixWorld);
923
+ var local = new Float32Array(pos.count * 3);
924
+ for (var i = 0; i < pos.count; i++) {
925
+ _vertex$1.set(pos.getX(i), pos.getY(i), pos.getZ(i)).applyMatrix4(_meshToFloor);
926
+ local[i * 3] = _vertex$1.x;
927
+ // Slice at the post-lift height (see signature); a Y shift cannot change the XZ outline.
928
+ local[i * 3 + 1] = _vertex$1.y + pendingLiftY;
929
+ local[i * 3 + 2] = _vertex$1.z;
930
+ }
931
+ return local;
932
+ });
933
+ // Two passes over cheap index reads: count the slice triangles, then fill ONE exactly-sized
934
+ // typed array — a `number[]` grown by pushes held hundreds of thousands of boxed slots on a
935
+ // dense article and was re-copied on every capacity step.
936
+ var forEachSliceTriangle = function forEachSliceTriangle(emit) {
937
+ var count = 0;
938
+ for (var m = 0; m < meshes.length; m++) {
939
+ var geometry = meshes[m].geometry;
882
940
  var pos = geometry.getAttribute('position');
883
- _vertexCount += pos.count;
884
- _meshToFloor.multiplyMatrices(floorLocal, mesh.matrixWorld);
885
- // Transform every vertex once; triangles then index into the flat array.
886
- var local = new Float32Array(pos.count * 3);
887
- for (var i = 0; i < pos.count; i++) {
888
- _vertex$1.set(pos.getX(i), pos.getY(i), pos.getZ(i)).applyMatrix4(_meshToFloor);
889
- local[i * 3] = _vertex$1.x;
890
- local[i * 3 + 1] = _vertex$1.y;
891
- local[i * 3 + 2] = _vertex$1.z;
892
- }
941
+ var local = locals[m];
893
942
  var index = geometry.index;
894
943
  var triTotal = index ? Math.floor(index.count / 3) : Math.floor(pos.count / 3);
895
944
  for (var t = 0; t < triTotal; t++) {
896
945
  var a = index ? index.getX(t * 3) : t * 3;
897
946
  var b = index ? index.getX(t * 3 + 1) : t * 3 + 1;
898
947
  var c = index ? index.getX(t * 3 + 2) : t * 3 + 2;
899
- var ya = local[a * 3 + 1];
900
- var yb = local[b * 3 + 1];
901
- var yc = local[c * 3 + 1];
902
- if (ya < minLocalY && yb < minLocalY && yc < minLocalY) continue;
903
- xz.push(local[a * 3], local[a * 3 + 2], local[b * 3], local[b * 3 + 2], local[c * 3], local[c * 3 + 2]);
904
- _triCount += 1;
948
+ if (local[a * 3 + 1] < minLocalY && local[b * 3 + 1] < minLocalY && local[c * 3 + 1] < minLocalY) continue;
949
+ count += 1;
950
+ if (emit) emit(local[a * 3], local[a * 3 + 2], local[b * 3], local[b * 3 + 2], local[c * 3], local[c * 3 + 2]);
905
951
  }
906
952
  }
907
- } catch (err) {
908
- _iterator3.e(err);
909
- } finally {
910
- _iterator3.f();
911
- }
953
+ return count;
954
+ };
955
+ var _triCount = forEachSliceTriangle(null);
956
+ var xz = new Float32Array(_triCount * 6);
957
+ var write = 0;
958
+ forEachSliceTriangle(function (ax, az, bx, bz, cx, cz) {
959
+ xz[write] = ax;
960
+ xz[write + 1] = az;
961
+ xz[write + 2] = bx;
962
+ xz[write + 3] = bz;
963
+ xz[write + 4] = cx;
964
+ xz[write + 5] = cz;
965
+ write += 6;
966
+ });
912
967
  var traced = traceFootprintFromTriangles(xz, {
913
968
  cellSize: GEOMETRY_FOOTPRINT_CELL,
914
969
  offset: offset,
@@ -959,6 +1014,7 @@ function traceArticleFootprint(elements, floorLocal, rect, minLocalY, offset, di
959
1014
  */
960
1015
  function deriveArticleFootprint(measurement, rule, margin, cache) {
961
1016
  var _result$points$length, _result$points;
1017
+ var pendingLiftY = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
962
1018
  var result = {
963
1019
  points: null,
964
1020
  rim: null,
@@ -975,19 +1031,19 @@ function deriveArticleFootprint(measurement, rule, margin, cache) {
975
1031
  elementObjects = measurement.elementObjects;
976
1032
  var rawSizeX = rect.maxX - rect.minX;
977
1033
  var rawSizeZ = rect.maxZ - rect.minZ;
978
- var traced = traceArticleFootprint(topLevelElements(elementObjects), floorLocal, rect, floorTopY - rule.footprintSliceDepth, margin, result.trace, cache);
1034
+ var traced = traceArticleFootprint(topLevelElements(elementObjects), floorLocal, rect, floorTopY - rule.footprintSliceDepth, margin, result.trace, cache, pendingLiftY);
979
1035
  var minX = Infinity;
980
1036
  var maxX = -Infinity;
981
1037
  var minZ = Infinity;
982
1038
  var maxZ = -Infinity;
983
1039
  if (traced && traced.points.length >= 3) {
984
- var _iterator4 = _createForOfIteratorHelper$3(traced.points),
985
- _step4;
1040
+ var _iterator3 = _createForOfIteratorHelper$4(traced.points),
1041
+ _step3;
986
1042
  try {
987
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
988
- var _step4$value = _slicedToArray(_step4.value, 2),
989
- x = _step4$value[0],
990
- z = _step4$value[1];
1043
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1044
+ var _step3$value = _slicedToArray(_step3.value, 2),
1045
+ x = _step3$value[0],
1046
+ z = _step3$value[1];
991
1047
  if (x < minX) minX = x;
992
1048
  if (x > maxX) maxX = x;
993
1049
  if (z < minZ) minZ = z;
@@ -997,9 +1053,9 @@ function deriveArticleFootprint(measurement, rule, margin, cache) {
997
1053
  // what the steps below need is the article's RIM extent (it sizes the
998
1054
  // water volume), not the collar's inner boundary.
999
1055
  } catch (err) {
1000
- _iterator4.e(err);
1056
+ _iterator3.e(err);
1001
1057
  } finally {
1002
- _iterator4.f();
1058
+ _iterator3.f();
1003
1059
  }
1004
1060
  minX += margin;
1005
1061
  maxX -= margin;
@@ -1042,9 +1098,9 @@ function deriveArticleFootprint(measurement, rule, margin, cache) {
1042
1098
  return result;
1043
1099
  }
1044
1100
 
1045
- function _createForOfIteratorHelper$2(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$2(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
1046
- function _unsupportedIterableToArray$2(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$2(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$2(r, a) : void 0; } }
1047
- function _arrayLikeToArray$2(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
1101
+ function _createForOfIteratorHelper$3(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$3(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
1102
+ function _unsupportedIterableToArray$3(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$3(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$3(r, a) : void 0; } }
1103
+ function _arrayLikeToArray$3(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
1048
1104
 
1049
1105
  /** Below this world delta (m) the element anchor correction is considered settled. */
1050
1106
  var ANCHOR_EPSILON = 1e-3;
@@ -1098,7 +1154,7 @@ function deriveElementAnchor(_ref) {
1098
1154
  var topLevel = topLevelElements(elementObjects);
1099
1155
  if (topLevel.length === 0) return null;
1100
1156
  var offsets = {};
1101
- var _iterator = _createForOfIteratorHelper$2(topLevel),
1157
+ var _iterator = _createForOfIteratorHelper$3(topLevel),
1102
1158
  _step;
1103
1159
  try {
1104
1160
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -1167,10 +1223,14 @@ function deriveWaterVolume(_ref) {
1167
1223
  };
1168
1224
  }
1169
1225
 
1226
+ function _createForOfIteratorHelper$2(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray$2(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
1227
+ function _unsupportedIterableToArray$2(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray$2(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray$2(r, a) : void 0; } }
1228
+ function _arrayLikeToArray$2(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
1170
1229
  /**
1171
1230
  * One line describing the whole cutout pipeline, plus the `key` it changes with:
1172
1231
  * the caller logs only when the key moves, so the line appears once per codex /
1173
1232
  * rule / outline state (bounds coarse-rounded to cm) rather than every measure.
1233
+ * `message` is lazy — composed only when the key moved.
1174
1234
  */
1175
1235
  function measureLog(_ref) {
1176
1236
  var _rule$match, _points$length, _fpBounds$map$join;
@@ -1182,38 +1242,59 @@ function measureLog(_ref) {
1182
1242
  var rect = measurement.rect,
1183
1243
  floorTopY = measurement.floorTopY;
1184
1244
  var points = footprint.points;
1185
- var fpBounds = points ? points.reduce(function (acc, _ref2) {
1186
- var _ref3 = _slicedToArray(_ref2, 2),
1187
- x = _ref3[0],
1188
- z = _ref3[1];
1189
- return [Math.min(acc[0], x), Math.max(acc[1], x), Math.min(acc[2], z), Math.max(acc[3], z)];
1190
- }, [Infinity, -Infinity, Infinity, -Infinity]) : null;
1245
+ var fpMinX = Infinity;
1246
+ var fpMaxX = -Infinity;
1247
+ var fpMinZ = Infinity;
1248
+ var fpMaxZ = -Infinity;
1249
+ if (points) {
1250
+ var _iterator = _createForOfIteratorHelper$2(points),
1251
+ _step;
1252
+ try {
1253
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1254
+ var _step$value = _slicedToArray(_step.value, 2),
1255
+ x = _step$value[0],
1256
+ z = _step$value[1];
1257
+ if (x < fpMinX) fpMinX = x;
1258
+ if (x > fpMaxX) fpMaxX = x;
1259
+ if (z < fpMinZ) fpMinZ = z;
1260
+ if (z > fpMaxZ) fpMaxZ = z;
1261
+ }
1262
+ } catch (err) {
1263
+ _iterator.e(err);
1264
+ } finally {
1265
+ _iterator.f();
1266
+ }
1267
+ }
1268
+ var fpBounds = points ? [fpMinX, fpMaxX, fpMinZ, fpMaxZ] : null;
1191
1269
  var key = "".concat(primaryCodex !== null && primaryCodex !== void 0 ? primaryCodex : '', "|").concat((_rule$match = rule === null || rule === void 0 ? void 0 : rule.match) !== null && _rule$match !== void 0 ? _rule$match : '', "|").concat(footprint.sampledCount, "|").concat((_points$length = points === null || points === void 0 ? void 0 : points.length) !== null && _points$length !== void 0 ? _points$length : 0, "|").concat((_fpBounds$map$join = fpBounds === null || fpBounds === void 0 ? void 0 : fpBounds.map(function (v) {
1192
1270
  return v.toFixed(2);
1193
1271
  }).join(',')) !== null && _fpBounds$map$join !== void 0 ? _fpBounds$map$join : '');
1194
- var rawSizeX = rect.maxX - rect.minX;
1195
- var rawSizeZ = rect.maxZ - rect.minZ;
1196
- // Where the slice plane sits relative to the article. The plane is anchored to
1197
- // the DECK top, so an article whose rim doesn't reach that high contributes
1198
- // nothing but its topmost stray detail.
1199
- var sliceInfo = rule && rule.footprintFromGeometry ? " | Y: deck top ".concat(floorTopY.toFixed(2), ", article [").concat(rect.minY.toFixed(2), ", ").concat(rect.maxY.toFixed(2), "], slice keeps \u2265 ").concat((floorTopY - rule.footprintSliceDepth).toFixed(2)) : '';
1200
- var fpInfo;
1201
- if (footprint.source === '') {
1202
- fpInfo = ' | NO outline rule matched this codex → NO COLLAR, the authored hole stays fully open';
1203
- } else if (fpBounds && points) {
1204
- // Room left between the outline and each authored hole edge. Negative on any
1205
- // side = the article is bigger than the hole it sits in; the collar clamps
1206
- // the outline to the hole and degrades toward a solid slab.
1207
- var slack = [fpBounds[0] - (hole.centerX - hole.halfExtentX), hole.centerX + hole.halfExtentX - fpBounds[1], fpBounds[2] - (hole.centerZ - hole.halfExtentZ), hole.centerZ + hole.halfExtentZ - fpBounds[3]].map(function (v) {
1208
- return v.toFixed(2);
1209
- });
1210
- fpInfo = " | measured ".concat(rawSizeX.toFixed(2), "\xD7").concat(rawSizeZ.toFixed(2)) + (footprint.tracedBounds === '' ? '' : " | traced ".concat(footprint.sampledCount, " pts ").concat(footprint.tracedBounds)) + " | outline ".concat(points.length, " pts X [").concat(fpBounds[0].toFixed(2), ", ").concat(fpBounds[1].toFixed(2), "] Z [").concat(fpBounds[2].toFixed(2), ", ").concat(fpBounds[3].toFixed(2), "]") + " | hole X [".concat((hole.centerX - hole.halfExtentX).toFixed(2), ", ").concat((hole.centerX + hole.halfExtentX).toFixed(2), "] Z [").concat((hole.centerZ - hole.halfExtentZ).toFixed(2), ", ").concat((hole.centerZ + hole.halfExtentZ).toFixed(2), "]") + " | slack X ".concat(slack[0], "/").concat(slack[1], " Z ").concat(slack[2], "/").concat(slack[3]) + " | ".concat(footprint.source).concat(footprint.repairNote) + (footprint.trace.loops > 0 ? " | trace: ".concat(footprint.trace.filledCells, " cells (").concat(footprint.trace.enclosedFilled, " filled in), ").concat(footprint.trace.loops, " loop(s), kept ").concat((footprint.trace.chosenAreaShare * 100).toFixed(0), "% of contour area, ").concat(footprint.trace.pointsBeforeSimplify, " \u2192 ").concat(footprint.trace.pointsAfterSimplify, " pts") : '');
1211
- } else {
1212
- fpInfo = " | ".concat(footprint.source, " produced NO usable outline (").concat(footprint.sampledCount, " pts)").concat(footprint.repairNote, " \u2192 NO COLLAR, the authored hole stays fully open");
1213
- }
1272
+ var message = function message() {
1273
+ var rawSizeX = rect.maxX - rect.minX;
1274
+ var rawSizeZ = rect.maxZ - rect.minZ;
1275
+ // Where the slice plane sits relative to the article. The plane is anchored to
1276
+ // the DECK top, so an article whose rim doesn't reach that high contributes
1277
+ // nothing but its topmost stray detail.
1278
+ var sliceInfo = rule && rule.footprintFromGeometry ? " | Y: deck top ".concat(floorTopY.toFixed(2), ", article [").concat(rect.minY.toFixed(2), ", ").concat(rect.maxY.toFixed(2), "], slice keeps \u2265 ").concat((floorTopY - rule.footprintSliceDepth).toFixed(2)) : '';
1279
+ var fpInfo;
1280
+ if (footprint.source === '') {
1281
+ fpInfo = ' | NO outline rule matched this codex → NO COLLAR, the authored hole stays fully open';
1282
+ } else if (fpBounds && points) {
1283
+ // Room left between the outline and each authored hole edge. Negative on any
1284
+ // side = the article is bigger than the hole it sits in; the collar clamps
1285
+ // the outline to the hole and degrades toward a solid slab.
1286
+ var slack = [fpBounds[0] - (hole.centerX - hole.halfExtentX), hole.centerX + hole.halfExtentX - fpBounds[1], fpBounds[2] - (hole.centerZ - hole.halfExtentZ), hole.centerZ + hole.halfExtentZ - fpBounds[3]].map(function (v) {
1287
+ return v.toFixed(2);
1288
+ });
1289
+ fpInfo = " | measured ".concat(rawSizeX.toFixed(2), "\xD7").concat(rawSizeZ.toFixed(2)) + (footprint.tracedBounds === '' ? '' : " | traced ".concat(footprint.sampledCount, " pts ").concat(footprint.tracedBounds)) + " | outline ".concat(points.length, " pts X [").concat(fpBounds[0].toFixed(2), ", ").concat(fpBounds[1].toFixed(2), "] Z [").concat(fpBounds[2].toFixed(2), ", ").concat(fpBounds[3].toFixed(2), "]") + " | hole X [".concat((hole.centerX - hole.halfExtentX).toFixed(2), ", ").concat((hole.centerX + hole.halfExtentX).toFixed(2), "] Z [").concat((hole.centerZ - hole.halfExtentZ).toFixed(2), ", ").concat((hole.centerZ + hole.halfExtentZ).toFixed(2), "]") + " | slack X ".concat(slack[0], "/").concat(slack[1], " Z ").concat(slack[2], "/").concat(slack[3]) + " | ".concat(footprint.source).concat(footprint.repairNote) + (footprint.trace.loops > 0 ? " | trace: ".concat(footprint.trace.filledCells, " cells (").concat(footprint.trace.enclosedFilled, " filled in), ").concat(footprint.trace.loops, " loop(s), kept ").concat((footprint.trace.chosenAreaShare * 100).toFixed(0), "% of contour area, ").concat(footprint.trace.pointsBeforeSimplify, " \u2192 ").concat(footprint.trace.pointsAfterSimplify, " pts") : '');
1290
+ } else {
1291
+ fpInfo = " | ".concat(footprint.source, " produced NO usable outline (").concat(footprint.sampledCount, " pts)").concat(footprint.repairNote, " \u2192 NO COLLAR, the authored hole stays fully open");
1292
+ }
1293
+ return "[DeckCutout] article codex: ".concat(primaryCodex !== null && primaryCodex !== void 0 ? primaryCodex : '(unknown)', " \u2192 rule: ").concat(rule ? "'".concat(rule.match, "'") : '(none — articleSync globals)').concat(sliceInfo).concat(fpInfo);
1294
+ };
1214
1295
  return {
1215
1296
  key: key,
1216
- message: "[DeckCutout] article codex: ".concat(primaryCodex !== null && primaryCodex !== void 0 ? primaryCodex : '(unknown)', " \u2192 rule: ").concat(rule ? "'".concat(rule.match, "'") : '(none — articleSync globals)').concat(sliceInfo).concat(fpInfo)
1297
+ message: message
1217
1298
  };
1218
1299
  }
1219
1300
 
@@ -1259,12 +1340,9 @@ function DeckCutoutArticleSync() {
1259
1340
  var scene = useThree(function (s) {
1260
1341
  return s.scene;
1261
1342
  });
1262
- var deckCutoutJson = useRTVStore(function (s) {
1263
- return JSON.stringify(s.resolvedEnvironment.scene.deckCutout);
1343
+ var deform = useStableEnvSlice(function (env) {
1344
+ return env.scene.deckCutout;
1264
1345
  });
1265
- var deform = useMemo(function () {
1266
- return JSON.parse(deckCutoutJson);
1267
- }, [deckCutoutJson]);
1268
1346
  var setWaterOverride = useRTVStore(function (s) {
1269
1347
  return s.setWaterVolumeOverride;
1270
1348
  });
@@ -1383,7 +1461,12 @@ function DeckCutoutArticleSync() {
1383
1461
  var _pickArticleRule = pickArticleRule(deform.articleRules, measured.codexesByArea),
1384
1462
  rule = _pickArticleRule.rule,
1385
1463
  primaryCodex = _pickArticleRule.primaryCodex;
1386
- var traced = deriveArticleFootprint(measured, rule, deform.articleSync.margin, geometryCacheRef);
1464
+ // The anchor's lift changes what clears the slice plane and used to force a second full trace
1465
+ // per article load. It is fully determined here (same formula as `deriveElementAnchor`), so
1466
+ // the first trace evaluates the article at its post-lift height directly.
1467
+ var anchorCfg = deform.elementAnchor;
1468
+ var pendingLiftY = anchorActive ? (anchorCfg.alignTopToDeck ? measured.floorTopY - measured.rect.maxY : 0) + anchorCfg.offsetY : 0;
1469
+ var traced = deriveArticleFootprint(measured, rule, deform.articleSync.margin, geometryCacheRef, pendingLiftY);
1387
1470
  var rect = traced.rim ? _objectSpread(_objectSpread({}, measured.rect), traced.rim) : measured.rect;
1388
1471
  var log = measureLog({
1389
1472
  measurement: measured,
@@ -1394,7 +1477,7 @@ function DeckCutoutArticleSync() {
1394
1477
  });
1395
1478
  if (matchLogRef.current !== log.key) {
1396
1479
  matchLogRef.current = log.key;
1397
- LOG$2.debug(log.message);
1480
+ LOG$2.debug(log.message());
1398
1481
  }
1399
1482
 
1400
1483
  // The deck is never reshaped: the collar fills everything between its
@@ -2167,8 +2250,10 @@ function buildCollarGeometry(_ref) {
2167
2250
  uvs[_i * 2] = (_x * cos - _z * sin) / scale;
2168
2251
  uvs[_i * 2 + 1] = (_x * sin + _z * cos) / scale;
2169
2252
  }
2170
- geo.setAttribute('uv', new BufferAttribute(uvs, 2));
2171
- geo.setAttribute('uv1', new BufferAttribute(uvs, 2));
2253
+ // ONE attribute on both slots: two attribute objects over the same array upload two GL buffers.
2254
+ var uvAttribute = new BufferAttribute(uvs, 2);
2255
+ geo.setAttribute('uv', uvAttribute);
2256
+ geo.setAttribute('uv1', uvAttribute);
2172
2257
  } else if (deck.triangles) {
2173
2258
  // Continue the deck's own mapping. One fitted mapping when the deck's
2174
2259
  // unwrap is a single plane — it cannot shear, whereas deciding per vertex
@@ -2196,8 +2281,10 @@ function buildCollarGeometry(_ref) {
2196
2281
  _uvs[_i2 * 2] = sampled[0];
2197
2282
  _uvs[_i2 * 2 + 1] = sampled[1];
2198
2283
  }
2199
- geo.setAttribute('uv', new BufferAttribute(_uvs, 2));
2200
- geo.setAttribute('uv1', new BufferAttribute(_uvs, 2));
2284
+ // See above — one attribute, one GL buffer, both slots.
2285
+ var _uvAttribute = new BufferAttribute(_uvs, 2);
2286
+ geo.setAttribute('uv', _uvAttribute);
2287
+ geo.setAttribute('uv1', _uvAttribute);
2201
2288
  }
2202
2289
  return {
2203
2290
  geometry: geo,
@@ -2319,12 +2406,9 @@ function DeckCutoutCollar() {
2319
2406
  var scene = useThree(function (s) {
2320
2407
  return s.scene;
2321
2408
  });
2322
- var deckCutoutJson = useRTVStore(function (s) {
2323
- return JSON.stringify(s.resolvedEnvironment.scene.deckCutout);
2409
+ var deform = useStableEnvSlice(function (env) {
2410
+ return env.scene.deckCutout;
2324
2411
  });
2325
- var deform = useMemo(function () {
2326
- return JSON.parse(deckCutoutJson);
2327
- }, [deckCutoutJson]);
2328
2412
  var footprint = useRTVStore(function (s) {
2329
2413
  return s.deckCutoutFootprintOverride;
2330
2414
  });