brepjs 20.12.5 → 20.12.7
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/2d/blueprints/booleanHelpers.d.ts +0 -5
- package/dist/2d/blueprints/intersectionSegments.d.ts +6 -5
- package/dist/2d/lib/offset.d.ts +1 -1
- package/dist/2d.cjs +6 -6
- package/dist/2d.js +6 -6
- package/dist/{adjacencyFns-B2Po1gPs.js → adjacencyFns-hMihivxC.js} +3 -3
- package/dist/{adjacencyFns-Cho9v3Q3.cjs → adjacencyFns-mMmTBUBY.cjs} +3 -3
- package/dist/{blueprint-ERmwk6zG.cjs → blueprint-17RUHsoP.cjs} +7 -8
- package/dist/{blueprint-Zmt5-07y.js → blueprint-BS50D3J_.js} +7 -8
- package/dist/{blueprintFns-B_sxtIo7.cjs → blueprintFns-Dw5_4_Bk.cjs} +2 -2
- package/dist/{blueprintFns-Dtm-XukP.js → blueprintFns-zy5lo2IF.js} +2 -2
- package/dist/{blueprintSketcher-CZyokLGJ.cjs → blueprintSketcher-BG--DxWB.cjs} +32 -14
- package/dist/{blueprintSketcher-CwTAtrkY.js → blueprintSketcher-C1WCI6E0.js} +32 -14
- package/dist/{boolean2D-CRuNKgXs.cjs → boolean2D-7SQ3B0pC.cjs} +42 -50
- package/dist/{boolean2D-yUpS8onH.js → boolean2D-CsdyfjFE.js} +42 -50
- package/dist/brepjs.cjs +30 -30
- package/dist/brepjs.js +30 -30
- package/dist/{cameraFns-DB1IyMmS.cjs → cameraFns-BAtzhCYM.cjs} +3 -3
- package/dist/{cameraFns-DRok4UxZ.js → cameraFns-BVWyAscu.js} +3 -3
- package/dist/core.cjs +2 -2
- package/dist/core.js +2 -2
- package/dist/{cornerFinder-D5qnDbtZ.js → cornerFinder-Dv_2rmaY.js} +1 -1
- package/dist/{cornerFinder-DfYBHHZG.cjs → cornerFinder-e4Wc7_yp.cjs} +1 -1
- package/dist/{curveFns-BKT4Sg8q.cjs → curveFns-BAM7QHrP.cjs} +1 -1
- package/dist/{curveFns-BGPSFUWB.js → curveFns-CEI-rouh.js} +1 -1
- package/dist/{drawFns-aGufr-pr.js → drawFns-Cl1i8B_B.js} +18 -13
- package/dist/{drawFns-D8e5RiRe.cjs → drawFns-d-RvC6u6.cjs} +18 -13
- package/dist/{faceFns-Cx7cwUMx.cjs → faceFns-C-2ttg8X.cjs} +2 -2
- package/dist/{faceFns-lE2OvBfq.js → faceFns-Q87HCJrp.js} +2 -2
- package/dist/{healingFns-B94zSk1-.js → healingFns-Cm4NkKD8.js} +5 -5
- package/dist/{healingFns-CcgyrL6h.cjs → healingFns-DZkO5okn.cjs} +5 -5
- package/dist/{helpers-C4f2MV-r.cjs → helpers-BdGGaLhF.cjs} +6 -6
- package/dist/{helpers-CNgipe6l.js → helpers-DpGcM2qZ.js} +6 -6
- package/dist/{importFns-gB1X3ti2.js → importFns-Bp-p0jiP.js} +2 -2
- package/dist/{importFns-fIdcaMqt.cjs → importFns-DLiP26NW.cjs} +2 -2
- package/dist/io.cjs +2 -2
- package/dist/io.js +2 -2
- package/dist/kernel/bspline2dFit.d.ts +42 -0
- package/dist/kernel/geometry2d.d.ts +45 -0
- package/dist/kernel/occt/kernel2dOps.d.ts +1 -1
- package/dist/kernel/occtWasm/kernel2dOps.d.ts +7 -4
- package/dist/kernel/occtWasm/occtWasmAdapter.cjs +1 -1
- package/dist/kernel/occtWasm/occtWasmAdapter.d.ts +2 -2
- package/dist/kernel/occtWasm/occtWasmAdapter.js +1 -1
- package/dist/kernel/occtWasm/occtWasmTypes.d.ts +2 -0
- package/dist/{loftFns-AHaYxJyS.cjs → loftFns-DGV1z2NJ.cjs} +1 -1
- package/dist/{loftFns-mbyBvJ8Y.js → loftFns-DQ7Z29n3.js} +1 -1
- package/dist/{measureFns-CuabRDRL.cjs → measureFns-BdSjw5fr.cjs} +3 -3
- package/dist/{measureFns-C94G0fH8.js → measureFns-BrdCH4cH.js} +3 -3
- package/dist/measurement.cjs +2 -2
- package/dist/measurement.js +2 -2
- package/dist/{meshFns-GXF5gPZL.cjs → meshFns-BeubBqEI.cjs} +3 -3
- package/dist/{meshFns-jarDZKw1.js → meshFns-UE8tByyw.js} +3 -3
- package/dist/{occtWasmAdapter-DeoeK2iJ.cjs → occtWasmAdapter-D3qerQTq.cjs} +1260 -291
- package/dist/{occtWasmAdapter-DlNmVNUn.js → occtWasmAdapter-DA7oMgcQ.js} +1249 -292
- package/dist/operations.cjs +2 -2
- package/dist/operations.js +2 -2
- package/dist/{originTrackingFns-BxeUD2NM.js → originTrackingFns-c2qgRZ_9.js} +2 -2
- package/dist/{originTrackingFns-C-yuMV3m.cjs → originTrackingFns-rEKOAP65.cjs} +2 -2
- package/dist/{primitiveFns-CualoFAk.js → primitiveFns-3-gs-FUX.js} +4 -4
- package/dist/{primitiveFns-BVAkYHVf.cjs → primitiveFns-CLGVLwG8.cjs} +4 -4
- package/dist/{principalBoxFns-DHh5_HCw.cjs → principalBoxFns-CiXzjOkr.cjs} +3 -3
- package/dist/{principalBoxFns-DtI7tVuS.js → principalBoxFns-RMZItjfR.js} +3 -3
- package/dist/projection.cjs +1 -1
- package/dist/projection.js +1 -1
- package/dist/query.cjs +2 -2
- package/dist/query.js +2 -2
- package/dist/{refResolveFns-CaAw-ya3.cjs → refResolveFns-DNX0bCrP.cjs} +6 -6
- package/dist/{refResolveFns--SRyN2qa.js → refResolveFns-kJg2iCad.js} +6 -6
- package/dist/{shapeFns-CdrDroOM.js → shapeFns-CZ6VF1yd.js} +2 -2
- package/dist/{shapeFns-CwjjUOr0.cjs → shapeFns-SgIrOjN4.cjs} +2 -2
- package/dist/shapeRef.cjs +1 -1
- package/dist/shapeRef.js +1 -1
- package/dist/{shapeTypes-PNVEv6ga.js → shapeTypes-BNGu103j.js} +10 -16
- package/dist/{shapeTypes-D938X2mg.cjs → shapeTypes-MUEH4XuH.cjs} +10 -16
- package/dist/sketching.cjs +3 -3
- package/dist/sketching.js +3 -3
- package/dist/{solidBuilders-Hft8Cr6a.js → solidBuilders-Bmr59cGp.js} +5 -5
- package/dist/{solidBuilders-CAkBQKWb.cjs → solidBuilders-DdG4NPHM.cjs} +5 -5
- package/dist/{surfaceBuilders-CrO_BBKM.js → surfaceBuilders-DI376ai0.js} +3 -3
- package/dist/{surfaceBuilders-BKCiVf7T.cjs → surfaceBuilders-DOmDVuMs.cjs} +3 -3
- package/dist/text.cjs +2 -2
- package/dist/text.js +2 -2
- package/dist/{textBlueprints-C9HeYc5a.cjs → textBlueprints-B7Vhr0xu.cjs} +7 -7
- package/dist/{textBlueprints-B8q2a-nN.js → textBlueprints-CE0oxB43.js} +7 -7
- package/dist/{textMetrics-1fhCSAGF.cjs → textMetrics-C6_kSht6.cjs} +1 -1
- package/dist/{textMetrics-DJfTSy6w.js → textMetrics-_VGqdewQ.js} +1 -1
- package/dist/{threadFns-CVIEbOW3.cjs → threadFns-1H60LrFX.cjs} +7 -7
- package/dist/{threadFns-f2KwibLR.js → threadFns-CHdY-4Cz.js} +7 -7
- package/dist/topology.cjs +9 -9
- package/dist/topology.js +9 -9
- package/dist/{topologyQueryFns-4WYqtSFF.cjs → topologyQueryFns-C_fXO6xL.cjs} +1 -1
- package/dist/{topologyQueryFns-BZUY10TD.js → topologyQueryFns-CltEv7Pi.js} +1 -1
- package/dist/{validityTypes-tWSx_zAH.cjs → validityTypes-CGX2ES81.cjs} +1 -1
- package/dist/{validityTypes-DAXIqwZM.js → validityTypes-GanLrmvY.js} +1 -1
- package/package.json +1 -1
|
@@ -296,9 +296,393 @@ function quickHull(inputPoints, tolerance) {
|
|
|
296
296
|
points
|
|
297
297
|
};
|
|
298
298
|
}
|
|
299
|
+
// src/kernel/bspline2dFit.ts
|
|
300
|
+
/* v8 ignore file -- pure-math hot path for 2D curve ops; excluded from coverage for the
|
|
301
|
+
same reason as geometry2d.ts (instrumentation overhead times out 2D-heavy tests). */
|
|
302
|
+
function findSpan(U, p, n, u) {
|
|
303
|
+
if (u >= U[n + 1]) return n;
|
|
304
|
+
if (u <= U[p]) return p;
|
|
305
|
+
let low = p;
|
|
306
|
+
let high = n + 1;
|
|
307
|
+
let mid = low + high >> 1;
|
|
308
|
+
while (u < U[mid] || u >= U[mid + 1]) {
|
|
309
|
+
if (u < U[mid]) high = mid;
|
|
310
|
+
else low = mid;
|
|
311
|
+
mid = low + high >> 1;
|
|
312
|
+
}
|
|
313
|
+
return mid;
|
|
314
|
+
}
|
|
315
|
+
function basisFuns(U, span, u, p) {
|
|
316
|
+
const N = new Array(p + 1).fill(0);
|
|
317
|
+
const left = new Array(p + 1).fill(0);
|
|
318
|
+
const right = new Array(p + 1).fill(0);
|
|
319
|
+
N[0] = 1;
|
|
320
|
+
for (let j = 1; j <= p; j++) {
|
|
321
|
+
left[j] = u - U[span + 1 - j];
|
|
322
|
+
right[j] = U[span + j] - u;
|
|
323
|
+
let saved = 0;
|
|
324
|
+
for (let r = 0; r < j; r++) {
|
|
325
|
+
const denom = right[r + 1] + left[j - r];
|
|
326
|
+
const temp = denom === 0 ? 0 : N[r] / denom;
|
|
327
|
+
N[r] = saved + right[r + 1] * temp;
|
|
328
|
+
saved = left[j - r] * temp;
|
|
329
|
+
}
|
|
330
|
+
N[j] = saved;
|
|
331
|
+
}
|
|
332
|
+
return N;
|
|
333
|
+
}
|
|
334
|
+
function evaluateFull(U, poles, p, u) {
|
|
335
|
+
const span = findSpan(U, p, poles.length - 1, u);
|
|
336
|
+
const N = basisFuns(U, span, u, p);
|
|
337
|
+
let x = 0;
|
|
338
|
+
let y = 0;
|
|
339
|
+
for (let j = 0; j <= p; j++) {
|
|
340
|
+
const pole = poles[span - p + j];
|
|
341
|
+
x += N[j] * pole[0];
|
|
342
|
+
y += N[j] * pole[1];
|
|
343
|
+
}
|
|
344
|
+
return [x, y];
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Gaussian elimination without pivoting on a banded system. B-spline collocation
|
|
348
|
+
* matrices are totally positive, so elimination in natural order is stable.
|
|
349
|
+
*/
|
|
350
|
+
function solveBanded(rows, rhs) {
|
|
351
|
+
const n = rows.length;
|
|
352
|
+
let kl = 0;
|
|
353
|
+
let ku = 0;
|
|
354
|
+
rows.forEach((row, k) => {
|
|
355
|
+
kl = Math.max(kl, k - row.start);
|
|
356
|
+
ku = Math.max(ku, row.start + row.vals.length - 1 - k);
|
|
357
|
+
});
|
|
358
|
+
const width = kl + ku + 1;
|
|
359
|
+
const band = rows.map((row, k) => {
|
|
360
|
+
const r = new Float64Array(width);
|
|
361
|
+
row.vals.forEach((v, j) => {
|
|
362
|
+
r[row.start + j - k + kl] = v;
|
|
363
|
+
});
|
|
364
|
+
return r;
|
|
365
|
+
});
|
|
366
|
+
const bx = rhs.map((q) => q[0]);
|
|
367
|
+
const by = rhs.map((q) => q[1]);
|
|
368
|
+
const at = (r, c) => band[r][c - r + kl];
|
|
369
|
+
for (let i = 0; i < n; i++) {
|
|
370
|
+
const pivot = at(i, i);
|
|
371
|
+
if (Math.abs(pivot) < 1e-300) throw new Error("interpolateBSpline2d: singular system");
|
|
372
|
+
for (let r = i + 1; r <= Math.min(n - 1, i + kl); r++) {
|
|
373
|
+
const f = at(r, i) / pivot;
|
|
374
|
+
if (f === 0) continue;
|
|
375
|
+
const rowR = band[r];
|
|
376
|
+
for (let c = i; c <= Math.min(n - 1, i + ku); c++) rowR[c - r + kl] = rowR[c - r + kl] - f * at(i, c);
|
|
377
|
+
bx[r] = bx[r] - f * bx[i];
|
|
378
|
+
by[r] = by[r] - f * by[i];
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const out = new Array(n);
|
|
382
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
383
|
+
let sx = bx[i];
|
|
384
|
+
let sy = by[i];
|
|
385
|
+
for (let c = i + 1; c <= Math.min(n - 1, i + ku); c++) {
|
|
386
|
+
const a = at(i, c);
|
|
387
|
+
sx -= a * out[c][0];
|
|
388
|
+
sy -= a * out[c][1];
|
|
389
|
+
}
|
|
390
|
+
const d = at(i, i);
|
|
391
|
+
out[i] = [sx / d, sy / d];
|
|
392
|
+
}
|
|
393
|
+
return out;
|
|
394
|
+
}
|
|
395
|
+
function chordLengthParams(points) {
|
|
396
|
+
const n = points.length - 1;
|
|
397
|
+
const d = [0];
|
|
398
|
+
for (let k = 1; k <= n; k++) {
|
|
399
|
+
const a = points[k - 1];
|
|
400
|
+
const b = points[k];
|
|
401
|
+
d.push(d[k - 1] + Math.hypot(b[0] - a[0], b[1] - a[1]));
|
|
402
|
+
}
|
|
403
|
+
const total = d[n];
|
|
404
|
+
if (total < 1e-300) return d.map((_, k) => k / n);
|
|
405
|
+
return d.map((v) => v / total);
|
|
406
|
+
}
|
|
407
|
+
function compressKnots(U) {
|
|
408
|
+
const knots = [];
|
|
409
|
+
const multiplicities = [];
|
|
410
|
+
for (const u of U) {
|
|
411
|
+
const last = knots.length - 1;
|
|
412
|
+
if (last >= 0 && u === knots[last]) multiplicities[last] = multiplicities[last] + 1;
|
|
413
|
+
else {
|
|
414
|
+
knots.push(u);
|
|
415
|
+
multiplicities.push(1);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
knots,
|
|
420
|
+
multiplicities
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Interpolate `points` with a clamped B-spline of `degree` (capped at
|
|
425
|
+
* `points.length - 1`). `params` are the increasing curve parameters assigned to
|
|
426
|
+
* the points, normalised to [0, 1]; chord length when omitted.
|
|
427
|
+
*/
|
|
428
|
+
function interpolateBSpline2d(points, degree = 3, params) {
|
|
429
|
+
const n = points.length - 1;
|
|
430
|
+
if (n < 1) throw new Error("interpolateBSpline2d: need at least 2 points");
|
|
431
|
+
const p = Math.max(1, Math.min(degree, n));
|
|
432
|
+
const ub = params ? [...params] : chordLengthParams(points);
|
|
433
|
+
const U = [];
|
|
434
|
+
for (let i = 0; i <= p; i++) U.push(0);
|
|
435
|
+
for (let j = 1; j <= n - p; j++) {
|
|
436
|
+
let sum = 0;
|
|
437
|
+
for (let i = j; i < j + p; i++) sum += ub[i];
|
|
438
|
+
U.push(sum / p);
|
|
439
|
+
}
|
|
440
|
+
for (let i = 0; i <= p; i++) U.push(1);
|
|
441
|
+
const poles = solveBanded(ub.map((u) => {
|
|
442
|
+
const span = findSpan(U, p, n, u);
|
|
443
|
+
return {
|
|
444
|
+
start: span - p,
|
|
445
|
+
vals: basisFuns(U, span, u, p)
|
|
446
|
+
};
|
|
447
|
+
}), points.map((q) => [q[0], q[1]]));
|
|
448
|
+
poles[0] = [points[0][0], points[0][1]];
|
|
449
|
+
poles[n] = [points[n][0], points[n][1]];
|
|
450
|
+
const { knots, multiplicities } = compressKnots(U);
|
|
451
|
+
return {
|
|
452
|
+
__bk2d: "bspline",
|
|
453
|
+
poles,
|
|
454
|
+
knots,
|
|
455
|
+
multiplicities,
|
|
456
|
+
degree: p,
|
|
457
|
+
isPeriodic: false
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function fitPiece(evaluate, s0, s1, tolerance, degree, minSegments, maxSegments) {
|
|
461
|
+
let segments = Math.min(maxSegments, Math.max(2, minSegments));
|
|
462
|
+
for (;;) {
|
|
463
|
+
const params = [];
|
|
464
|
+
const points = [];
|
|
465
|
+
for (let i = 0; i <= segments; i++) {
|
|
466
|
+
const u = i / segments;
|
|
467
|
+
params.push(u);
|
|
468
|
+
points.push(evaluate(s0 + (s1 - s0) * u));
|
|
469
|
+
}
|
|
470
|
+
const curve = interpolateBSpline2d(points, degree, params);
|
|
471
|
+
if (segments >= maxSegments) return curve;
|
|
472
|
+
const U = [];
|
|
473
|
+
curve.knots.forEach((k, i) => {
|
|
474
|
+
for (let m = 0; m < (curve.multiplicities[i] ?? 1); m++) U.push(k);
|
|
475
|
+
});
|
|
476
|
+
let maxErr = 0;
|
|
477
|
+
for (let i = 0; i < segments && maxErr <= tolerance; i++) {
|
|
478
|
+
const u = (i + .5) / segments;
|
|
479
|
+
const [fx, fy] = evaluateFull(U, curve.poles, curve.degree, u);
|
|
480
|
+
const [tx, ty] = evaluate(s0 + (s1 - s0) * u);
|
|
481
|
+
maxErr = Math.max(maxErr, Math.hypot(fx - tx, fy - ty));
|
|
482
|
+
}
|
|
483
|
+
if (maxErr <= tolerance) return curve;
|
|
484
|
+
segments = Math.min(maxSegments, segments * 2);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Concatenate clamped B-splines of one degree, each running over [0, 1], onto
|
|
489
|
+
* the consecutive intervals of `cuts`. Joints are C0 (knot multiplicity =
|
|
490
|
+
* degree); each piece starts on the pole the previous one ended on.
|
|
491
|
+
*/
|
|
492
|
+
function joinPieces(pieces, cuts) {
|
|
493
|
+
const p = pieces[0].degree;
|
|
494
|
+
const knots = [];
|
|
495
|
+
const multiplicities = [];
|
|
496
|
+
const poles = [];
|
|
497
|
+
pieces.forEach((piece, i) => {
|
|
498
|
+
const a = cuts[i];
|
|
499
|
+
const b = cuts[i + 1];
|
|
500
|
+
if (i === 0) {
|
|
501
|
+
knots.push(a);
|
|
502
|
+
multiplicities.push(p + 1);
|
|
503
|
+
poles.push(...piece.poles);
|
|
504
|
+
} else poles.push(...piece.poles.slice(1));
|
|
505
|
+
for (let j = 1; j < piece.knots.length - 1; j++) {
|
|
506
|
+
knots.push(a + piece.knots[j] * (b - a));
|
|
507
|
+
multiplicities.push(piece.multiplicities[j]);
|
|
508
|
+
}
|
|
509
|
+
knots.push(b);
|
|
510
|
+
multiplicities.push(i === pieces.length - 1 ? p + 1 : p);
|
|
511
|
+
});
|
|
512
|
+
return {
|
|
513
|
+
__bk2d: "bspline",
|
|
514
|
+
poles,
|
|
515
|
+
knots,
|
|
516
|
+
multiplicities,
|
|
517
|
+
degree: p,
|
|
518
|
+
isPeriodic: false
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Approximate the parametric curve `evaluate` over [s0, s1] by interpolating
|
|
523
|
+
* uniformly spaced samples, doubling the sample count until the interpolant is
|
|
524
|
+
* within `tolerance` of the curve at every mid-sample parameter (or the
|
|
525
|
+
* segment cap is reached). With `breaks`, each piece between them is fitted
|
|
526
|
+
* separately and the pieces are joined. The B-spline runs over [0, 1] in step
|
|
527
|
+
* with s.
|
|
528
|
+
*/
|
|
529
|
+
function fitBSpline2d(evaluate, s0, s1, tolerance, options = {}) {
|
|
530
|
+
const degree = options.degree ?? 3;
|
|
531
|
+
const maxSegments = Math.max(2, options.maxSegments ?? 1024);
|
|
532
|
+
const lo = Math.min(s0, s1);
|
|
533
|
+
const hi = Math.max(s0, s1);
|
|
534
|
+
const margin = 1e-12 * (hi - lo);
|
|
535
|
+
const inner = [...new Set(options.breaks ?? [])].filter((b) => b > lo + margin && b < hi - margin).sort((a, b) => s0 <= s1 ? a - b : b - a);
|
|
536
|
+
if (!inner.length) return fitPiece(evaluate, s0, s1, tolerance, degree, options.minSegments ?? 8, maxSegments);
|
|
537
|
+
const cuts = [
|
|
538
|
+
s0,
|
|
539
|
+
...inner,
|
|
540
|
+
s1
|
|
541
|
+
];
|
|
542
|
+
return joinPieces(cuts.slice(1).map((end, i) => fitPiece(evaluate, cuts[i], end, tolerance, degree, Math.max(degree, 2), maxSegments)), cuts.map((c) => (c - s0) / (s1 - s0)));
|
|
543
|
+
}
|
|
544
|
+
function fullKnotVector(c) {
|
|
545
|
+
const U = [];
|
|
546
|
+
c.knots.forEach((k, i) => {
|
|
547
|
+
for (let m = 0; m < (c.multiplicities[i] ?? 1); m++) U.push(k);
|
|
548
|
+
});
|
|
549
|
+
return U;
|
|
550
|
+
}
|
|
551
|
+
/** The B-spline form of a Bezier; a B-spline is returned as is. */
|
|
552
|
+
function asBSpline2d(c) {
|
|
553
|
+
if (c.__bk2d === "bspline") return c;
|
|
554
|
+
const degree = c.poles.length - 1;
|
|
555
|
+
return {
|
|
556
|
+
__bk2d: "bspline",
|
|
557
|
+
poles: c.poles.map(([x, y]) => [x, y]),
|
|
558
|
+
knots: [0, 1],
|
|
559
|
+
multiplicities: [degree + 1, degree + 1],
|
|
560
|
+
degree,
|
|
561
|
+
isPeriodic: false
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
/** Insert `u` once (Boehm, The NURBS Book A5.1 with r = 1). */
|
|
565
|
+
function insertKnot(U, P, p, u) {
|
|
566
|
+
const n = P.length - 1;
|
|
567
|
+
const k = findSpan(U, p, n, u);
|
|
568
|
+
const Q = [];
|
|
569
|
+
for (let i = 0; i <= n + 1; i++) if (i <= k - p) Q.push(P[i]);
|
|
570
|
+
else if (i > k) Q.push(P[i - 1]);
|
|
571
|
+
else {
|
|
572
|
+
const alpha = (u - U[i]) / (U[i + p] - U[i]);
|
|
573
|
+
const a = P[i];
|
|
574
|
+
const b = P[i - 1];
|
|
575
|
+
Q.push([alpha * a[0] + (1 - alpha) * b[0], alpha * a[1] + (1 - alpha) * b[1]]);
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
U: [
|
|
579
|
+
...U.slice(0, k + 1),
|
|
580
|
+
u,
|
|
581
|
+
...U.slice(k + 1)
|
|
582
|
+
],
|
|
583
|
+
P: Q
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* The part of a clamped B-spline over [lo, hi] (inside its knot range, lo < hi)
|
|
588
|
+
* as a clamped B-spline of its own, exact by knot insertion.
|
|
589
|
+
*/
|
|
590
|
+
function subBSpline2d(c, lo, hi) {
|
|
591
|
+
const p = c.degree;
|
|
592
|
+
let U = fullKnotVector(c);
|
|
593
|
+
let P = c.poles.map(([x, y]) => [x, y]);
|
|
594
|
+
const first = U[p];
|
|
595
|
+
const last = U[U.length - 1 - p];
|
|
596
|
+
const a = Math.min(last, Math.max(first, lo));
|
|
597
|
+
const b = Math.min(last, Math.max(first, hi));
|
|
598
|
+
for (const u of [a, b]) {
|
|
599
|
+
if (u <= first || u >= last) continue;
|
|
600
|
+
while (U.filter((k) => k === u).length < p) ({U, P} = insertKnot(U, P, p, u));
|
|
601
|
+
}
|
|
602
|
+
const kLo = a <= first ? p : U.lastIndexOf(a);
|
|
603
|
+
const kHi = b >= last ? U.length - 1 - p : U.indexOf(b);
|
|
604
|
+
const sub = [];
|
|
605
|
+
for (let i = 0; i <= p; i++) sub.push(a);
|
|
606
|
+
sub.push(...U.slice(kLo + 1, kHi));
|
|
607
|
+
for (let i = 0; i <= p; i++) sub.push(b);
|
|
608
|
+
const { knots, multiplicities } = compressKnots(sub);
|
|
609
|
+
return {
|
|
610
|
+
__bk2d: "bspline",
|
|
611
|
+
poles: P.slice(kLo - p, kHi),
|
|
612
|
+
knots,
|
|
613
|
+
multiplicities,
|
|
614
|
+
degree: p,
|
|
615
|
+
isPeriodic: false
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Bezier poles of every non-empty knot span (The NURBS Book A5.6). A malformed
|
|
620
|
+
* knot vector yields the whole control polygon as one segment, which still
|
|
621
|
+
* bounds the curve.
|
|
622
|
+
*/
|
|
623
|
+
function bezierSegments2d(c) {
|
|
624
|
+
const poles = c.poles;
|
|
625
|
+
const p = c.degree;
|
|
626
|
+
const U = fullKnotVector(c);
|
|
627
|
+
const m = poles.length - 1 + p + 1;
|
|
628
|
+
const clone = (pt) => [pt[0], pt[1]];
|
|
629
|
+
if (p < 1 || U.length !== m + 1) return [poles.map(clone)];
|
|
630
|
+
const segments = [];
|
|
631
|
+
let a = p;
|
|
632
|
+
let b = p + 1;
|
|
633
|
+
let segPoles = [];
|
|
634
|
+
for (let i = 0; i <= p; i++) segPoles[i] = clone(poles[i]);
|
|
635
|
+
let nextPoles = [];
|
|
636
|
+
while (b < m) {
|
|
637
|
+
const i0 = b;
|
|
638
|
+
while (b < m && U[b + 1] === U[b]) b++;
|
|
639
|
+
const mult = b - i0 + 1;
|
|
640
|
+
if (mult > p && b < m) return [poles.map(clone)];
|
|
641
|
+
if (mult < p) {
|
|
642
|
+
const numer = U[b] - U[a];
|
|
643
|
+
const alphas = [];
|
|
644
|
+
for (let j = p; j > mult; j--) alphas[j - mult - 1] = numer / (U[a + j] - U[a]);
|
|
645
|
+
const r = p - mult;
|
|
646
|
+
for (let j = 1; j <= r; j++) {
|
|
647
|
+
const save = r - j;
|
|
648
|
+
const s = mult + j;
|
|
649
|
+
for (let k = p; k >= s; k--) {
|
|
650
|
+
const alpha = alphas[k - s];
|
|
651
|
+
const cur = segPoles[k];
|
|
652
|
+
const prev = segPoles[k - 1];
|
|
653
|
+
segPoles[k] = [alpha * cur[0] + (1 - alpha) * prev[0], alpha * cur[1] + (1 - alpha) * prev[1]];
|
|
654
|
+
}
|
|
655
|
+
if (b < m) nextPoles[save] = clone(segPoles[p]);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
segments.push(segPoles);
|
|
659
|
+
if (b < m) {
|
|
660
|
+
for (let i = p - mult; i <= p; i++) nextPoles[i] = clone(poles[b - p + i]);
|
|
661
|
+
segPoles = nextPoles;
|
|
662
|
+
nextPoles = [];
|
|
663
|
+
a = b;
|
|
664
|
+
b++;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return segments;
|
|
668
|
+
}
|
|
299
669
|
// src/kernel/geometry2d.ts
|
|
300
670
|
/* v8 ignore file -- excluded from coverage reports; prevents V8 instrumentation
|
|
301
671
|
overhead that causes 2D-heavy tests to timeout under coverage mode. */
|
|
672
|
+
/**
|
|
673
|
+
* Pure-TypeScript 2D geometry implementation for Kernel2DCapability.
|
|
674
|
+
*
|
|
675
|
+
* All 2D curves are represented as plain objects with a `type` discriminant
|
|
676
|
+
* and `evaluate(t)` method. No WASM boundary crossing required.
|
|
677
|
+
*
|
|
678
|
+
* ADR-0006: Inline math here (distance, rotation, normalization, dot/cross
|
|
679
|
+
* products) operates on curve struct fields, not Point2D tuples. The canonical
|
|
680
|
+
* tuple-based functions live in src/utils/vec2d.ts. The struct-field patterns
|
|
681
|
+
* are not worth converting to tuple form — the temporary array allocations
|
|
682
|
+
* would degrade both readability and performance.
|
|
683
|
+
*
|
|
684
|
+
* @module
|
|
685
|
+
*/
|
|
302
686
|
function evaluateCurve2d$1(c, t) {
|
|
303
687
|
switch (c.__bk2d) {
|
|
304
688
|
case "line": return [c.ox + c.dx * t, c.oy + c.dy * t];
|
|
@@ -398,6 +782,79 @@ function curveTypeName(c) {
|
|
|
398
782
|
case "trimmed": return "TRIMMED_" + curveTypeName(c.basis);
|
|
399
783
|
}
|
|
400
784
|
}
|
|
785
|
+
/**
|
|
786
|
+
* Collapse nested trimmed wrappers into a single trimmed curve over a
|
|
787
|
+
* non-trimmed basis (or return the curve itself when it is not trimmed).
|
|
788
|
+
* Evaluation is unchanged: both forms map t in [0, 1] to the same basis point.
|
|
789
|
+
*/
|
|
790
|
+
function flattenTrimmed(c) {
|
|
791
|
+
if (c.__bk2d !== "trimmed") return c;
|
|
792
|
+
const inner = flattenTrimmed(c.basis);
|
|
793
|
+
if (inner.__bk2d !== "trimmed") return c;
|
|
794
|
+
const span = inner.tEnd - inner.tStart;
|
|
795
|
+
return {
|
|
796
|
+
__bk2d: "trimmed",
|
|
797
|
+
basis: inner.basis,
|
|
798
|
+
tStart: inner.tStart + c.tStart * span,
|
|
799
|
+
tEnd: inner.tStart + c.tEnd * span
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
/** Basis parameter of the reversed basis that lands on the point at `u` of the original. */
|
|
803
|
+
function reversedBasisParam(c, u) {
|
|
804
|
+
switch (c.__bk2d) {
|
|
805
|
+
case "circle":
|
|
806
|
+
case "ellipse": return -u;
|
|
807
|
+
default: {
|
|
808
|
+
const { first, last } = curveBounds(c);
|
|
809
|
+
return first + last - u;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* The same geometry traversed in the opposite direction (OCCT `Geom2d_Curve::Reversed`).
|
|
815
|
+
* Trimmed curves keep an increasing trim range: the basis is reversed and the
|
|
816
|
+
* range is remapped onto it.
|
|
817
|
+
*/
|
|
818
|
+
function reverseCurve2d$1(c) {
|
|
819
|
+
switch (c.__bk2d) {
|
|
820
|
+
case "line": return {
|
|
821
|
+
...c,
|
|
822
|
+
ox: c.ox + c.dx * c.len,
|
|
823
|
+
oy: c.oy + c.dy * c.len,
|
|
824
|
+
dx: -c.dx,
|
|
825
|
+
dy: -c.dy
|
|
826
|
+
};
|
|
827
|
+
case "circle":
|
|
828
|
+
case "ellipse": return {
|
|
829
|
+
...c,
|
|
830
|
+
sense: !c.sense
|
|
831
|
+
};
|
|
832
|
+
case "bezier": return {
|
|
833
|
+
...c,
|
|
834
|
+
poles: [...c.poles].reverse()
|
|
835
|
+
};
|
|
836
|
+
case "bspline": {
|
|
837
|
+
const lo = c.knots[0] ?? 0;
|
|
838
|
+
const hi = c.knots[c.knots.length - 1] ?? 1;
|
|
839
|
+
return {
|
|
840
|
+
...c,
|
|
841
|
+
poles: [...c.poles].reverse(),
|
|
842
|
+
knots: c.knots.map((k) => lo + hi - k).reverse(),
|
|
843
|
+
multiplicities: [...c.multiplicities].reverse()
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
case "trimmed": {
|
|
847
|
+
const flat = flattenTrimmed(c);
|
|
848
|
+
if (flat.__bk2d !== "trimmed") return reverseCurve2d$1(flat);
|
|
849
|
+
return {
|
|
850
|
+
__bk2d: "trimmed",
|
|
851
|
+
basis: reverseCurve2d$1(flat.basis),
|
|
852
|
+
tStart: reversedBasisParam(flat.basis, flat.tEnd),
|
|
853
|
+
tEnd: reversedBasisParam(flat.basis, flat.tStart)
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
401
858
|
function makeLine2d$1(x1, y1, x2, y2) {
|
|
402
859
|
const dx = x2 - x1;
|
|
403
860
|
const dy = y2 - y1;
|
|
@@ -630,7 +1087,8 @@ function scaleCurve2d$1(c, factor, cx, cy) {
|
|
|
630
1087
|
cx: ncx,
|
|
631
1088
|
cy: ncy,
|
|
632
1089
|
majorRadius: c.majorRadius * Math.abs(factor),
|
|
633
|
-
minorRadius: c.minorRadius * Math.abs(factor)
|
|
1090
|
+
minorRadius: c.minorRadius * Math.abs(factor),
|
|
1091
|
+
xDirAngle: factor < 0 ? c.xDirAngle + Math.PI : c.xDirAngle
|
|
634
1092
|
};
|
|
635
1093
|
}
|
|
636
1094
|
case "bezier": return {
|
|
@@ -641,10 +1099,31 @@ function scaleCurve2d$1(c, factor, cx, cy) {
|
|
|
641
1099
|
...c,
|
|
642
1100
|
poles: c.poles.map(([x, y]) => scalePoint(x, y))
|
|
643
1101
|
};
|
|
644
|
-
case "trimmed":
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
1102
|
+
case "trimmed": {
|
|
1103
|
+
const basis = scaleCurve2d$1(c.basis, factor, cx, cy);
|
|
1104
|
+
if (basis.__bk2d === "line") {
|
|
1105
|
+
const s = Math.abs(factor);
|
|
1106
|
+
return {
|
|
1107
|
+
...c,
|
|
1108
|
+
basis,
|
|
1109
|
+
tStart: c.tStart * s,
|
|
1110
|
+
tEnd: c.tEnd * s
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
if (basis.__bk2d === "circle" && factor < 0) {
|
|
1114
|
+
const shift = basis.sense ? Math.PI : -Math.PI;
|
|
1115
|
+
return {
|
|
1116
|
+
...c,
|
|
1117
|
+
basis,
|
|
1118
|
+
tStart: c.tStart + shift,
|
|
1119
|
+
tEnd: c.tEnd + shift
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
...c,
|
|
1124
|
+
basis
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
648
1127
|
}
|
|
649
1128
|
}
|
|
650
1129
|
function mirrorAtPoint(c, cx, cy) {
|
|
@@ -723,6 +1202,328 @@ function mirrorAcrossAxis(c, ox, oy, dx, dy) {
|
|
|
723
1202
|
}
|
|
724
1203
|
}
|
|
725
1204
|
}
|
|
1205
|
+
function applyAffine(m, x, y) {
|
|
1206
|
+
return [m.a * x + m.b * y + m.tx, m.c * x + m.d * y + m.ty];
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Image of the conic C + M0 * (cos psi, sin psi), psi = sense ? t : -t.
|
|
1210
|
+
* The closed-form 2x2 SVD M = R(phi) * diag(sx, sy) * R(theta) gives the new
|
|
1211
|
+
* frame (phi), radii (sx, |sy|) and the angular shift (theta); sy < 0 means the
|
|
1212
|
+
* map reflects, which flips the sense.
|
|
1213
|
+
*/
|
|
1214
|
+
function affineConic(cx, cy, m0, sense, m) {
|
|
1215
|
+
const [ncx, ncy] = applyAffine(m, cx, cy);
|
|
1216
|
+
const m00 = m.a * m0[0] + m.b * m0[2];
|
|
1217
|
+
const m01 = m.a * m0[1] + m.b * m0[3];
|
|
1218
|
+
const m10 = m.c * m0[0] + m.d * m0[2];
|
|
1219
|
+
const m11 = m.c * m0[1] + m.d * m0[3];
|
|
1220
|
+
const e = (m00 + m11) / 2;
|
|
1221
|
+
const f = (m00 - m11) / 2;
|
|
1222
|
+
const g = (m10 + m01) / 2;
|
|
1223
|
+
const h = (m10 - m01) / 2;
|
|
1224
|
+
const q = Math.hypot(e, h);
|
|
1225
|
+
const r = Math.hypot(f, g);
|
|
1226
|
+
const sx = q + r;
|
|
1227
|
+
const sy = q - r;
|
|
1228
|
+
const a1 = Math.atan2(g, f);
|
|
1229
|
+
const a2 = Math.atan2(h, e);
|
|
1230
|
+
const theta = (a2 - a1) / 2;
|
|
1231
|
+
const phi = (a2 + a1) / 2;
|
|
1232
|
+
const newSense = sy < 0 ? !sense : sense;
|
|
1233
|
+
const shift = sense ? theta : -theta;
|
|
1234
|
+
if (Math.min(q, r) <= 1e-12 * Math.max(q, r)) {
|
|
1235
|
+
const circleShift = shift + (newSense ? phi : -phi);
|
|
1236
|
+
return {
|
|
1237
|
+
curve: {
|
|
1238
|
+
__bk2d: "circle",
|
|
1239
|
+
cx: ncx,
|
|
1240
|
+
cy: ncy,
|
|
1241
|
+
radius: sx,
|
|
1242
|
+
sense: newSense
|
|
1243
|
+
},
|
|
1244
|
+
mapParam: (u) => u + circleShift
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
return {
|
|
1248
|
+
curve: {
|
|
1249
|
+
__bk2d: "ellipse",
|
|
1250
|
+
cx: ncx,
|
|
1251
|
+
cy: ncy,
|
|
1252
|
+
majorRadius: sx,
|
|
1253
|
+
minorRadius: Math.abs(sy),
|
|
1254
|
+
xDirAngle: phi,
|
|
1255
|
+
sense: newSense
|
|
1256
|
+
},
|
|
1257
|
+
mapParam: (u) => u + shift
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
function affineBasis(c, m) {
|
|
1261
|
+
switch (c.__bk2d) {
|
|
1262
|
+
case "line": {
|
|
1263
|
+
const ldx = m.a * c.dx + m.b * c.dy;
|
|
1264
|
+
const ldy = m.c * c.dx + m.d * c.dy;
|
|
1265
|
+
const speed = Math.hypot(ldx, ldy);
|
|
1266
|
+
const [ox, oy] = applyAffine(m, c.ox, c.oy);
|
|
1267
|
+
if (speed < 1e-300) return {
|
|
1268
|
+
curve: {
|
|
1269
|
+
...c,
|
|
1270
|
+
ox,
|
|
1271
|
+
oy,
|
|
1272
|
+
len: 0
|
|
1273
|
+
},
|
|
1274
|
+
mapParam: () => 0
|
|
1275
|
+
};
|
|
1276
|
+
return {
|
|
1277
|
+
curve: {
|
|
1278
|
+
__bk2d: "line",
|
|
1279
|
+
ox,
|
|
1280
|
+
oy,
|
|
1281
|
+
dx: ldx / speed,
|
|
1282
|
+
dy: ldy / speed,
|
|
1283
|
+
len: c.len * speed
|
|
1284
|
+
},
|
|
1285
|
+
mapParam: (u) => u * speed
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
case "circle": return affineConic(c.cx, c.cy, [
|
|
1289
|
+
c.radius,
|
|
1290
|
+
0,
|
|
1291
|
+
0,
|
|
1292
|
+
c.radius
|
|
1293
|
+
], c.sense, m);
|
|
1294
|
+
case "ellipse": {
|
|
1295
|
+
const cos = Math.cos(c.xDirAngle);
|
|
1296
|
+
const sin = Math.sin(c.xDirAngle);
|
|
1297
|
+
const m0 = [
|
|
1298
|
+
c.majorRadius * cos,
|
|
1299
|
+
-c.minorRadius * sin,
|
|
1300
|
+
c.majorRadius * sin,
|
|
1301
|
+
c.minorRadius * cos
|
|
1302
|
+
];
|
|
1303
|
+
return affineConic(c.cx, c.cy, m0, c.sense, m);
|
|
1304
|
+
}
|
|
1305
|
+
case "bezier":
|
|
1306
|
+
case "bspline": return {
|
|
1307
|
+
curve: {
|
|
1308
|
+
...c,
|
|
1309
|
+
poles: c.poles.map(([x, y]) => applyAffine(m, x, y))
|
|
1310
|
+
},
|
|
1311
|
+
mapParam: (u) => u
|
|
1312
|
+
};
|
|
1313
|
+
case "trimmed": return {
|
|
1314
|
+
curve: affineCurve2d(c, m),
|
|
1315
|
+
mapParam: (u) => u
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Exact image of a curve under an affine map (OCCT `Geom2d_Curve::Transformed`
|
|
1321
|
+
* with a `gp_GTrsf2d`): lines stay lines, circles and ellipses become ellipses
|
|
1322
|
+
* (or circles), and pole-based curves map their poles.
|
|
1323
|
+
*/
|
|
1324
|
+
function affineCurve2d(c, m) {
|
|
1325
|
+
const flat = flattenTrimmed(c);
|
|
1326
|
+
if (flat.__bk2d !== "trimmed") return affineBasis(flat, m).curve;
|
|
1327
|
+
const { curve, mapParam } = affineBasis(flat.basis, m);
|
|
1328
|
+
return {
|
|
1329
|
+
__bk2d: "trimmed",
|
|
1330
|
+
basis: curve,
|
|
1331
|
+
tStart: mapParam(flat.tStart),
|
|
1332
|
+
tEnd: mapParam(flat.tEnd)
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
function unitTangent(c, t, first, last) {
|
|
1336
|
+
let [tx, ty] = tangentCurve2d(c, t);
|
|
1337
|
+
let len = Math.hypot(tx, ty);
|
|
1338
|
+
for (let k = 1; len < 1e-12 && k <= 6; k++) {
|
|
1339
|
+
const h = (last - first) * 10 ** (-9 + k);
|
|
1340
|
+
[tx, ty] = tangentCurve2d(c, t + h <= Math.max(first, last) ? t + h : t - h);
|
|
1341
|
+
len = Math.hypot(tx, ty);
|
|
1342
|
+
}
|
|
1343
|
+
return len < 1e-300 ? [1, 0] : [tx / len, ty / len];
|
|
1344
|
+
}
|
|
1345
|
+
function curveExtent(c) {
|
|
1346
|
+
const box = createBBox2d();
|
|
1347
|
+
addCurveToBBox(box, c, 0);
|
|
1348
|
+
return Math.hypot(box.xMax - box.xMin, box.yMax - box.yMin);
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Parameters of `c`, in its own parameter range, at the interior knots of its
|
|
1352
|
+
* B-spline basis: where a fit of the curve (or of its offset) must sample.
|
|
1353
|
+
*/
|
|
1354
|
+
function curveKnotParams(c) {
|
|
1355
|
+
const flat = flattenTrimmed(c);
|
|
1356
|
+
const basis = flat.__bk2d === "trimmed" ? flat.basis : flat;
|
|
1357
|
+
if (basis.__bk2d !== "bspline") return [];
|
|
1358
|
+
const inner = basis.knots.slice(1, -1);
|
|
1359
|
+
if (flat.__bk2d !== "trimmed") return inner;
|
|
1360
|
+
const { tStart, tEnd } = flat;
|
|
1361
|
+
return inner.filter((k) => k > Math.min(tStart, tEnd) && k < Math.max(tStart, tEnd)).map((k) => (k - tStart) / (tEnd - tStart));
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Offset `distance` to the right of the direction of travel (OCCT
|
|
1365
|
+
* `Geom2d_OffsetCurve`: a counter-clockwise circle grows). Lines and circles
|
|
1366
|
+
* are offset exactly; other curves are fitted within `tolerance` (by default
|
|
1367
|
+
* relative to the curve's size).
|
|
1368
|
+
*/
|
|
1369
|
+
function offsetCurve2d$1(c, distance, tolerance) {
|
|
1370
|
+
const flat = flattenTrimmed(c);
|
|
1371
|
+
const trimmed = flat.__bk2d === "trimmed" ? flat : null;
|
|
1372
|
+
const basis = trimmed ? trimmed.basis : flat;
|
|
1373
|
+
const d = trimmed && trimmed.tEnd < trimmed.tStart ? -distance : distance;
|
|
1374
|
+
if (basis.__bk2d === "line") {
|
|
1375
|
+
const shifted = {
|
|
1376
|
+
...basis,
|
|
1377
|
+
ox: basis.ox + basis.dy * d,
|
|
1378
|
+
oy: basis.oy - basis.dx * d
|
|
1379
|
+
};
|
|
1380
|
+
return trimmed ? {
|
|
1381
|
+
...trimmed,
|
|
1382
|
+
basis: shifted
|
|
1383
|
+
} : shifted;
|
|
1384
|
+
}
|
|
1385
|
+
if (basis.__bk2d === "circle") {
|
|
1386
|
+
const radius = basis.radius + (basis.sense ? d : -d);
|
|
1387
|
+
if (Math.abs(radius) <= 1e-12 * Math.max(1, basis.radius)) throw new Error("offsetCurve2d: the offset collapses the circle to its center");
|
|
1388
|
+
const circle = {
|
|
1389
|
+
...basis,
|
|
1390
|
+
radius: Math.abs(radius)
|
|
1391
|
+
};
|
|
1392
|
+
if (!trimmed) return circle;
|
|
1393
|
+
if (radius > 0) return {
|
|
1394
|
+
...trimmed,
|
|
1395
|
+
basis: circle
|
|
1396
|
+
};
|
|
1397
|
+
const shift = basis.sense ? Math.PI : -Math.PI;
|
|
1398
|
+
return {
|
|
1399
|
+
...trimmed,
|
|
1400
|
+
basis: circle,
|
|
1401
|
+
tStart: trimmed.tStart + shift,
|
|
1402
|
+
tEnd: trimmed.tEnd + shift
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
const { first, last } = curveBounds(flat);
|
|
1406
|
+
return fitBSpline2d((t) => {
|
|
1407
|
+
const [px, py] = evaluateCurve2d$1(flat, t);
|
|
1408
|
+
const [ux, uy] = unitTangent(flat, t, first, last);
|
|
1409
|
+
return [px + uy * distance, py - ux * distance];
|
|
1410
|
+
}, first, last, tolerance ?? 1e-9 * Math.max(1, curveExtent(flat) + Math.abs(distance)), { breaks: curveKnotParams(flat) });
|
|
1411
|
+
}
|
|
1412
|
+
function sampleCurve(c, from, to, n) {
|
|
1413
|
+
const pts = [];
|
|
1414
|
+
for (let i = 0; i <= n; i++) pts.push(evaluateCurve2d$1(c, from + (to - from) * i / n));
|
|
1415
|
+
return pts;
|
|
1416
|
+
}
|
|
1417
|
+
/** Second derivative by central differences, kept inside [lo, hi] for clamped curves. */
|
|
1418
|
+
function secondDerivative(c, t, lo, hi) {
|
|
1419
|
+
const h = 1e-4 * Math.max(hi - lo, 1e-9);
|
|
1420
|
+
const mid = Math.min(hi - h, Math.max(lo + h, t));
|
|
1421
|
+
const [ax, ay] = evaluateCurve2d$1(c, mid - h);
|
|
1422
|
+
const [bx, by] = evaluateCurve2d$1(c, mid);
|
|
1423
|
+
const [cx, cy] = evaluateCurve2d$1(c, mid + h);
|
|
1424
|
+
return [(ax - 2 * bx + cx) / (h * h), (ay - 2 * by + cy) / (h * h)];
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Newton step for f(s, t) = |c1(s) - c2(t)|^2 / 2. The closest pair of two
|
|
1428
|
+
* disjoint curves has parallel tangents, so the curvature terms of the Hessian
|
|
1429
|
+
* are what make it converge; where the Hessian is not positive definite the
|
|
1430
|
+
* step falls back to Gauss-Newton, then to projecting c1(s) onto c2.
|
|
1431
|
+
*/
|
|
1432
|
+
function closestPairStep(c1, c2, s, t, box1, box2) {
|
|
1433
|
+
const [px, py] = evaluateCurve2d$1(c1, s);
|
|
1434
|
+
const [qx, qy] = evaluateCurve2d$1(c2, t);
|
|
1435
|
+
const dx = px - qx;
|
|
1436
|
+
const dy = py - qy;
|
|
1437
|
+
const t1 = tangentCurve2d(c1, s);
|
|
1438
|
+
const t2 = tangentCurve2d(c2, t);
|
|
1439
|
+
const g1 = t1[0] * dx + t1[1] * dy;
|
|
1440
|
+
const g2 = -(t2[0] * dx + t2[1] * dy);
|
|
1441
|
+
const a11 = t1[0] * t1[0] + t1[1] * t1[1];
|
|
1442
|
+
const a22 = t2[0] * t2[0] + t2[1] * t2[1];
|
|
1443
|
+
const a12 = -(t1[0] * t2[0] + t1[1] * t2[1]);
|
|
1444
|
+
const k1 = secondDerivative(c1, s, box1[0], box1[1]);
|
|
1445
|
+
const k2 = secondDerivative(c2, t, box2[0], box2[1]);
|
|
1446
|
+
const h11 = a11 + k1[0] * dx + k1[1] * dy;
|
|
1447
|
+
const h22 = a22 - (k2[0] * dx + k2[1] * dy);
|
|
1448
|
+
const detH = h11 * h22 - a12 * a12;
|
|
1449
|
+
if (h11 > 0 && detH > 1e-12 * Math.abs(h11 * h22)) return [(-g1 * h22 + g2 * a12) / detH, (-g2 * h11 + g1 * a12) / detH];
|
|
1450
|
+
const detGN = a11 * a22 - a12 * a12;
|
|
1451
|
+
if (detGN > 1e-12 * a11 * a22) return [(-g1 * a22 + g2 * a12) / detGN, (-g2 * a11 + g1 * a12) / detGN];
|
|
1452
|
+
if (a22 > 0) return [0, -g2 / a22];
|
|
1453
|
+
return a11 > 0 ? [-g1 / a11, 0] : [0, 0];
|
|
1454
|
+
}
|
|
1455
|
+
/** Refine a seed pair, halving each step until it lowers the distance; returns the squared distance. */
|
|
1456
|
+
function refineClosestPair(c1, c2, seed, box1, box2) {
|
|
1457
|
+
const clamp = (v, [lo, hi]) => Math.min(hi, Math.max(lo, v));
|
|
1458
|
+
const distSq = (a, b) => {
|
|
1459
|
+
const [px, py] = evaluateCurve2d$1(c1, a);
|
|
1460
|
+
const [qx, qy] = evaluateCurve2d$1(c2, b);
|
|
1461
|
+
return (px - qx) ** 2 + (py - qy) ** 2;
|
|
1462
|
+
};
|
|
1463
|
+
let [s, t] = seed;
|
|
1464
|
+
let f = distSq(s, t);
|
|
1465
|
+
for (let iter = 0; iter < 60 && f > 0; iter++) {
|
|
1466
|
+
const [ds, dt] = closestPairStep(c1, c2, s, t, box1, box2);
|
|
1467
|
+
let lambda = 1;
|
|
1468
|
+
let ns = s;
|
|
1469
|
+
let nt = t;
|
|
1470
|
+
let nf = f;
|
|
1471
|
+
for (; lambda > 1e-6 && nf >= f; lambda /= 2) {
|
|
1472
|
+
ns = clamp(s + lambda * ds, box1);
|
|
1473
|
+
nt = clamp(t + lambda * dt, box2);
|
|
1474
|
+
nf = distSq(ns, nt);
|
|
1475
|
+
}
|
|
1476
|
+
if (nf >= f || ns === s && nt === t) break;
|
|
1477
|
+
[s, t, f] = [
|
|
1478
|
+
ns,
|
|
1479
|
+
nt,
|
|
1480
|
+
nf
|
|
1481
|
+
];
|
|
1482
|
+
}
|
|
1483
|
+
return f;
|
|
1484
|
+
}
|
|
1485
|
+
/** Whether grid cell (i, j) is no larger than any of its eight neighbours. */
|
|
1486
|
+
function isGridMinimum(at, i, j, n) {
|
|
1487
|
+
const d = at(i, j);
|
|
1488
|
+
for (let ni = Math.max(0, i - 1); ni <= Math.min(n, i + 1); ni++) for (let nj = Math.max(0, j - 1); nj <= Math.min(n, j + 1); nj++) if (at(ni, nj) < d) return false;
|
|
1489
|
+
return true;
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Minimum distance between c1 over [from1, to1] and c2 over [from2, to2]
|
|
1493
|
+
* (OCCT `Geom2dAPI_ExtremaCurveCurve`): every local minimum of a sampled
|
|
1494
|
+
* distance grid seeds a Gauss-Newton refinement.
|
|
1495
|
+
*/
|
|
1496
|
+
function distanceBetweenCurves2d$1(c1, c2, from1, to1, from2, to2) {
|
|
1497
|
+
const n = 32;
|
|
1498
|
+
const p1 = sampleCurve(c1, from1, to1, n);
|
|
1499
|
+
const p2 = sampleCurve(c2, from2, to2, n);
|
|
1500
|
+
const grid = /* @__PURE__ */ new Float64Array(1089);
|
|
1501
|
+
for (let i = 0; i <= n; i++) for (let j = 0; j <= n; j++) {
|
|
1502
|
+
const a = p1[i];
|
|
1503
|
+
const b = p2[j];
|
|
1504
|
+
grid[i * 33 + j] = (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2;
|
|
1505
|
+
}
|
|
1506
|
+
const at = (i, j) => grid[i * 33 + j] ?? Infinity;
|
|
1507
|
+
const seeds = [];
|
|
1508
|
+
let best = Infinity;
|
|
1509
|
+
for (let i = 0; i <= n; i++) for (let j = 0; j <= n; j++) {
|
|
1510
|
+
const d = at(i, j);
|
|
1511
|
+
best = Math.min(best, d);
|
|
1512
|
+
if (isGridMinimum(at, i, j, n)) seeds.push({
|
|
1513
|
+
i,
|
|
1514
|
+
j,
|
|
1515
|
+
d
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
seeds.sort((x, y) => x.d - y.d);
|
|
1519
|
+
const box1 = [Math.min(from1, to1), Math.max(from1, to1)];
|
|
1520
|
+
const box2 = [Math.min(from2, to2), Math.max(from2, to2)];
|
|
1521
|
+
for (const { i, j } of seeds.slice(0, 8)) {
|
|
1522
|
+
const seed = [from1 + (to1 - from1) * i / n, from2 + (to2 - from2) * j / n];
|
|
1523
|
+
best = Math.min(best, refineClosestPair(c1, c2, seed, box1, box2));
|
|
1524
|
+
}
|
|
1525
|
+
return Math.sqrt(best);
|
|
1526
|
+
}
|
|
726
1527
|
/**
|
|
727
1528
|
* Compute intersection points (and overlapping segments) between two 2D curves.
|
|
728
1529
|
* Handles analytic cases (line-line, line-circle, circle-circle) and falls back
|
|
@@ -740,10 +1541,7 @@ function intersectCurves2dFn(c1, c2, tolerance) {
|
|
|
740
1541
|
points: intersectLineCircle(c2, b2, c1, b1, tolerance),
|
|
741
1542
|
segments: []
|
|
742
1543
|
};
|
|
743
|
-
if (b1.__bk2d === "circle" && b2.__bk2d === "circle") return
|
|
744
|
-
points: intersectCircleCircle(c1, b1, c2, b2, tolerance),
|
|
745
|
-
segments: []
|
|
746
|
-
};
|
|
1544
|
+
if (b1.__bk2d === "circle" && b2.__bk2d === "circle") return intersectCircleCircle(c1, b1, c2, b2, tolerance);
|
|
747
1545
|
return numericalIntersect(c1, c2, tolerance, c1 === c2);
|
|
748
1546
|
}
|
|
749
1547
|
/** Unwrap trimmed wrappers to get the basis curve type. */
|
|
@@ -752,9 +1550,32 @@ function unwrapCurve(c) {
|
|
|
752
1550
|
while (cur.__bk2d === "trimmed") cur = cur.basis;
|
|
753
1551
|
return cur;
|
|
754
1552
|
}
|
|
755
|
-
/**
|
|
1553
|
+
/**
|
|
1554
|
+
* The interval a curve covers on its innermost basis's parameter. A trimmed wrapper's own
|
|
1555
|
+
* parameter runs 0..1, but the analytic solvers work on the unwrapped basis, so their
|
|
1556
|
+
* parameters must be bounded by this interval rather than by `curveBounds(c)`.
|
|
1557
|
+
*/
|
|
1558
|
+
function basisInterval(c) {
|
|
1559
|
+
if (c.__bk2d !== "trimmed") return curveBounds(c);
|
|
1560
|
+
const a = basisParam(c.basis, c.tStart);
|
|
1561
|
+
const b = basisParam(c.basis, c.tEnd);
|
|
1562
|
+
return {
|
|
1563
|
+
first: Math.min(a, b),
|
|
1564
|
+
last: Math.max(a, b)
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
function basisParam(c, t) {
|
|
1568
|
+
let cur = c;
|
|
1569
|
+
let s = t;
|
|
1570
|
+
while (cur.__bk2d === "trimmed") {
|
|
1571
|
+
s = cur.tStart + s * (cur.tEnd - cur.tStart);
|
|
1572
|
+
cur = cur.basis;
|
|
1573
|
+
}
|
|
1574
|
+
return s;
|
|
1575
|
+
}
|
|
1576
|
+
/** Check if basis parameter t lies within the part of the basis that c covers. */
|
|
756
1577
|
function inDomain(c, t, tol) {
|
|
757
|
-
const b =
|
|
1578
|
+
const b = basisInterval(c);
|
|
758
1579
|
return t >= b.first - tol && t <= b.last + tol;
|
|
759
1580
|
}
|
|
760
1581
|
/** Find the parameter on curve c closest to point (px, py), searching near tGuess. */
|
|
@@ -830,7 +1651,7 @@ function intersectLineLine(c1, l1, c2, l2, tol) {
|
|
|
830
1651
|
points: [],
|
|
831
1652
|
segments: []
|
|
832
1653
|
};
|
|
833
|
-
const b1 =
|
|
1654
|
+
const b1 = basisInterval(c1);
|
|
834
1655
|
const b2 = curveBounds(c2);
|
|
835
1656
|
const p2s = evaluateCurve2d$1(c2, b2.first);
|
|
836
1657
|
const p2e = evaluateCurve2d$1(c2, b2.last);
|
|
@@ -871,42 +1692,83 @@ function intersectLineCircle(cLine, line, cCirc, circ, tol) {
|
|
|
871
1692
|
}
|
|
872
1693
|
return results;
|
|
873
1694
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
1695
|
+
/**
|
|
1696
|
+
* The counter-clockwise angular range [start, start + sweep] a circular curve covers,
|
|
1697
|
+
* and whether the curve itself runs counter-clockwise through it.
|
|
1698
|
+
*/
|
|
1699
|
+
function arcSpan(c, circ) {
|
|
1700
|
+
const range = basisInterval(c);
|
|
1701
|
+
const own = curveBounds(c);
|
|
1702
|
+
const forward = basisParam(c, own.last) >= basisParam(c, own.first);
|
|
1703
|
+
return {
|
|
1704
|
+
start: circ.sense ? range.first : -range.last,
|
|
1705
|
+
sweep: Math.min(range.last - range.first, 2 * Math.PI),
|
|
1706
|
+
ccw: circ.sense === forward
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
/** The arc of `circ` from angle `from` counter-clockwise to `to`, traversed as `ccw` says. */
|
|
1710
|
+
function arcBetween(circ, from, to, ccw) {
|
|
1711
|
+
const basis = makeCircle2d$1(circ.cx, circ.cy, circ.radius, ccw);
|
|
1712
|
+
return ccw ? {
|
|
1713
|
+
__bk2d: "trimmed",
|
|
1714
|
+
basis,
|
|
1715
|
+
tStart: from,
|
|
1716
|
+
tEnd: to
|
|
1717
|
+
} : {
|
|
1718
|
+
__bk2d: "trimmed",
|
|
1719
|
+
basis,
|
|
1720
|
+
tStart: -to,
|
|
1721
|
+
tEnd: -from
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* Overlap of two arcs on the same circle: the shared stretches as segments oriented
|
|
1726
|
+
* along c1, and the places where the arcs only touch end to end as points.
|
|
1727
|
+
*/
|
|
1728
|
+
function intersectCoincidentArcs(c1, circ1, c2, circ2, tol) {
|
|
1729
|
+
const turn = 2 * Math.PI;
|
|
1730
|
+
const a = arcSpan(c1, circ1);
|
|
1731
|
+
const b = arcSpan(c2, circ2);
|
|
1732
|
+
const angleTol = tol / circ1.radius;
|
|
1733
|
+
const isFull = (sweep) => turn - sweep <= angleTol;
|
|
1734
|
+
const d = ((b.start - a.start) % turn + turn) % turn;
|
|
1735
|
+
let windows;
|
|
1736
|
+
if (isFull(b.sweep)) windows = [[0, a.sweep]];
|
|
1737
|
+
else if (isFull(a.sweep)) windows = [[d, d + b.sweep]];
|
|
1738
|
+
else windows = [[Math.max(0, d), Math.min(a.sweep, d + b.sweep)], [Math.max(0, d - turn), Math.min(a.sweep, d - turn + b.sweep)]];
|
|
1739
|
+
const points = [];
|
|
1740
|
+
const segments = [];
|
|
1741
|
+
for (const [u, v] of windows) if (v - u > angleTol) segments.push(arcBetween(circ1, a.start + u, a.start + v, a.ccw));
|
|
1742
|
+
else if (v - u > -angleTol) {
|
|
1743
|
+
const angle = a.start + (u + v) / 2;
|
|
1744
|
+
points.push([circ1.cx + circ1.radius * Math.cos(angle), circ1.cy + circ1.radius * Math.sin(angle)]);
|
|
895
1745
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
1746
|
+
return {
|
|
1747
|
+
points,
|
|
1748
|
+
segments
|
|
1749
|
+
};
|
|
899
1750
|
}
|
|
900
1751
|
function intersectCircleCircle(c1, circ1, c2, circ2, tol) {
|
|
901
1752
|
const dx = circ2.cx - circ1.cx;
|
|
902
1753
|
const dy = circ2.cy - circ1.cy;
|
|
903
1754
|
const d = Math.sqrt(dx * dx + dy * dy);
|
|
1755
|
+
if (d <= tol) {
|
|
1756
|
+
if (Math.abs(circ1.radius - circ2.radius) > tol) return {
|
|
1757
|
+
points: [],
|
|
1758
|
+
segments: []
|
|
1759
|
+
};
|
|
1760
|
+
return intersectCoincidentArcs(c1, circ1, c2, circ2, tol);
|
|
1761
|
+
}
|
|
1762
|
+
return {
|
|
1763
|
+
points: intersectCrossingCircles(c1, circ1, c2, circ2, tol, d),
|
|
1764
|
+
segments: []
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
function intersectCrossingCircles(c1, circ1, c2, circ2, tol, d) {
|
|
1768
|
+
const dx = circ2.cx - circ1.cx;
|
|
1769
|
+
const dy = circ2.cy - circ1.cy;
|
|
904
1770
|
if (d > circ1.radius + circ2.radius + tol) return [];
|
|
905
1771
|
if (d < Math.abs(circ1.radius - circ2.radius) - tol) return [];
|
|
906
|
-
if (d < 1e-14) {
|
|
907
|
-
if (Math.abs(circ1.radius - circ2.radius) > tol) return [];
|
|
908
|
-
return intersectConcentricArcs(c1, c2, tol);
|
|
909
|
-
}
|
|
910
1772
|
const a = (circ1.radius * circ1.radius - circ2.radius * circ2.radius + d * d) / (2 * d);
|
|
911
1773
|
const h2 = circ1.radius * circ1.radius - a * a;
|
|
912
1774
|
const h = Math.sqrt(Math.max(0, h2));
|
|
@@ -1046,61 +1908,136 @@ function createBBox2d() {
|
|
|
1046
1908
|
yMax: -Infinity
|
|
1047
1909
|
};
|
|
1048
1910
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1911
|
+
/** The first parameter congruent to `t` (mod 2*pi) inside [lo, hi], if any. */
|
|
1912
|
+
function periodicParamInRange(t, lo, hi) {
|
|
1913
|
+
const period = 2 * Math.PI;
|
|
1914
|
+
const tt = t + period * Math.ceil((lo - t) / period - 1e-12);
|
|
1915
|
+
return tt <= hi + 1e-12 ? tt : null;
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Basis parameters where a conic reaches its axis-aligned extremes. For
|
|
1919
|
+
* C + a*cos(psi)*U + b*sin(psi)*V (U at angle phi), dx/dpsi = 0 at
|
|
1920
|
+
* tan(psi) = -b*sin(phi) / (a*cos(phi)) and dy/dpsi = 0 at
|
|
1921
|
+
* tan(psi) = b*cos(phi) / (a*sin(phi)), each plus pi.
|
|
1922
|
+
*/
|
|
1923
|
+
function conicExtremeParams(c) {
|
|
1924
|
+
const [a, b, phi] = c.__bk2d === "circle" ? [
|
|
1925
|
+
c.radius,
|
|
1926
|
+
c.radius,
|
|
1927
|
+
0
|
|
1928
|
+
] : [
|
|
1929
|
+
c.majorRadius,
|
|
1930
|
+
c.minorRadius,
|
|
1931
|
+
c.xDirAngle
|
|
1932
|
+
];
|
|
1933
|
+
const psiX = Math.atan2(-b * Math.sin(phi), a * Math.cos(phi));
|
|
1934
|
+
const psiY = Math.atan2(b * Math.cos(phi), a * Math.sin(phi));
|
|
1935
|
+
return [
|
|
1936
|
+
psiX,
|
|
1937
|
+
psiX + Math.PI,
|
|
1938
|
+
psiY,
|
|
1939
|
+
psiY + Math.PI
|
|
1940
|
+
].map((psi) => c.sense ? psi : -psi);
|
|
1941
|
+
}
|
|
1942
|
+
function splitBezier1d(vals) {
|
|
1943
|
+
const work = [...vals];
|
|
1944
|
+
const n = work.length;
|
|
1945
|
+
const left = [];
|
|
1946
|
+
const right = [];
|
|
1947
|
+
left.push(work[0]);
|
|
1948
|
+
right.push(work[n - 1]);
|
|
1949
|
+
for (let r = 1; r < n; r++) {
|
|
1950
|
+
for (let i = 0; i < n - r; i++) work[i] = (work[i] + work[i + 1]) / 2;
|
|
1951
|
+
left.push(work[0]);
|
|
1952
|
+
right.push(work[n - 1 - r]);
|
|
1953
|
+
}
|
|
1954
|
+
return [left, right.reverse()];
|
|
1955
|
+
}
|
|
1956
|
+
/**
|
|
1957
|
+
* Widen `range` to the values a 1D Bezier takes. Its coefficients bound it
|
|
1958
|
+
* (convex hull), so a piece whose coefficients stay inside the range is done;
|
|
1959
|
+
* otherwise the piece is halved, adding the curve value at the split.
|
|
1960
|
+
*/
|
|
1961
|
+
function widenToBezier(vals, range, depth = 0) {
|
|
1962
|
+
const lo = Math.min(...vals);
|
|
1963
|
+
const hi = Math.max(...vals);
|
|
1964
|
+
const tol = 1e-12 * (1 + Math.abs(range.min) + Math.abs(range.max));
|
|
1965
|
+
if (lo >= range.min - tol && hi <= range.max + tol) return;
|
|
1966
|
+
if (depth >= 60) {
|
|
1967
|
+
range.min = Math.min(range.min, lo);
|
|
1968
|
+
range.max = Math.max(range.max, hi);
|
|
1092
1969
|
return;
|
|
1093
1970
|
}
|
|
1971
|
+
const [left, right] = splitBezier1d(vals);
|
|
1972
|
+
const mid = right[0] ?? lo;
|
|
1973
|
+
range.min = Math.min(range.min, mid);
|
|
1974
|
+
range.max = Math.max(range.max, mid);
|
|
1975
|
+
widenToBezier(left, range, depth + 1);
|
|
1976
|
+
widenToBezier(right, range, depth + 1);
|
|
1977
|
+
}
|
|
1978
|
+
/** Add the exact extent of a Bezier or B-spline over [lo, hi], span by span. */
|
|
1979
|
+
function addPoleCurveExtent(bbox, c, lo, hi) {
|
|
1980
|
+
const bs = asBSpline2d(c);
|
|
1981
|
+
const first = bs.knots[0] ?? 0;
|
|
1982
|
+
const last = bs.knots[bs.knots.length - 1] ?? 1;
|
|
1983
|
+
const a = Math.max(first, lo);
|
|
1984
|
+
const b = Math.min(last, hi);
|
|
1985
|
+
if (b <= a) return;
|
|
1986
|
+
const piece = a > first || b < last ? subBSpline2d(bs, a, b) : bs;
|
|
1987
|
+
const x = {
|
|
1988
|
+
min: bbox.xMin,
|
|
1989
|
+
max: bbox.xMax
|
|
1990
|
+
};
|
|
1991
|
+
const y = {
|
|
1992
|
+
min: bbox.yMin,
|
|
1993
|
+
max: bbox.yMax
|
|
1994
|
+
};
|
|
1995
|
+
for (const seg of bezierSegments2d(piece)) for (const [range, axis] of [[x, 0], [y, 1]]) {
|
|
1996
|
+
const vals = seg.map((pt) => pt[axis]);
|
|
1997
|
+
const ends = [vals[0] ?? range.min, vals[vals.length - 1] ?? range.min];
|
|
1998
|
+
range.min = Math.min(range.min, ...ends);
|
|
1999
|
+
range.max = Math.max(range.max, ...ends);
|
|
2000
|
+
widenToBezier(vals, range);
|
|
2001
|
+
}
|
|
2002
|
+
bbox.xMin = x.min;
|
|
2003
|
+
bbox.xMax = x.max;
|
|
2004
|
+
bbox.yMin = y.min;
|
|
2005
|
+
bbox.yMax = y.max;
|
|
2006
|
+
}
|
|
2007
|
+
function addCurveToBBox(bbox, c, _tol) {
|
|
1094
2008
|
const bounds = curveBounds(c);
|
|
1095
2009
|
if (!isFinite(bounds.first) || !isFinite(bounds.last)) return;
|
|
1096
|
-
const
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
2010
|
+
const addPoint = ([x, y]) => {
|
|
2011
|
+
bbox.xMin = Math.min(bbox.xMin, x);
|
|
2012
|
+
bbox.yMin = Math.min(bbox.yMin, y);
|
|
2013
|
+
bbox.xMax = Math.max(bbox.xMax, x);
|
|
2014
|
+
bbox.yMax = Math.max(bbox.yMax, y);
|
|
2015
|
+
};
|
|
2016
|
+
addPoint(evaluateCurve2d$1(c, bounds.first));
|
|
2017
|
+
addPoint(evaluateCurve2d$1(c, bounds.last));
|
|
2018
|
+
const flat = flattenTrimmed(c);
|
|
2019
|
+
const basis = flat.__bk2d === "trimmed" ? flat.basis : flat;
|
|
2020
|
+
const basisBounds = curveBounds(basis);
|
|
2021
|
+
const [from, to] = flat.__bk2d === "trimmed" ? [flat.tStart, flat.tEnd] : [basisBounds.first, basisBounds.last];
|
|
2022
|
+
const lo = Math.min(from, to);
|
|
2023
|
+
const hi = Math.max(from, to);
|
|
2024
|
+
const add = (t) => {
|
|
2025
|
+
addPoint(evaluateCurve2d$1(basis, t));
|
|
2026
|
+
};
|
|
2027
|
+
switch (basis.__bk2d) {
|
|
2028
|
+
case "line": return;
|
|
2029
|
+
case "circle":
|
|
2030
|
+
case "ellipse":
|
|
2031
|
+
for (const t of conicExtremeParams(basis)) {
|
|
2032
|
+
const tt = periodicParamInRange(t, lo, hi);
|
|
2033
|
+
if (tt !== null) add(tt);
|
|
2034
|
+
}
|
|
2035
|
+
return;
|
|
2036
|
+
case "bezier":
|
|
2037
|
+
case "bspline":
|
|
2038
|
+
addPoleCurveExtent(bbox, basis, lo, hi);
|
|
2039
|
+
return;
|
|
2040
|
+
case "trimmed": return;
|
|
1104
2041
|
}
|
|
1105
2042
|
}
|
|
1106
2043
|
function evaluateBezier(poles, t) {
|
|
@@ -4255,26 +5192,13 @@ function makeBezier2d(points) {
|
|
|
4255
5192
|
return c2dWrap(makeBezier2d$1(points));
|
|
4256
5193
|
}
|
|
4257
5194
|
function makeBSpline2d(points, options) {
|
|
4258
|
-
const
|
|
4259
|
-
const
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
knots.push(i / (nInternalKnots + 1));
|
|
4266
|
-
mults.push(1);
|
|
4267
|
-
}
|
|
4268
|
-
knots.push(1);
|
|
4269
|
-
mults.push(degree + 1);
|
|
4270
|
-
return c2dWrap({
|
|
4271
|
-
__bk2d: "bspline",
|
|
4272
|
-
poles,
|
|
4273
|
-
knots,
|
|
4274
|
-
multiplicities: mults,
|
|
4275
|
-
degree,
|
|
4276
|
-
isPeriodic: false
|
|
4277
|
-
});
|
|
5195
|
+
const pts = [];
|
|
5196
|
+
for (const p of points) {
|
|
5197
|
+
const prev = pts[pts.length - 1];
|
|
5198
|
+
if (!prev || Math.hypot(p[0] - prev[0], p[1] - prev[1]) > 1e-12) pts.push([p[0], p[1]]);
|
|
5199
|
+
}
|
|
5200
|
+
if (pts.length < 2) throw new Error("occt-wasm: makeBSpline2d needs at least 2 distinct points");
|
|
5201
|
+
return c2dWrap(interpolateBSpline2d(pts, options?.degMax ?? 3));
|
|
4278
5202
|
}
|
|
4279
5203
|
function evaluateCurve2d(curve, param) {
|
|
4280
5204
|
return evaluateCurve2d$1(c2d(curve), param);
|
|
@@ -4296,26 +5220,20 @@ function getCurve2dType(curve) {
|
|
|
4296
5220
|
function trimCurve2d(curve, start, end) {
|
|
4297
5221
|
return c2dWrap({
|
|
4298
5222
|
__bk2d: "trimmed",
|
|
4299
|
-
basis: c2d(curve),
|
|
5223
|
+
basis: structuredClone(c2d(curve)),
|
|
4300
5224
|
tStart: start,
|
|
4301
5225
|
tEnd: end
|
|
4302
5226
|
});
|
|
4303
5227
|
}
|
|
5228
|
+
function reverseCurve2d(curve) {
|
|
5229
|
+
const c = c2d(curve);
|
|
5230
|
+
Object.assign(c, reverseCurve2d$1(c));
|
|
5231
|
+
}
|
|
4304
5232
|
function copyCurve2d(curve) {
|
|
4305
5233
|
return c2dWrap(JSON.parse(JSON.stringify(c2d(curve))));
|
|
4306
5234
|
}
|
|
4307
5235
|
function offsetCurve2d(curve, offset) {
|
|
4308
|
-
|
|
4309
|
-
const bounds = curveBounds(c);
|
|
4310
|
-
const pts = [];
|
|
4311
|
-
for (let i = 0; i <= 20; i++) {
|
|
4312
|
-
const t = bounds.first + (bounds.last - bounds.first) * i / 20;
|
|
4313
|
-
const [px, py] = evaluateCurve2d$1(c, t);
|
|
4314
|
-
const [tx, ty] = tangentCurve2d(c, t);
|
|
4315
|
-
const len = Math.sqrt(tx * tx + ty * ty) || 1;
|
|
4316
|
-
pts.push([px - ty / len * offset, py + tx / len * offset]);
|
|
4317
|
-
}
|
|
4318
|
-
return c2dWrap(makeBezier2d$1(pts.length <= 25 ? pts : pts.filter((_, i) => i % 2 === 0 || i === pts.length - 1)));
|
|
5236
|
+
return c2dWrap(offsetCurve2d$1(c2d(curve), offset));
|
|
4319
5237
|
}
|
|
4320
5238
|
function translateCurve2d(curve, dx, dy) {
|
|
4321
5239
|
return c2dWrap(translateCurve2d$1(c2d(curve), dx, dy));
|
|
@@ -4335,8 +5253,7 @@ function mirrorCurve2dAcrossAxis(curve, originX, originY, dirX, dirY) {
|
|
|
4335
5253
|
/**
|
|
4336
5254
|
* Directional affinity matching OCCT `gp_GTrsf2d::SetAffinity`: scales the
|
|
4337
5255
|
* component perpendicular to the axis `(dx, dy)` through `(ox, oy)` by `ratio`,
|
|
4338
|
-
* leaving the parallel component fixed.
|
|
4339
|
-
* Bezier, since occt-wasm has no native Geom2d transform.
|
|
5256
|
+
* leaving the parallel component fixed.
|
|
4340
5257
|
*/
|
|
4341
5258
|
function affinityCurve2d(curve, ox, oy, dx, dy, ratio) {
|
|
4342
5259
|
const len = Math.sqrt(dx * dx + dy * dy);
|
|
@@ -4346,15 +5263,14 @@ function affinityCurve2d(curve, ox, oy, dx, dy, ratio) {
|
|
|
4346
5263
|
const a = 1 + k * px * px, b = k * px * py, d = k * py * px, e = 1 + k * py * py;
|
|
4347
5264
|
const tx = ox - a * ox - b * oy;
|
|
4348
5265
|
const ty = oy - d * ox - e * oy;
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
}
|
|
4357
|
-
return c2dWrap(makeBezier2d$1(pts));
|
|
5266
|
+
return c2dWrap(affineCurve2d(c2d(curve), {
|
|
5267
|
+
a,
|
|
5268
|
+
b,
|
|
5269
|
+
c: d,
|
|
5270
|
+
d: e,
|
|
5271
|
+
tx,
|
|
5272
|
+
ty
|
|
5273
|
+
}));
|
|
4358
5274
|
}
|
|
4359
5275
|
function affinityTransform2d(curve, ox, oy, dx, dy, ratio) {
|
|
4360
5276
|
return affinityCurve2d(curve, ox, oy, dx, dy, ratio);
|
|
@@ -4499,103 +5415,46 @@ function projectPointOnCurve2d(curve, x, y) {
|
|
|
4499
5415
|
return projectOnBasis(c, bounds.first, bounds.last);
|
|
4500
5416
|
}
|
|
4501
5417
|
function distanceBetweenCurves2d(c1, c2, p1Start, p1End, p2Start, p2End) {
|
|
4502
|
-
|
|
4503
|
-
let minDist = Infinity;
|
|
4504
|
-
for (let i = 0; i <= n; i++) {
|
|
4505
|
-
const t1 = p1Start + (p1End - p1Start) * (i / n);
|
|
4506
|
-
const [x1, y1] = evaluateCurve2d$1(c2d(c1), t1);
|
|
4507
|
-
for (let j = 0; j <= n; j++) {
|
|
4508
|
-
const t2 = p2Start + (p2End - p2Start) * (j / n);
|
|
4509
|
-
const [x2, y2] = evaluateCurve2d$1(c2d(c2), t2);
|
|
4510
|
-
const d = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
|
|
4511
|
-
if (d < minDist) minDist = d;
|
|
4512
|
-
}
|
|
4513
|
-
}
|
|
4514
|
-
return minDist;
|
|
5418
|
+
return distanceBetweenCurves2d$1(c2d(c1), c2d(c2), p1Start, p1End, p2Start, p2End);
|
|
4515
5419
|
}
|
|
4516
|
-
|
|
4517
|
-
|
|
5420
|
+
/**
|
|
5421
|
+
* The exact B-spline of a Bezier or B-spline basis over its trim range, traversed
|
|
5422
|
+
* from `from` to `to`; null for an empty range.
|
|
5423
|
+
*/
|
|
5424
|
+
function trimmedPoleCurve(basis, from, to) {
|
|
5425
|
+
if (from === to) return null;
|
|
5426
|
+
const sub = subBSpline2d(asBSpline2d(basis), Math.min(from, to), Math.max(from, to));
|
|
5427
|
+
if (from < to) return sub;
|
|
5428
|
+
const reversed = reverseCurve2d$1(sub);
|
|
5429
|
+
return reversed.__bk2d === "bspline" ? reversed : null;
|
|
5430
|
+
}
|
|
5431
|
+
/**
|
|
5432
|
+
* Degree <= 3 B-spline within `tolerance` of the curve (Geom2dConvert_ApproxCurve
|
|
5433
|
+
* with MaxDegree 3, as brepjs calls it); exact where the curve already is one.
|
|
5434
|
+
*/
|
|
5435
|
+
function approximateCurve2dAsBSpline(curve, tolerance, maxSegments) {
|
|
5436
|
+
const cu = flattenTrimmed(c2d(curve));
|
|
5437
|
+
const basis = cu.__bk2d === "trimmed" ? cu.basis : cu;
|
|
5438
|
+
const poleCurve = basis.__bk2d === "bspline" || basis.__bk2d === "bezier" ? basis : null;
|
|
5439
|
+
if (poleCurve && asBSpline2d(poleCurve).degree <= 3) {
|
|
5440
|
+
if (cu.__bk2d !== "trimmed") return copyCurve2d(asBSpline2d(poleCurve));
|
|
5441
|
+
const exact = trimmedPoleCurve(poleCurve, cu.tStart, cu.tEnd);
|
|
5442
|
+
if (exact) return c2dWrap(exact);
|
|
5443
|
+
}
|
|
5444
|
+
if (cu.__bk2d === "line") return c2dWrap(asBSpline2d(makeBezier2d$1([evaluateCurve2d$1(cu, 0), evaluateCurve2d$1(cu, cu.len)])));
|
|
4518
5445
|
const bounds = curveBounds(cu);
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
poles.push(evaluateCurve2d$1(cu, t));
|
|
4524
|
-
}
|
|
4525
|
-
const degree = Math.min(3, nPts - 1);
|
|
4526
|
-
const n = poles.length;
|
|
4527
|
-
const knots = [];
|
|
4528
|
-
const mults = [];
|
|
4529
|
-
const nInternalKnots = n - degree - 1;
|
|
4530
|
-
knots.push(0);
|
|
4531
|
-
mults.push(degree + 1);
|
|
4532
|
-
for (let i = 1; i <= nInternalKnots; i++) {
|
|
4533
|
-
knots.push(i / (nInternalKnots + 1));
|
|
4534
|
-
mults.push(1);
|
|
4535
|
-
}
|
|
4536
|
-
knots.push(1);
|
|
4537
|
-
mults.push(degree + 1);
|
|
4538
|
-
return c2dWrap({
|
|
4539
|
-
__bk2d: "bspline",
|
|
4540
|
-
poles,
|
|
4541
|
-
knots,
|
|
4542
|
-
multiplicities: mults,
|
|
4543
|
-
degree,
|
|
4544
|
-
isPeriodic: false
|
|
4545
|
-
});
|
|
5446
|
+
return c2dWrap(fitBSpline2d((t) => evaluateCurve2d$1(cu, t), bounds.first, bounds.last, tolerance, {
|
|
5447
|
+
maxSegments: Math.max(8, maxSegments),
|
|
5448
|
+
breaks: curveKnotParams(cu)
|
|
5449
|
+
}));
|
|
4546
5450
|
}
|
|
4547
5451
|
function decomposeBSpline2dToBeziers(curve) {
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
if (
|
|
4551
|
-
const
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
for (let i = 0; i < cu.knots.length; i++) {
|
|
4555
|
-
const knot = cu.knots[i] ?? 0;
|
|
4556
|
-
const mult = cu.multiplicities[i] ?? 0;
|
|
4557
|
-
for (let j = 0; j < mult; j++) U.push(knot);
|
|
4558
|
-
}
|
|
4559
|
-
const m = poles.length - 1 + p + 1;
|
|
4560
|
-
if (p < 1 || U.length !== m + 1) return [c2dWrap(makeBezier2d$1(poles))];
|
|
4561
|
-
const clone = (pt) => [pt[0], pt[1]];
|
|
4562
|
-
const segments = [];
|
|
4563
|
-
let a = p;
|
|
4564
|
-
let b = p + 1;
|
|
4565
|
-
let segPoles = [];
|
|
4566
|
-
for (let i = 0; i <= p; i++) segPoles[i] = clone(poles[i]);
|
|
4567
|
-
let nextPoles = [];
|
|
4568
|
-
while (b < m) {
|
|
4569
|
-
const i0 = b;
|
|
4570
|
-
while (b < m && U[b + 1] === U[b]) b++;
|
|
4571
|
-
const mult = b - i0 + 1;
|
|
4572
|
-
if (mult < p) {
|
|
4573
|
-
const numer = U[b] - U[a];
|
|
4574
|
-
const alphas = [];
|
|
4575
|
-
for (let j = p; j > mult; j--) alphas[j - mult - 1] = numer / (U[a + j] - U[a]);
|
|
4576
|
-
const r = p - mult;
|
|
4577
|
-
for (let j = 1; j <= r; j++) {
|
|
4578
|
-
const save = r - j;
|
|
4579
|
-
const s = mult + j;
|
|
4580
|
-
for (let k = p; k >= s; k--) {
|
|
4581
|
-
const alpha = alphas[k - s];
|
|
4582
|
-
const cur = segPoles[k];
|
|
4583
|
-
const prev = segPoles[k - 1];
|
|
4584
|
-
segPoles[k] = [alpha * cur[0] + (1 - alpha) * prev[0], alpha * cur[1] + (1 - alpha) * prev[1]];
|
|
4585
|
-
}
|
|
4586
|
-
if (b < m) nextPoles[save] = clone(segPoles[p]);
|
|
4587
|
-
}
|
|
4588
|
-
}
|
|
4589
|
-
segments.push(segPoles);
|
|
4590
|
-
if (b < m) {
|
|
4591
|
-
for (let i = p - mult; i <= p; i++) nextPoles[i] = clone(poles[b - p + i]);
|
|
4592
|
-
segPoles = nextPoles;
|
|
4593
|
-
nextPoles = [];
|
|
4594
|
-
a = b;
|
|
4595
|
-
b++;
|
|
4596
|
-
}
|
|
4597
|
-
}
|
|
4598
|
-
return segments.map((sp) => c2dWrap(makeBezier2d$1(sp)));
|
|
5452
|
+
const cu = flattenTrimmed(c2d(curve));
|
|
5453
|
+
const basis = cu.__bk2d === "trimmed" ? cu.basis : cu;
|
|
5454
|
+
if (basis.__bk2d !== "bspline") return [curve];
|
|
5455
|
+
const piece = cu.__bk2d === "trimmed" ? trimmedPoleCurve(basis, cu.tStart, cu.tEnd) : basis;
|
|
5456
|
+
if (!piece) return [curve];
|
|
5457
|
+
return bezierSegments2d(piece).map((poles) => c2dWrap(makeBezier2d$1(poles)));
|
|
4599
5458
|
}
|
|
4600
5459
|
function createBoundingBox2d() {
|
|
4601
5460
|
return createBBox2d();
|
|
@@ -4678,78 +5537,174 @@ function splitCurve2d(curve, params) {
|
|
|
4678
5537
|
}
|
|
4679
5538
|
return result;
|
|
4680
5539
|
}
|
|
4681
|
-
function
|
|
4682
|
-
const
|
|
4683
|
-
const [
|
|
4684
|
-
const [
|
|
4685
|
-
const [xx, xy, xz] = planeX;
|
|
5540
|
+
function planeFrame(origin, z, x) {
|
|
5541
|
+
const [ox, oy, oz] = origin;
|
|
5542
|
+
const [zx, zy, zz] = z;
|
|
5543
|
+
const [xx, xy, xz] = x;
|
|
4686
5544
|
const yx = zy * xz - zz * xy, yy = zz * xx - zx * xz, yz = zx * xy - zy * xx;
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
5545
|
+
return {
|
|
5546
|
+
point: (u, v) => [
|
|
5547
|
+
ox + u * xx + v * yx,
|
|
5548
|
+
oy + u * xy + v * yy,
|
|
5549
|
+
oz + u * xz + v * yz
|
|
5550
|
+
],
|
|
5551
|
+
dir: (u, v) => [
|
|
5552
|
+
u * xx + v * yx,
|
|
5553
|
+
u * xy + v * yy,
|
|
5554
|
+
u * xz + v * yz
|
|
5555
|
+
],
|
|
5556
|
+
normal: z
|
|
5557
|
+
};
|
|
5558
|
+
}
|
|
5559
|
+
/** Lift an ellipse (or an arc of it over `range`) as an exact OCCT ellipse edge. */
|
|
5560
|
+
function liftEllipse(k, frame, basis, range) {
|
|
5561
|
+
let e = basis;
|
|
5562
|
+
let r = range;
|
|
5563
|
+
if (r && r[1] < r[0]) {
|
|
5564
|
+
e = {
|
|
5565
|
+
...e,
|
|
5566
|
+
sense: !e.sense
|
|
5567
|
+
};
|
|
5568
|
+
r = [-r[0], -r[1]];
|
|
5569
|
+
}
|
|
5570
|
+
let major = e.majorRadius;
|
|
5571
|
+
let minor = e.minorRadius;
|
|
5572
|
+
let angle = e.xDirAngle;
|
|
5573
|
+
let shift = 0;
|
|
5574
|
+
if (major < minor) {
|
|
5575
|
+
[major, minor] = [minor, major];
|
|
5576
|
+
angle += Math.PI / 2;
|
|
5577
|
+
shift = e.sense ? -Math.PI / 2 : Math.PI / 2;
|
|
5578
|
+
}
|
|
5579
|
+
const n = frame.normal;
|
|
5580
|
+
const normal = e.sense ? n : [
|
|
5581
|
+
-n[0],
|
|
5582
|
+
-n[1],
|
|
5583
|
+
-n[2]
|
|
4691
5584
|
];
|
|
4692
|
-
const
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
const
|
|
4707
|
-
const
|
|
4708
|
-
|
|
4709
|
-
}
|
|
4710
|
-
if (trimmed.basis && trimmed.basis.__bk2d === "circle") {
|
|
4711
|
-
const bounds = curveBounds(cu);
|
|
4712
|
-
const [u1, v1] = evaluateCurve2d$1(cu, bounds.first);
|
|
4713
|
-
const [um, vm] = evaluateCurve2d$1(cu, (bounds.first + bounds.last) / 2);
|
|
4714
|
-
const [u2, v2] = evaluateCurve2d$1(cu, bounds.last);
|
|
4715
|
-
const p1 = lift(u1, v1);
|
|
4716
|
-
const pm = lift(um, vm);
|
|
4717
|
-
const p2 = lift(u2, v2);
|
|
4718
|
-
return handle("edge", k.makeArcEdge(p1[0], p1[1], p1[2], pm[0], pm[1], pm[2], p2[0], p2[1], p2[2]));
|
|
5585
|
+
const center = frame.point(e.cx, e.cy);
|
|
5586
|
+
const xDir = frame.dir(Math.cos(angle), Math.sin(angle));
|
|
5587
|
+
if (!r || r[1] - r[0] >= 2 * Math.PI - 1e-12) return makeEllipseEdge(k, center, normal, major, minor, xDir);
|
|
5588
|
+
return makeEllipseArc(k, center, normal, major, minor, r[0] + shift, r[1] + shift, xDir);
|
|
5589
|
+
}
|
|
5590
|
+
function splitBezier(poles, t) {
|
|
5591
|
+
const work = poles.map(([x, y]) => [x, y]);
|
|
5592
|
+
const n = work.length;
|
|
5593
|
+
const left = [];
|
|
5594
|
+
const right = [];
|
|
5595
|
+
left.push([...work[0]]);
|
|
5596
|
+
right.push([...work[n - 1]]);
|
|
5597
|
+
for (let r = 1; r < n; r++) {
|
|
5598
|
+
for (let i = 0; i < n - r; i++) {
|
|
5599
|
+
const a = work[i];
|
|
5600
|
+
const b = work[i + 1];
|
|
5601
|
+
work[i] = [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
|
4719
5602
|
}
|
|
5603
|
+
left.push([...work[0]]);
|
|
5604
|
+
right.push([...work[n - 1 - r]]);
|
|
4720
5605
|
}
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
5606
|
+
return [left, right.reverse()];
|
|
5607
|
+
}
|
|
5608
|
+
/** Poles of the Bezier restricted to [from, to] (either order), traversed from -> to. */
|
|
5609
|
+
function bezierSegmentPoles(poles, from, to) {
|
|
5610
|
+
const lo = Math.min(from, to);
|
|
5611
|
+
const hi = Math.max(from, to);
|
|
5612
|
+
let seg = poles;
|
|
5613
|
+
if (hi < 1) seg = splitBezier(seg, hi)[0];
|
|
5614
|
+
if (lo > 0) seg = splitBezier(seg, hi > 0 ? lo / hi : 0)[1];
|
|
5615
|
+
return from <= to ? seg : [...seg].reverse();
|
|
5616
|
+
}
|
|
5617
|
+
function liftBezier(k, Module, frame, poles) {
|
|
5618
|
+
const vec = new Module.VectorDouble();
|
|
5619
|
+
try {
|
|
5620
|
+
for (const [u, v] of poles) for (const x of frame.point(u, v)) vec.push_back(x);
|
|
5621
|
+
return handle("edge", k.makeBezierEdge(vec));
|
|
5622
|
+
} finally {
|
|
5623
|
+
vec.delete();
|
|
4735
5624
|
}
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
5625
|
+
}
|
|
5626
|
+
/** Lift a non-periodic B-spline exactly (an affine lift maps poles to poles). */
|
|
5627
|
+
function liftBSpline(k, Module, frame, c) {
|
|
5628
|
+
if (!k.makeBSplineEdge || c.isPeriodic) return null;
|
|
5629
|
+
const poles = new Module.VectorDouble();
|
|
5630
|
+
const weights = new Module.VectorDouble();
|
|
5631
|
+
const knots = new Module.VectorDouble();
|
|
5632
|
+
const mults = new Module.VectorInt();
|
|
5633
|
+
try {
|
|
5634
|
+
for (const [u, v] of c.poles) for (const x of frame.point(u, v)) poles.push_back(x);
|
|
5635
|
+
for (const knot of c.knots) knots.push_back(knot);
|
|
5636
|
+
for (const m of c.multiplicities) mults.push_back(m);
|
|
5637
|
+
return handle("edge", k.makeBSplineEdge(poles, weights, knots, mults, c.degree, false));
|
|
5638
|
+
} finally {
|
|
5639
|
+
poles.delete();
|
|
5640
|
+
weights.delete();
|
|
5641
|
+
knots.delete();
|
|
5642
|
+
mults.delete();
|
|
4744
5643
|
}
|
|
5644
|
+
}
|
|
5645
|
+
function liftFitted(k, Module, frame, cu) {
|
|
5646
|
+
const bounds = curveBounds(cu);
|
|
5647
|
+
const bbox = createBBox2d();
|
|
5648
|
+
addCurveToBBox(bbox, cu, 0);
|
|
5649
|
+
const extent = Math.hypot(bbox.xMax - bbox.xMin, bbox.yMax - bbox.yMin);
|
|
5650
|
+
const knots = curveKnotParams(cu);
|
|
5651
|
+
const fit = fitBSpline2d((t) => evaluateCurve2d$1(cu, t), bounds.first, bounds.last, 1e-9 * Math.max(1, extent), { breaks: knots });
|
|
5652
|
+
const exact = liftBSpline(k, Module, frame, fit);
|
|
5653
|
+
if (exact) return exact;
|
|
5654
|
+
const nSamples = Math.max(24, fit.poles.length);
|
|
5655
|
+
const span = bounds.last - bounds.first;
|
|
5656
|
+
const params = [];
|
|
5657
|
+
for (let i = 0; i <= nSamples; i++) params.push(bounds.first + span * i / nSamples);
|
|
5658
|
+
for (const t of knots) {
|
|
5659
|
+
const nearest = bounds.first + span * Math.round((t - bounds.first) / span * nSamples) / nSamples;
|
|
5660
|
+
if (Math.abs(t - nearest) > 1e-9 * span) params.push(t);
|
|
5661
|
+
}
|
|
5662
|
+
params.sort((a, b) => a - b);
|
|
5663
|
+
const distinct = params.filter((t, i) => i === 0 || t - (params[i - 1] ?? t) > 1e-9 * span);
|
|
4745
5664
|
const vec = new Module.VectorDouble();
|
|
4746
|
-
for (const p of pts) vec.push_back(p);
|
|
4747
5665
|
try {
|
|
5666
|
+
for (const t of distinct) {
|
|
5667
|
+
const [u, v] = evaluateCurve2d$1(cu, t);
|
|
5668
|
+
for (const x of frame.point(u, v)) vec.push_back(x);
|
|
5669
|
+
}
|
|
4748
5670
|
return handle("edge", k.interpolatePoints(vec, false));
|
|
4749
5671
|
} finally {
|
|
4750
5672
|
vec.delete();
|
|
4751
5673
|
}
|
|
4752
5674
|
}
|
|
5675
|
+
function liftCurve2dToPlane(k, Module, curve, planeOrigin, planeZ, planeX) {
|
|
5676
|
+
const cu = flattenTrimmed(c2d(curve));
|
|
5677
|
+
const frame = planeFrame(planeOrigin, planeZ, planeX);
|
|
5678
|
+
const trimmed = cu.__bk2d === "trimmed" ? cu : null;
|
|
5679
|
+
const basis = trimmed ? trimmed.basis : cu;
|
|
5680
|
+
const range = trimmed ? [trimmed.tStart, trimmed.tEnd] : null;
|
|
5681
|
+
const bounds = curveBounds(cu);
|
|
5682
|
+
switch (basis.__bk2d) {
|
|
5683
|
+
case "line": {
|
|
5684
|
+
const p1 = frame.point(...evaluateCurve2d$1(cu, bounds.first));
|
|
5685
|
+
const p2 = frame.point(...evaluateCurve2d$1(cu, bounds.last));
|
|
5686
|
+
return handle("edge", k.makeLineEdge(p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]));
|
|
5687
|
+
}
|
|
5688
|
+
case "circle": {
|
|
5689
|
+
if (!range || Math.abs(range[1] - range[0]) >= 2 * Math.PI - 1e-12) {
|
|
5690
|
+
const [pcx, pcy, pcz] = frame.point(basis.cx, basis.cy);
|
|
5691
|
+
const [zx, zy, zz] = planeZ;
|
|
5692
|
+
return handle("edge", k.makeCircleEdge(pcx, pcy, pcz, zx, zy, zz, basis.radius));
|
|
5693
|
+
}
|
|
5694
|
+
const p1 = frame.point(...evaluateCurve2d$1(cu, bounds.first));
|
|
5695
|
+
const pm = frame.point(...evaluateCurve2d$1(cu, (bounds.first + bounds.last) / 2));
|
|
5696
|
+
const p2 = frame.point(...evaluateCurve2d$1(cu, bounds.last));
|
|
5697
|
+
return handle("edge", k.makeArcEdge(p1[0], p1[1], p1[2], pm[0], pm[1], pm[2], p2[0], p2[1], p2[2]));
|
|
5698
|
+
}
|
|
5699
|
+
case "ellipse": return liftEllipse(k, frame, basis, range);
|
|
5700
|
+
case "bezier": return liftBezier(k, Module, frame, range ? bezierSegmentPoles(basis.poles, range[0], range[1]) : basis.poles);
|
|
5701
|
+
case "bspline": {
|
|
5702
|
+
const piece = range ? trimmedPoleCurve(basis, range[0], range[1]) : basis;
|
|
5703
|
+
return piece && liftBSpline(k, Module, frame, piece) || liftFitted(k, Module, frame, cu);
|
|
5704
|
+
}
|
|
5705
|
+
case "trimmed": return liftFitted(k, Module, frame, cu);
|
|
5706
|
+
}
|
|
5707
|
+
}
|
|
4753
5708
|
function buildEdgeOnSurface(k, Module, curve, surface) {
|
|
4754
5709
|
const cu = c2d(curve);
|
|
4755
5710
|
const bounds = curveBounds(cu);
|
|
@@ -5831,8 +6786,8 @@ var OcctWasmAdapter = class OcctWasmAdapter {
|
|
|
5831
6786
|
makeBezier2d(points) {
|
|
5832
6787
|
return makeBezier2d(points);
|
|
5833
6788
|
}
|
|
5834
|
-
makeBSpline2d(points,
|
|
5835
|
-
return makeBSpline2d(points);
|
|
6789
|
+
makeBSpline2d(points, options) {
|
|
6790
|
+
return makeBSpline2d(points, options);
|
|
5836
6791
|
}
|
|
5837
6792
|
evaluateCurve2d(curve, param) {
|
|
5838
6793
|
return evaluateCurve2d(curve, param);
|
|
@@ -5849,7 +6804,9 @@ var OcctWasmAdapter = class OcctWasmAdapter {
|
|
|
5849
6804
|
trimCurve2d(curve, start, end) {
|
|
5850
6805
|
return trimCurve2d(curve, start, end);
|
|
5851
6806
|
}
|
|
5852
|
-
reverseCurve2d(curve) {
|
|
6807
|
+
reverseCurve2d(curve) {
|
|
6808
|
+
reverseCurve2d(curve);
|
|
6809
|
+
}
|
|
5853
6810
|
copyCurve2d(curve) {
|
|
5854
6811
|
return copyCurve2d(curve);
|
|
5855
6812
|
}
|
|
@@ -5910,8 +6867,8 @@ var OcctWasmAdapter = class OcctWasmAdapter {
|
|
|
5910
6867
|
distanceBetweenCurves2d(c1, c2, p1Start, p1End, p2Start, p2End) {
|
|
5911
6868
|
return distanceBetweenCurves2d(c1, c2, p1Start, p1End, p2Start, p2End);
|
|
5912
6869
|
}
|
|
5913
|
-
approximateCurve2dAsBSpline(curve,
|
|
5914
|
-
return approximateCurve2dAsBSpline(curve, maxSegments);
|
|
6870
|
+
approximateCurve2dAsBSpline(curve, tolerance, _continuity, maxSegments) {
|
|
6871
|
+
return approximateCurve2dAsBSpline(curve, tolerance, maxSegments);
|
|
5915
6872
|
}
|
|
5916
6873
|
decomposeBSpline2dToBeziers(curve) {
|
|
5917
6874
|
return decomposeBSpline2dToBeziers(curve);
|
|
@@ -6139,12 +7096,24 @@ Object.defineProperty(exports, "mirrorAtPoint", {
|
|
|
6139
7096
|
return mirrorAtPoint;
|
|
6140
7097
|
}
|
|
6141
7098
|
});
|
|
7099
|
+
Object.defineProperty(exports, "offsetCurve2d", {
|
|
7100
|
+
enumerable: true,
|
|
7101
|
+
get: function() {
|
|
7102
|
+
return offsetCurve2d$1;
|
|
7103
|
+
}
|
|
7104
|
+
});
|
|
6142
7105
|
Object.defineProperty(exports, "quickHull", {
|
|
6143
7106
|
enumerable: true,
|
|
6144
7107
|
get: function() {
|
|
6145
7108
|
return quickHull;
|
|
6146
7109
|
}
|
|
6147
7110
|
});
|
|
7111
|
+
Object.defineProperty(exports, "reverseCurve2d", {
|
|
7112
|
+
enumerable: true,
|
|
7113
|
+
get: function() {
|
|
7114
|
+
return reverseCurve2d$1;
|
|
7115
|
+
}
|
|
7116
|
+
});
|
|
6148
7117
|
Object.defineProperty(exports, "rotateCurve2d", {
|
|
6149
7118
|
enumerable: true,
|
|
6150
7119
|
get: function() {
|