okgeometry-api 1.26.1 → 1.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dist/Arc.js +1 -1
  2. package/dist/Arc.js.map +1 -1
  3. package/dist/Brep.d.ts +18 -0
  4. package/dist/Brep.d.ts.map +1 -1
  5. package/dist/Brep.js +32 -1
  6. package/dist/Brep.js.map +1 -1
  7. package/dist/Circle.js +1 -1
  8. package/dist/Circle.js.map +1 -1
  9. package/dist/Line.js +1 -1
  10. package/dist/Line.js.map +1 -1
  11. package/dist/Mesh.d.ts +71 -0
  12. package/dist/Mesh.d.ts.map +1 -1
  13. package/dist/Mesh.js +264 -21
  14. package/dist/Mesh.js.map +1 -1
  15. package/dist/MeshSurface.js +1 -1
  16. package/dist/MeshSurface.js.map +1 -1
  17. package/dist/NurbsCurve.d.ts +6 -0
  18. package/dist/NurbsCurve.d.ts.map +1 -1
  19. package/dist/NurbsCurve.js +10 -1
  20. package/dist/NurbsCurve.js.map +1 -1
  21. package/dist/NurbsSurface.js +1 -1
  22. package/dist/NurbsSurface.js.map +1 -1
  23. package/dist/PolyCurve.d.ts +15 -0
  24. package/dist/PolyCurve.d.ts.map +1 -1
  25. package/dist/PolyCurve.js +31 -1
  26. package/dist/PolyCurve.js.map +1 -1
  27. package/dist/Polyline.d.ts +6 -0
  28. package/dist/Polyline.d.ts.map +1 -1
  29. package/dist/Polyline.js +12 -1
  30. package/dist/Polyline.js.map +1 -1
  31. package/dist/Ray.js +1 -1
  32. package/dist/Ray.js.map +1 -1
  33. package/dist/SculptSession.js +1 -1
  34. package/dist/SculptSession.js.map +1 -1
  35. package/dist/engine-threaded.d.ts +2 -0
  36. package/dist/engine-threaded.d.ts.map +1 -0
  37. package/dist/engine-threaded.js +33 -0
  38. package/dist/engine-threaded.js.map +1 -0
  39. package/dist/engine.d.ts +21 -0
  40. package/dist/engine.d.ts.map +1 -1
  41. package/dist/engine.js +42 -0
  42. package/dist/engine.js.map +1 -1
  43. package/dist/index.d.ts +2 -2
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +1 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/mesh-boolean.pool.d.ts +23 -1
  48. package/dist/mesh-boolean.pool.d.ts.map +1 -1
  49. package/dist/mesh-boolean.pool.js +97 -12
  50. package/dist/mesh-boolean.pool.js.map +1 -1
  51. package/dist/mesh-boolean.protocol.d.ts +21 -1
  52. package/dist/mesh-boolean.protocol.d.ts.map +1 -1
  53. package/dist/mesh-boolean.protocol.js.map +1 -1
  54. package/dist/mesh-boolean.worker.d.ts.map +1 -1
  55. package/dist/mesh-boolean.worker.js +94 -1
  56. package/dist/mesh-boolean.worker.js.map +1 -1
  57. package/dist/wasm-active.d.ts +20 -0
  58. package/dist/wasm-active.d.ts.map +1 -0
  59. package/dist/wasm-active.js +26 -0
  60. package/dist/wasm-active.js.map +1 -0
  61. package/dist/wasm-base64-threaded.d.ts +2 -0
  62. package/dist/wasm-base64-threaded.d.ts.map +1 -0
  63. package/dist/wasm-base64-threaded.js +3 -0
  64. package/dist/wasm-base64-threaded.js.map +1 -0
  65. package/dist/wasm-base64.d.ts +1 -1
  66. package/dist/wasm-base64.d.ts.map +1 -1
  67. package/dist/wasm-base64.js +1 -1
  68. package/dist/wasm-base64.js.map +1 -1
  69. package/dist/wasm-bindings-threaded.d.ts +2716 -0
  70. package/dist/wasm-bindings-threaded.d.ts.map +1 -0
  71. package/dist/wasm-bindings-threaded.js +5088 -0
  72. package/dist/wasm-bindings-threaded.js.map +1 -0
  73. package/dist/wasm-bindings.d.ts +182 -0
  74. package/dist/wasm-bindings.d.ts.map +1 -1
  75. package/dist/wasm-bindings.js +288 -0
  76. package/dist/wasm-bindings.js.map +1 -1
  77. package/dist/workerHelpers-threaded.d.ts +2 -0
  78. package/dist/workerHelpers-threaded.d.ts.map +1 -0
  79. package/dist/workerHelpers-threaded.js +100 -0
  80. package/dist/workerHelpers-threaded.js.map +1 -0
  81. package/package.json +10 -3
  82. package/src/Arc.ts +117 -117
  83. package/src/Brep.ts +45 -1
  84. package/src/Circle.ts +166 -166
  85. package/src/Line.ts +171 -171
  86. package/src/Mesh.ts +361 -45
  87. package/src/MeshSurface.ts +1 -1
  88. package/src/NurbsCurve.ts +671 -655
  89. package/src/NurbsSurface.ts +721 -721
  90. package/src/PolyCurve.ts +249 -203
  91. package/src/Polyline.ts +333 -316
  92. package/src/Ray.ts +90 -90
  93. package/src/SculptSession.ts +377 -377
  94. package/src/engine-threaded.ts +39 -0
  95. package/src/engine.ts +46 -0
  96. package/src/index.ts +3 -1
  97. package/src/mesh-boolean.pool.ts +208 -86
  98. package/src/mesh-boolean.protocol.ts +44 -1
  99. package/src/mesh-boolean.worker.ts +170 -56
  100. package/src/wasm-active.ts +31 -0
  101. package/src/wasm-base64-threaded.ts +2 -0
  102. package/src/wasm-base64.ts +1 -1
  103. package/src/wasm-bindings-threaded.d.ts +2027 -0
  104. package/src/wasm-bindings-threaded.js +5317 -0
  105. package/src/wasm-bindings.d.ts +142 -0
  106. package/src/wasm-bindings.js +307 -0
  107. package/src/workerHelpers-threaded.js +107 -0
