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.
- package/dist/Arc.js +1 -1
- package/dist/Arc.js.map +1 -1
- package/dist/Brep.d.ts +18 -0
- package/dist/Brep.d.ts.map +1 -1
- package/dist/Brep.js +32 -1
- package/dist/Brep.js.map +1 -1
- package/dist/Circle.js +1 -1
- package/dist/Circle.js.map +1 -1
- package/dist/Line.js +1 -1
- package/dist/Line.js.map +1 -1
- package/dist/Mesh.d.ts +71 -0
- package/dist/Mesh.d.ts.map +1 -1
- package/dist/Mesh.js +264 -21
- package/dist/Mesh.js.map +1 -1
- package/dist/MeshSurface.js +1 -1
- package/dist/MeshSurface.js.map +1 -1
- package/dist/NurbsCurve.d.ts +6 -0
- package/dist/NurbsCurve.d.ts.map +1 -1
- package/dist/NurbsCurve.js +10 -1
- package/dist/NurbsCurve.js.map +1 -1
- package/dist/NurbsSurface.js +1 -1
- package/dist/NurbsSurface.js.map +1 -1
- package/dist/PolyCurve.d.ts +15 -0
- package/dist/PolyCurve.d.ts.map +1 -1
- package/dist/PolyCurve.js +31 -1
- package/dist/PolyCurve.js.map +1 -1
- package/dist/Polyline.d.ts +6 -0
- package/dist/Polyline.d.ts.map +1 -1
- package/dist/Polyline.js +12 -1
- package/dist/Polyline.js.map +1 -1
- package/dist/Ray.js +1 -1
- package/dist/Ray.js.map +1 -1
- package/dist/SculptSession.js +1 -1
- package/dist/SculptSession.js.map +1 -1
- package/dist/engine-threaded.d.ts +2 -0
- package/dist/engine-threaded.d.ts.map +1 -0
- package/dist/engine-threaded.js +33 -0
- package/dist/engine-threaded.js.map +1 -0
- package/dist/engine.d.ts +21 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +42 -0
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mesh-boolean.pool.d.ts +23 -1
- package/dist/mesh-boolean.pool.d.ts.map +1 -1
- package/dist/mesh-boolean.pool.js +97 -12
- package/dist/mesh-boolean.pool.js.map +1 -1
- package/dist/mesh-boolean.protocol.d.ts +21 -1
- package/dist/mesh-boolean.protocol.d.ts.map +1 -1
- package/dist/mesh-boolean.protocol.js.map +1 -1
- package/dist/mesh-boolean.worker.d.ts.map +1 -1
- package/dist/mesh-boolean.worker.js +94 -1
- package/dist/mesh-boolean.worker.js.map +1 -1
- package/dist/wasm-active.d.ts +20 -0
- package/dist/wasm-active.d.ts.map +1 -0
- package/dist/wasm-active.js +26 -0
- package/dist/wasm-active.js.map +1 -0
- package/dist/wasm-base64-threaded.d.ts +2 -0
- package/dist/wasm-base64-threaded.d.ts.map +1 -0
- package/dist/wasm-base64-threaded.js +3 -0
- package/dist/wasm-base64-threaded.js.map +1 -0
- package/dist/wasm-base64.d.ts +1 -1
- package/dist/wasm-base64.d.ts.map +1 -1
- package/dist/wasm-base64.js +1 -1
- package/dist/wasm-base64.js.map +1 -1
- package/dist/wasm-bindings-threaded.d.ts +2716 -0
- package/dist/wasm-bindings-threaded.d.ts.map +1 -0
- package/dist/wasm-bindings-threaded.js +5088 -0
- package/dist/wasm-bindings-threaded.js.map +1 -0
- package/dist/wasm-bindings.d.ts +182 -0
- package/dist/wasm-bindings.d.ts.map +1 -1
- package/dist/wasm-bindings.js +288 -0
- package/dist/wasm-bindings.js.map +1 -1
- package/dist/workerHelpers-threaded.d.ts +2 -0
- package/dist/workerHelpers-threaded.d.ts.map +1 -0
- package/dist/workerHelpers-threaded.js +100 -0
- package/dist/workerHelpers-threaded.js.map +1 -0
- package/package.json +10 -3
- package/src/Arc.ts +117 -117
- package/src/Brep.ts +45 -1
- package/src/Circle.ts +166 -166
- package/src/Line.ts +171 -171
- package/src/Mesh.ts +361 -45
- package/src/MeshSurface.ts +1 -1
- package/src/NurbsCurve.ts +671 -655
- package/src/NurbsSurface.ts +721 -721
- package/src/PolyCurve.ts +249 -203
- package/src/Polyline.ts +333 -316
- package/src/Ray.ts +90 -90
- package/src/SculptSession.ts +377 -377
- package/src/engine-threaded.ts +39 -0
- package/src/engine.ts +46 -0
- package/src/index.ts +3 -1
- package/src/mesh-boolean.pool.ts +208 -86
- package/src/mesh-boolean.protocol.ts +44 -1
- package/src/mesh-boolean.worker.ts +170 -56
- package/src/wasm-active.ts +31 -0
- package/src/wasm-base64-threaded.ts +2 -0
- package/src/wasm-base64.ts +1 -1
- package/src/wasm-bindings-threaded.d.ts +2027 -0
- package/src/wasm-bindings-threaded.js +5317 -0
- package/src/wasm-bindings.d.ts +142 -0
- package/src/wasm-bindings.js +307 -0
- 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
|
|
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
|
|
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
|
|
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
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
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
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
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
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
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
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
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
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
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
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 +
|
package/src/MeshSurface.ts
CHANGED
|
@@ -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
|
|
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.
|