package/src/Mesh.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ensureInit } from "./engine.js";
1
+ import { ensureInit, isInitialized } from "./engine.js";
2
2
  import { Point } from "./Point.js";
3
3
  import { Vec3 } from "./Vec3.js";
4
4
  import { Plane } from "./Plane.js";
@@ -17,6 +17,7 @@ import {
17
17
  configureDefaultMeshBooleanWorkerPool,
18
18
  disposeMeshBooleanWorkerPools,
19
19
  runMeshBooleanInWorkerPool,
20
+ runMeshHeavyInWorkerPool,
20
21
  } from "./mesh-boolean.pool.js";
21
22
  import { MeshBooleanExecutionError } from "./mesh-boolean.protocol.js";
22
23
  import type {
@@ -24,8 +25,7 @@ import type {
24
25
  MeshBooleanLimits,
25
26
  MeshBooleanOptions,
26
27
  } from "./mesh-boolean.protocol.js";
27
- import * as wasm from "./wasm-bindings.js";
28
- import { mesh_topology_metrics, mesh_topology_metrics_raw } from "./wasm-bindings.js";
28
+ import { wasm } from "./wasm-active.js";
29
29
 
30
30
  export { MeshBooleanExecutionError };
31
31
  export type {
@@ -277,6 +277,26 @@ export interface MeshRepairResult {
277
277
  filledHoles: number;
278
278
  }
279
279
 
280
+ /** Which side of the source the thickened band occupies, relative to the
281
+ * surface normal: `"centered"` = ±d/2, `"up"` = [0, d], `"down"` = [-d, 0]. */
282
+ export type ThickenMode = "centered" | "up" | "down";
283
+
284
+ /** Options for {@link Mesh.thicken}. */
285
+ export interface MeshThickenOptions {
286
+ /** Band placement relative to the vertex normals. Default: `"centered"`. */
287
+ mode?: ThickenMode;
288
+ /** Miter compensation for uniform wall distance across creases
289
+ * (Blender Solidify "even thickness"). Default: true. */
290
+ evenThickness?: boolean;
291
+ /** Clamp for the even-thickness displacement multiplier at sharp creases.
292
+ * Default: 4. */
293
+ miterLimit?: number;
294
+ /** Proximity-weld the input first. Set false when the mesh's indices
295
+ * already carry the intended connectivity (kernel-produced meshes) — the
296
+ * weld is the largest cost at high triangle counts. Default: true. */
297
+ weldInput?: boolean;
298
+ }
299
+
280
300
  /** Result of {@link Mesh.makeWatertight}. */
281
301
  export interface MakeWatertightResult {
282
302
  mesh: Mesh;
@@ -362,6 +382,83 @@ export class Mesh {
362
382
  this._trustedBooleanInput = trustedBooleanInput;
363
383
  }
364
384
 
385
+ // ---- WASM-side residency (KERNEL-PERF-ARC lever 2) -----------------------
386
+ //
387
+ // Repeated queries on the same mesh (hover face groups, raycasts, render
388
+ // buffers, diagnostics) skip the per-call JS→WASM buffer copy by storing
389
+ // the buffer WASM-side once; raycasts additionally reuse a cached BVH.
390
+ // Residency is self-tuning: the FIRST hot call uses the classic buffer
391
+ // path (one-shot meshes never pay the store), the second call onward
392
+ // stores. `dispose()` releases eagerly; a FinalizationRegistry frees
393
+ // leaked ids on GC as a safety net. Buffers are immutable, so stored
394
+ // copies never need invalidation.
395
+
396
+ private _storeId: number | null = null;
397
+ private _hotCallCount = 0;
398
+
399
+ private static residencyFinalizer: FinalizationRegistry<number> | null =
400
+ typeof FinalizationRegistry !== "undefined"
401
+ ? new FinalizationRegistry<number>((id) => {
402
+ try {
403
+ if (isInitialized()) wasm.mesh_store_free(id);
404
+ } catch {
405
+ // engine torn down — nothing to free
406
+ }
407
+ })
408
+ : null;
409
+
410
+ /**
411
+ * WASM-side stored-mesh id, or 0 when this call should use the buffer
412
+ * path (first hot call, store failure, or disposed-and-cold mesh).
413
+ */
414
+ private storedId(): number {
415
+ if (this._storeId !== null) return this._storeId;
416
+ if (this._hotCallCount < 1) {
417
+ this._hotCallCount++;
418
+ return 0;
419
+ }
420
+ let id = 0;
421
+ try {
422
+ id = wasm.mesh_store(this._vertexCount, this._buffer);
423
+ } catch {
424
+ id = 0;
425
+ }
426
+ this._storeId = id;
427
+ if (id > 0) Mesh.residencyFinalizer?.register(this, id, this);
428
+ return id;
429
+ }
430
+
431
+ /**
432
+ * Release this mesh's WASM-side stored copy (idempotent). The mesh stays
433
+ * fully usable — the next repeated hot call re-stores it. Call from
434
+ * long-lived hosts (okdraw engine-bridge release paths) to return WASM
435
+ * memory promptly instead of waiting for GC.
436
+ */
437
+ dispose(): void {
438
+ if (this._storeId !== null && this._storeId > 0) {
439
+ try {
440
+ wasm.mesh_store_free(this._storeId);
441
+ } catch {
442
+ // engine torn down — nothing to free
443
+ }
444
+ Mesh.residencyFinalizer?.unregister(this);
445
+ }
446
+ this._storeId = null;
447
+ this._hotCallCount = 0;
448
+ }
449
+
450
+ /** Number of meshes currently resident WASM-side (diagnostics/tests). */
451
+ static residentMeshCount(): number {
452
+ ensureInit();
453
+ return wasm.mesh_store_count();
454
+ }
455
+
456
+ /** Approximate bytes of WASM-side resident mesh buffers (diagnostics). */
457
+ static residentMeshBytes(): number {
458
+ ensureInit();
459
+ return wasm.mesh_store_bytes();
460
+ }
461
+
365
462
  /**
366
463
  * Configure default size for the shared boolean worker pool.
367
464
  */
@@ -2198,7 +2295,10 @@ export class Mesh {
2198
2295
  if (this._edgeVertexPairs) return this._edgeVertexPairs;
2199
2296
 
2200
2297
  ensureInit();
2201
- const packed = wasm.mesh_get_edge_vertex_pairs(this._vertexCount, this._buffer);
2298
+ const sidEdges = this.storedId();
2299
+ const packed = sidEdges > 0
2300
+ ? wasm.mesh_get_edge_vertex_pairs_h(sidEdges)
2301
+ : wasm.mesh_get_edge_vertex_pairs(this._vertexCount, this._buffer);
2202
2302
  const edgeCount = Math.max(0, Math.floor(packed[0] ?? 0));
2203
2303
  const pairs: Array<[number, number]> = [];
2204
2304
  let off = 1;
@@ -3111,6 +3211,141 @@ export class Mesh {
3111
3211
  }
3112
3212
  }
3113
3213
 
3214
+ // ── Heavy async ops on the worker pool (KERNEL-PERF-ARC lever 4) ──
3215
+ //
3216
+ // Multi-second operations run off the main thread with the same
3217
+ // fallback contract as the pairwise async booleans: any worker-side
3218
+ // failure falls back to the synchronous main-thread path.
3219
+
3220
+ /** Async `Mesh.unionAll` on the worker pool. */
3221
+ static async unionAllAsync(
3222
+ meshes: Mesh[],
3223
+ options?: MeshBooleanAsyncOptions,
3224
+ ): Promise<Mesh> {
3225
+ try {
3226
+ const { buffer } = await runMeshHeavyInWorkerPool(
3227
+ "unionAll",
3228
+ meshes.map((m) => m._buffer),
3229
+ { ...options, trusted: meshes.map((m) => m._trustedBooleanInput) },
3230
+ );
3231
+ return Mesh.fromTrustedBuffer(buffer);
3232
+ } catch (error) {
3233
+ if (!shouldFallbackFromWorkerFailure(error)) throw error;
3234
+ console.warn("Mesh.unionAllAsync worker failed; falling back to main thread.", error);
3235
+ return Mesh.unionAll(meshes, {
3236
+ allowUnsafe: options?.allowUnsafe ?? true,
3237
+ limits: options?.limits,
3238
+ });
3239
+ }
3240
+ }
3241
+
3242
+ /** Async `mesh.subtractAll` on the worker pool. */
3243
+ async subtractAllAsync(
3244
+ cutters: Mesh[],
3245
+ options?: MeshBooleanAsyncOptions,
3246
+ ): Promise<Mesh> {
3247
+ try {
3248
+ const { buffer } = await runMeshHeavyInWorkerPool(
3249
+ "differenceAll",
3250
+ [this._buffer, ...cutters.map((m) => m._buffer)],
3251
+ {
3252
+ ...options,
3253
+ trusted: [this._trustedBooleanInput, ...cutters.map((m) => m._trustedBooleanInput)],
3254
+ },
3255
+ );
3256
+ return Mesh.fromTrustedBuffer(buffer);
3257
+ } catch (error) {
3258
+ if (!shouldFallbackFromWorkerFailure(error)) throw error;
3259
+ console.warn("Mesh.subtractAllAsync worker failed; falling back to main thread.", error);
3260
+ return this.subtractAll(cutters, {
3261
+ allowUnsafe: options?.allowUnsafe ?? true,
3262
+ limits: options?.limits,
3263
+ });
3264
+ }
3265
+ }
3266
+
3267
+ /** Async `mesh.makeWatertight` on the worker pool. NOTE: the async
3268
+ * variant reports the result mesh, method and effective resolution;
3269
+ * before/after diagnostics can be recomputed by the caller if needed. */
3270
+ async makeWatertightAsync(
3271
+ options: { resolution?: number; iso?: number } & MeshBooleanAsyncOptions = {},
3272
+ ): Promise<{ mesh: Mesh; method: "repair" | "remesh"; effectiveResolution: number }> {
3273
+ try {
3274
+ const { buffer, meta } = await runMeshHeavyInWorkerPool(
3275
+ "makeWatertight",
3276
+ [this._buffer],
3277
+ {
3278
+ ...options,
3279
+ trusted: [this._trustedBooleanInput],
3280
+ params: { resolution: options.resolution, iso: options.iso },
3281
+ },
3282
+ );
3283
+ return {
3284
+ mesh: Mesh.fromTrustedBuffer(buffer),
3285
+ method: (meta?.method as "repair" | "remesh") ?? "repair",
3286
+ effectiveResolution: Number(meta?.effectiveResolution ?? 0),
3287
+ };
3288
+ } catch (error) {
3289
+ if (!shouldFallbackFromWorkerFailure(error)) throw error;
3290
+ console.warn("Mesh.makeWatertightAsync worker failed; falling back to main thread.", error);
3291
+ const r = this.makeWatertight({ resolution: options.resolution, iso: options.iso });
3292
+ return { mesh: r.mesh, method: r.method, effectiveResolution: r.effectiveResolution };
3293
+ }
3294
+ }
3295
+
3296
+ /** Async `mesh.voxelRemesh` on the worker pool. */
3297
+ async voxelRemeshAsync(
3298
+ resolution = 0,
3299
+ iso = 0.5,
3300
+ options?: MeshBooleanAsyncOptions,
3301
+ ): Promise<Mesh> {
3302
+ try {
3303
+ const { buffer } = await runMeshHeavyInWorkerPool("voxelRemesh", [this._buffer], {
3304
+ ...options,
3305
+ trusted: [this._trustedBooleanInput],
3306
+ params: { resolution, iso },
3307
+ });
3308
+ return Mesh.fromTrustedBuffer(buffer);
3309
+ } catch (error) {
3310
+ if (!shouldFallbackFromWorkerFailure(error)) throw error;
3311
+ console.warn("Mesh.voxelRemeshAsync worker failed; falling back to main thread.", error);
3312
+ return this.voxelRemesh(resolution, iso);
3313
+ }
3314
+ }
3315
+
3316
+ /** Async `mesh.chamferEdges` on the worker pool. */
3317
+ async chamferEdgesAsync(
3318
+ edges: Array<{ a: Point | Vec3; b: Point | Vec3 }>,
3319
+ distance: number,
3320
+ options?: MeshBooleanAsyncOptions,
3321
+ ): Promise<Mesh> {
3322
+ const flat = new Float64Array(edges.length * 6);
3323
+ edges.forEach((edge, i) => {
3324
+ flat[i * 6] = edge.a.x;
3325
+ flat[i * 6 + 1] = edge.a.y;
3326
+ flat[i * 6 + 2] = edge.a.z;
3327
+ flat[i * 6 + 3] = edge.b.x;
3328
+ flat[i * 6 + 4] = edge.b.y;
3329
+ flat[i * 6 + 5] = edge.b.z;
3330
+ });
3331
+ try {
3332
+ const { buffer } = await runMeshHeavyInWorkerPool(
3333
+ "chamferEdges",
3334
+ [this._buffer, flat],
3335
+ {
3336
+ ...options,
3337
+ trusted: [this._trustedBooleanInput],
3338
+ params: { distance },
3339
+ },
3340
+ );
3341
+ return Mesh.fromTrustedBuffer(buffer);
3342
+ } catch (error) {
3343
+ if (!shouldFallbackFromWorkerFailure(error)) throw error;
3344
+ console.warn("Mesh.chamferEdgesAsync worker failed; falling back to main thread.", error);
3345
+ return this.chamferEdges(edges, distance);
3346
+ }
3347
+ }
3348
+
3114
3349
  // ── Intersection queries ───────────────────────────────────────
3115
3350
 
3116
3351
  /**
@@ -3459,7 +3694,10 @@ export class Mesh {
3459
3694
  getCoplanarFaceIndices(faceIndex: number): number[] {
3460
3695
  ensureInit();
3461
3696
  if (!Number.isFinite(faceIndex) || faceIndex < 0) return [];
3462
- const r = wasm.mesh_get_coplanar_face_indices(this._vertexCount, this._buffer, Math.floor(faceIndex));
3697
+ const sidCfi = this.storedId();
3698
+ const r = sidCfi > 0
3699
+ ? wasm.mesh_get_coplanar_face_indices_h(sidCfi, Math.floor(faceIndex))
3700
+ : wasm.mesh_get_coplanar_face_indices(this._vertexCount, this._buffer, Math.floor(faceIndex));
3463
3701
  const count = Math.max(0, Math.floor(r[0] ?? 0));
3464
3702
  const out: number[] = [];
3465
3703
  for (let i = 0; i < count; i++) {
@@ -3483,12 +3721,15 @@ export class Mesh {
3483
3721
  if (!Number.isFinite(faceIndex) || faceIndex < 0) {
3484
3722
  return { faceIndices: [], isPlanar: true };
3485
3723
  }
3486
- const r = wasm.mesh_get_smooth_face_group(
3487
- this._vertexCount,
3488
- this._buffer,
3489
- Math.floor(faceIndex),
3490
- maxAngleDeg,
3491
- );
3724
+ const sidSfg = this.storedId();
3725
+ const r = sidSfg > 0
3726
+ ? wasm.mesh_get_smooth_face_group_h(sidSfg, Math.floor(faceIndex), maxAngleDeg)
3727
+ : wasm.mesh_get_smooth_face_group(
3728
+ this._vertexCount,
3729
+ this._buffer,
3730
+ Math.floor(faceIndex),
3731
+ maxAngleDeg,
3732
+ );
3492
3733
  const count = Math.max(0, Math.floor(r[0] ?? 0));
3493
3734
  const isPlanar = (r[1] ?? 1) !== 0;
3494
3735
  const faceIndices: number[] = [];
@@ -3515,11 +3756,14 @@ export class Mesh {
3515
3756
  featureEdges: Float32Array;
3516
3757
  } {
3517
3758
  ensureInit();
3518
- const r: Float32Array = wasm.mesh_build_render_buffers(
3519
- this._vertexCount,
3520
- this._buffer,
3521
- creaseAngleDeg,
3522
- );
3759
+ const sidRb = this.storedId();
3760
+ const r: Float32Array = sidRb > 0
3761
+ ? wasm.mesh_build_render_buffers_h(sidRb, creaseAngleDeg)
3762
+ : wasm.mesh_build_render_buffers(
3763
+ this._vertexCount,
3764
+ this._buffer,
3765
+ creaseAngleDeg,
3766
+ );
3523
3767
  const vertexCount = Math.max(0, Math.floor(r[0] ?? 0));
3524
3768
  const posStart = 1;
3525
3769
  const norStart = posStart + vertexCount * 3;
@@ -3582,11 +3826,14 @@ export class Mesh {
3582
3826
  getCoplanarFaceGroupCentroid(faceIndex: number): Point | null {
3583
3827
  ensureInit();
3584
3828
  if (!Number.isFinite(faceIndex) || faceIndex < 0) return null;
3585
- const r = wasm.mesh_get_coplanar_face_group_centroid(
3586
- this._vertexCount,
3587
- this._buffer,
3588
- Math.floor(faceIndex),
3589
- );
3829
+ const sidCent = this.storedId();
3830
+ const r = sidCent > 0
3831
+ ? wasm.mesh_get_coplanar_face_group_centroid_h(sidCent, Math.floor(faceIndex))
3832
+ : wasm.mesh_get_coplanar_face_group_centroid(
3833
+ this._vertexCount,
3834
+ this._buffer,
3835
+ Math.floor(faceIndex),
3836
+ );
3590
3837
  if (!r || r.length < 3) return null;
3591
3838
  return new Point(r[0], r[1], r[2]);
3592
3839
  }
@@ -3600,11 +3847,14 @@ export class Mesh {
3600
3847
  getFaceBoundaryLoops(faceIndex: number): Point[][] {
3601
3848
  ensureInit();
3602
3849
  if (!Number.isFinite(faceIndex) || faceIndex < 0) return [];
3603
- const r = wasm.mesh_get_face_boundary_loops(
3604
- this._vertexCount,
3605
- this._buffer,
3606
- Math.floor(faceIndex),
3607
- );
3850
+ const sidLoops = this.storedId();
3851
+ const r = sidLoops > 0
3852
+ ? wasm.mesh_get_face_boundary_loops_h(sidLoops, Math.floor(faceIndex))
3853
+ : wasm.mesh_get_face_boundary_loops(
3854
+ this._vertexCount,
3855
+ this._buffer,
3856
+ Math.floor(faceIndex),
3857
+ );
3608
3858
  if (!r || r.length < 1) return [];
3609
3859
  const loopCount = Math.floor(r[0]);
3610
3860
  const loops: Point[][] = [];
@@ -3658,13 +3908,21 @@ export class Mesh {
3658
3908
  maxDistance = Number.POSITIVE_INFINITY,
3659
3909
  ): { point: Point; normal: Vec3; faceIndex: number; distance: number } | null {
3660
3910
  ensureInit();
3661
- const r = wasm.mesh_raycast(
3662
- this._vertexCount,
3663
- this._buffer,
3664
- origin.x, origin.y, origin.z,
3665
- direction.x, direction.y, direction.z,
3666
- maxDistance,
3667
- );
3911
+ const sidRay = this.storedId();
3912
+ const r = sidRay > 0
3913
+ ? wasm.mesh_raycast_h(
3914
+ sidRay,
3915
+ origin.x, origin.y, origin.z,
3916
+ direction.x, direction.y, direction.z,
3917
+ maxDistance,
3918
+ )
3919
+ : wasm.mesh_raycast(
3920
+ this._vertexCount,
3921
+ this._buffer,
3922
+ origin.x, origin.y, origin.z,
3923
+ direction.x, direction.y, direction.z,
3924
+ maxDistance,
3925
+ );
3668
3926
  if (!r || r.length < 8) return null;
3669
3927
  return {
3670
3928
  point: new Point(r[0], r[1], r[2]),
@@ -3683,13 +3941,21 @@ export class Mesh {
3683
3941
  maxDistance = Number.POSITIVE_INFINITY,
3684
3942
  ): Array<{ point: Point; normal: Vec3; faceIndex: number; distance: number }> {
3685
3943
  ensureInit();
3686
- const buf = wasm.mesh_raycast_all(
3687
- this._vertexCount,
3688
- this._buffer,
3689
- origin.x, origin.y, origin.z,
3690
- direction.x, direction.y, direction.z,
3691
- maxDistance,
3692
- );
3944
+ const sidRayAll = this.storedId();
3945
+ const buf = sidRayAll > 0
3946
+ ? wasm.mesh_raycast_all_h(
3947
+ sidRayAll,
3948
+ origin.x, origin.y, origin.z,
3949
+ direction.x, direction.y, direction.z,
3950
+ maxDistance,
3951
+ )
3952
+ : wasm.mesh_raycast_all(
3953
+ this._vertexCount,
3954
+ this._buffer,
3955
+ origin.x, origin.y, origin.z,
3956
+ direction.x, direction.y, direction.z,
3957
+ maxDistance,
3958
+ );
3693
3959
  const count = Math.max(0, Math.floor(buf[0] ?? 0));
3694
3960
  const hits: Array<{ point: Point; normal: Vec3; faceIndex: number; distance: number }> = [];
3695
3961
  let off = 1;
@@ -3774,8 +4040,8 @@ export class Mesh {
3774
4040
 
3775
4041
  ensureInit();
3776
4042
  const metrics = this._trustedBooleanInput
3777
- ? mesh_topology_metrics_raw(this._vertexCount, this._buffer)
3778
- : mesh_topology_metrics(this._vertexCount, this._buffer);
4043
+ ? wasm.mesh_topology_metrics_raw(this._vertexCount, this._buffer)
4044
+ : wasm.mesh_topology_metrics(this._vertexCount, this._buffer);
3779
4045
  const boundaryEdges = Math.floor(metrics[0] ?? 0);
3780
4046
  const nonManifoldEdges = Math.floor(metrics[1] ?? 0);
3781
4047
  this._topologyMetricsCache = { boundaryEdges, nonManifoldEdges };
@@ -3795,7 +4061,10 @@ export class Mesh {
3795
4061
  */
3796
4062
  diagnostics(): MeshDiagnostics {
3797
4063
  ensureInit();
3798
- const r = wasm.mesh_repair_diagnostics(this._vertexCount, this._buffer);
4064
+ const sidDiag = this.storedId();
4065
+ const r = sidDiag > 0
4066
+ ? wasm.mesh_repair_diagnostics_h(sidDiag)
4067
+ : wasm.mesh_repair_diagnostics(this._vertexCount, this._buffer);
3799
4068
  return parseMeshDiagnostics(r, 0);
3800
4069
  }
3801
4070
 
@@ -3806,7 +4075,10 @@ export class Mesh {
3806
4075
  */
3807
4076
  boundaryEdgeSegments(): Float64Array {
3808
4077
  ensureInit();
3809
- return wasm.mesh_boundary_edge_segments(this._vertexCount, this._buffer);
4078
+ const sidBes = this.storedId();
4079
+ return sidBes > 0
4080
+ ? wasm.mesh_boundary_edge_segments_h(sidBes)
4081
+ : wasm.mesh_boundary_edge_segments(this._vertexCount, this._buffer);
3810
4082
  }
3811
4083
 
3812
4084
  /**
@@ -3857,7 +4129,10 @@ export class Mesh {
3857
4129
  */
3858
4130
  featureEdgeChains(): Array<{ closed: boolean; points: Float64Array }> {
3859
4131
  ensureInit();
3860
- const r = wasm.mesh_feature_edge_chains(this._vertexCount, this._buffer);
4132
+ const sidFec = this.storedId();
4133
+ const r = sidFec > 0
4134
+ ? wasm.mesh_feature_edge_chains_h(sidFec)
4135
+ : wasm.mesh_feature_edge_chains(this._vertexCount, this._buffer);
3861
4136
  if (!r.length) {
3862
4137
  throw new Error(
3863
4138
  `Mesh.featureEdgeChains() failed: ${wasm.geometry_last_error() || "unknown error"}`,
@@ -3876,6 +4151,47 @@ export class Mesh {
3876
4151
  return chains;
3877
4152
  }
3878
4153
 
4154
+ /**
4155
+ * Thicken (solidify) this mesh into a solid: an open sheet becomes a
4156
+ * watertight solid (bottom shell + top shell + boundary walls); a closed
4157
+ * mesh becomes a hollow shell of wall thickness `distance`. Displacement is
4158
+ * along angle-weighted vertex normals with even-thickness miter
4159
+ * compensation (see {@link MeshThickenOptions}). Linear complexity — no CSG.
4160
+ *
4161
+ * Self-intersection where the thickness exceeds the local curvature radius
4162
+ * is not resolved (standard offset semantics). Throws on non-manifold or
4163
+ * non-orientable input, or non-positive distance.
4164
+ */
4165
+ thicken(distance: number, options: MeshThickenOptions = {}): Mesh {
4166
+ ensureInit();
4167
+ if (!Number.isFinite(distance) || distance <= 0) {
4168
+ throw new Error("Mesh.thicken() requires a positive distance");
4169
+ }
4170
+ const mode = options.mode ?? "centered";
4171
+ const [bottom, top] =
4172
+ mode === "up"
4173
+ ? [0, distance]
4174
+ : mode === "down"
4175
+ ? [-distance, 0]
4176
+ : [-distance / 2, distance / 2];
4177
+ const flags =
4178
+ (options.evenThickness === false ? 1 : 0) | (options.weldInput === false ? 2 : 0);
4179
+ const r = wasm.mesh_thicken(
4180
+ this._vertexCount,
4181
+ this._buffer,
4182
+ bottom,
4183
+ top,
4184
+ flags,
4185
+ options.miterLimit ?? 0,
4186
+ );
4187
+ if (!r.length) {
4188
+ throw new Error(
4189
+ `Mesh.thicken() failed: ${wasm.geometry_last_error() || "unknown error"}`,
4190
+ );
4191
+ }
4192
+ return Mesh.fromTrustedBuffer(r as Float64Array);
4193
+ }
4194
+
3879
4195
  /**
3880
4196
  * Geometry-preserving repair: weld coincident vertices, drop degenerate /
3881
4197
  * duplicate triangles, remove orphan vertices, make winding consistent +
@@ -4,7 +4,7 @@ import type { Mesh, MeshPlanarFace } from "./Mesh.js";
4
4
  import { Point } from "./Point.js";
5
5
  import type { Surface } from "./Surface.js";
6
6
  import { Vec3 } from "./Vec3.js";
7
- import * as wasm from "./wasm-bindings.js";
7
+ import { wasm } from "./wasm-active.js";
8
8
 
9
9
  /**
10
10
  * Logical planar surface extracted from a coplanar connected face group on a mesh.