@itwin/core-geometry 5.13.0-dev.4 → 5.13.0-dev.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/lib/cjs/curve/Arc3d.d.ts +1 -1
  3. package/lib/cjs/curve/Arc3d.js +1 -1
  4. package/lib/cjs/curve/Arc3d.js.map +1 -1
  5. package/lib/cjs/curve/CurveFactory.d.ts +14 -6
  6. package/lib/cjs/curve/CurveFactory.d.ts.map +1 -1
  7. package/lib/cjs/curve/CurveFactory.js +11 -1
  8. package/lib/cjs/curve/CurveFactory.js.map +1 -1
  9. package/lib/cjs/curve/LineSegment3d.d.ts +6 -1
  10. package/lib/cjs/curve/LineSegment3d.d.ts.map +1 -1
  11. package/lib/cjs/curve/LineSegment3d.js +8 -1
  12. package/lib/cjs/curve/LineSegment3d.js.map +1 -1
  13. package/lib/cjs/curve/Query/ConsolidateAdjacentPrimitivesContext.d.ts +9 -4
  14. package/lib/cjs/curve/Query/ConsolidateAdjacentPrimitivesContext.d.ts.map +1 -1
  15. package/lib/cjs/curve/Query/ConsolidateAdjacentPrimitivesContext.js +25 -16
  16. package/lib/cjs/curve/Query/ConsolidateAdjacentPrimitivesContext.js.map +1 -1
  17. package/lib/cjs/curve/Query/PlanarSubdivision.d.ts.map +1 -1
  18. package/lib/cjs/curve/Query/PlanarSubdivision.js +1 -3
  19. package/lib/cjs/curve/Query/PlanarSubdivision.js.map +1 -1
  20. package/lib/cjs/curve/RegionOps.d.ts +25 -3
  21. package/lib/cjs/curve/RegionOps.d.ts.map +1 -1
  22. package/lib/cjs/curve/RegionOps.js +2 -1
  23. package/lib/cjs/curve/RegionOps.js.map +1 -1
  24. package/lib/cjs/curve/internalContexts/CurveCurveIntersectXY.d.ts +4 -0
  25. package/lib/cjs/curve/internalContexts/CurveCurveIntersectXY.d.ts.map +1 -1
  26. package/lib/cjs/curve/internalContexts/CurveCurveIntersectXY.js +132 -106
  27. package/lib/cjs/curve/internalContexts/CurveCurveIntersectXY.js.map +1 -1
  28. package/lib/cjs/curve/spiral/TransitionSpiral3d.d.ts +5 -0
  29. package/lib/cjs/curve/spiral/TransitionSpiral3d.d.ts.map +1 -1
  30. package/lib/cjs/curve/spiral/TransitionSpiral3d.js +7 -0
  31. package/lib/cjs/curve/spiral/TransitionSpiral3d.js.map +1 -1
  32. package/lib/cjs/geometry3d/AngleSweep.d.ts +2 -2
  33. package/lib/cjs/geometry3d/AngleSweep.js +2 -2
  34. package/lib/cjs/geometry3d/AngleSweep.js.map +1 -1
  35. package/lib/cjs/geometry3d/Range.d.ts +8 -0
  36. package/lib/cjs/geometry3d/Range.d.ts.map +1 -1
  37. package/lib/cjs/geometry3d/Range.js +16 -0
  38. package/lib/cjs/geometry3d/Range.js.map +1 -1
  39. package/lib/esm/curve/Arc3d.d.ts +1 -1
  40. package/lib/esm/curve/Arc3d.js +1 -1
  41. package/lib/esm/curve/Arc3d.js.map +1 -1
  42. package/lib/esm/curve/CurveFactory.d.ts +14 -6
  43. package/lib/esm/curve/CurveFactory.d.ts.map +1 -1
  44. package/lib/esm/curve/CurveFactory.js +11 -1
  45. package/lib/esm/curve/CurveFactory.js.map +1 -1
  46. package/lib/esm/curve/LineSegment3d.d.ts +6 -1
  47. package/lib/esm/curve/LineSegment3d.d.ts.map +1 -1
  48. package/lib/esm/curve/LineSegment3d.js +8 -1
  49. package/lib/esm/curve/LineSegment3d.js.map +1 -1
  50. package/lib/esm/curve/Query/ConsolidateAdjacentPrimitivesContext.d.ts +9 -4
  51. package/lib/esm/curve/Query/ConsolidateAdjacentPrimitivesContext.d.ts.map +1 -1
  52. package/lib/esm/curve/Query/ConsolidateAdjacentPrimitivesContext.js +25 -16
  53. package/lib/esm/curve/Query/ConsolidateAdjacentPrimitivesContext.js.map +1 -1
  54. package/lib/esm/curve/Query/PlanarSubdivision.d.ts.map +1 -1
  55. package/lib/esm/curve/Query/PlanarSubdivision.js +2 -4
  56. package/lib/esm/curve/Query/PlanarSubdivision.js.map +1 -1
  57. package/lib/esm/curve/RegionOps.d.ts +25 -3
  58. package/lib/esm/curve/RegionOps.d.ts.map +1 -1
  59. package/lib/esm/curve/RegionOps.js +2 -1
  60. package/lib/esm/curve/RegionOps.js.map +1 -1
  61. package/lib/esm/curve/internalContexts/CurveCurveIntersectXY.d.ts +4 -0
  62. package/lib/esm/curve/internalContexts/CurveCurveIntersectXY.d.ts.map +1 -1
  63. package/lib/esm/curve/internalContexts/CurveCurveIntersectXY.js +132 -106
  64. package/lib/esm/curve/internalContexts/CurveCurveIntersectXY.js.map +1 -1
  65. package/lib/esm/curve/spiral/TransitionSpiral3d.d.ts +5 -0
  66. package/lib/esm/curve/spiral/TransitionSpiral3d.d.ts.map +1 -1
  67. package/lib/esm/curve/spiral/TransitionSpiral3d.js +7 -0
  68. package/lib/esm/curve/spiral/TransitionSpiral3d.js.map +1 -1
  69. package/lib/esm/geometry3d/AngleSweep.d.ts +2 -2
  70. package/lib/esm/geometry3d/AngleSweep.js +2 -2
  71. package/lib/esm/geometry3d/AngleSweep.js.map +1 -1
  72. package/lib/esm/geometry3d/Range.d.ts +8 -0
  73. package/lib/esm/geometry3d/Range.d.ts.map +1 -1
  74. package/lib/esm/geometry3d/Range.js +16 -0
  75. package/lib/esm/geometry3d/Range.js.map +1 -1
  76. package/package.json +3 -3
@@ -284,124 +284,149 @@ export class CurveCurveIntersectXY extends RecurseToCurvesGeometryHandler {
284
284
  this.computeSegmentSegment3D(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, cpB, extendB0, pointB0, fractionB0, pointB1, fractionB1, extendB1, reversed);
285
285
  }
286
286
  }
287
- // Caller accesses data from a linestring or segment and passes it here.
288
- // The line segment in question might be (a) a full line segment or (b) a fragment within a linestring.
289
- // The fraction and extend parameters allow all combinations to be passed in.
290
- dispatchSegmentArc(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, arc, extendB0, extendB1, reversed) {
287
+ /** Implementation of dispatchSegmentArc under affine transform. KEEP IN SYNC WITH computeSegmentArcHomogeneous. */
288
+ computeSegmentArc(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, arc, extendB0, extendB1, reversed) {
291
289
  const tol2 = this._coincidentGeometryContext.tolerance * this._coincidentGeometryContext.tolerance;
292
290
  const cosines = new GrowableFloat64Array(2);
293
291
  const sines = new GrowableFloat64Array(2);
294
292
  const radians = new GrowableFloat64Array(2);
295
- // Arc: X(theta) = C + cos(theta)·U + sin(theta)·V
296
- // Line: contains points A0,A1
297
- // Arc point X is colinear (intersects) with line if det(A0, A1, X) = 0 with homogeneous xyw points and vectors
298
- // This leads to
299
- // det(A0, A1, C) + cos(theta) det(A0, A1, U) + sin(theta) det(A0, A1, V) = 0
300
- // or
301
- // alpha + beta cos(theta) + gamma sin(theta) = 0
302
- // solve for theta; evaluate points; project back to line
303
- if (this._worldToLocalPerspective) {
304
- const data = arc.toTransformedPoint4d(this._worldToLocalPerspective);
305
- const pointA0H = this._worldToLocalPerspective.multiplyPoint3d(pointA0, 1);
306
- const pointA1H = this._worldToLocalPerspective.multiplyPoint3d(pointA1, 1);
307
- const alpha = Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.center);
308
- const beta = Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.vector0);
309
- const gamma = Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.vector90);
310
- let numRoots = AnalyticRoots.appendImplicitLineUnitCircleIntersections(alpha, beta, gamma, cosines, sines, radians, tol2);
311
- const closeApproach = (0 === numRoots);
312
- if (closeApproach)
313
- numRoots = 1; // arc closest approach is the first entry
314
- let numAccepted = 0;
315
- for (let i = 0; i < numRoots; i++) {
316
- const arcPoint = data.center.plus2Scaled(data.vector0, cosines.atUncheckedIndex(i), data.vector90, sines.atUncheckedIndex(i));
317
- const fArc = data.sweep.radiansToSignedFraction(radians.atUncheckedIndex(i), extendB0);
318
- const fLine = SmallSystem.lineSegment3dHXYClosestPointUnbounded(pointA0H, pointA1H, arcPoint);
319
- if (fLine === undefined)
320
- continue;
321
- if (closeApproach) {
322
- const linePoint = pointA0H.interpolate(fLine, pointA1H);
323
- const dist2 = arcPoint.realDistanceSquaredXY(linePoint);
324
- if (dist2 === undefined || !Geometry.isDistanceWithinTol(dist2, tol2))
325
- continue;
326
- }
327
- if (this.acceptFraction(extendA0, fLine, extendA1) && this.acceptFraction(extendB0, fArc, extendB1)) {
328
- this.recordPointWithLocalFractions(fLine, cpA, fractionA0, fractionA1, fArc, arc, 0, 1, reversed);
329
- numAccepted++;
330
- }
293
+ const recordIfNearIntersection = (fA, fB, d2) => {
294
+ if (!Geometry.isDistanceWithinTol(d2, tol2))
295
+ return false;
296
+ this.recordPointWithLocalFractions(fA, cpA, fractionA0, fractionA1, fB, arc, 0, 1, reversed);
297
+ return true;
298
+ };
299
+ const data = arc.toTransformedVectors(this._worldToLocalAffine);
300
+ let pointA0Local = pointA0;
301
+ let pointA1Local = pointA1;
302
+ const pointB0Local = arc.startPoint();
303
+ const pointB1Local = arc.endPoint();
304
+ if (this._worldToLocalAffine) {
305
+ pointA0Local = this._worldToLocalAffine.multiplyPoint3d(pointA0);
306
+ pointA1Local = this._worldToLocalAffine.multiplyPoint3d(pointA1);
307
+ this._worldToLocalAffine.multiplyXYAndZInPlace(pointB0Local);
308
+ this._worldToLocalAffine.multiplyXYAndZInPlace(pointB1Local);
309
+ }
310
+ const alpha = Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.center, 1);
311
+ const beta = Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.vector0, 0);
312
+ const gamma = Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.vector90, 0);
313
+ // note really tight tol for tangency since below we check closest approach also
314
+ let numRoots = AnalyticRoots.appendImplicitLineUnitCircleIntersections(alpha, beta, gamma, cosines, sines, radians, tol2);
315
+ const closeApproach = (0 === numRoots);
316
+ if (closeApproach)
317
+ numRoots = 1; // we returned the closest approach instead: first entry is the arc point, which is all we need
318
+ const roots = [];
319
+ for (let i = 0; i < numRoots; i++) {
320
+ const arcPt = data.center.plus2Scaled(data.vector0, cosines.atUncheckedIndex(i), data.vector90, sines.atUncheckedIndex(i));
321
+ const fArc = data.sweep.radiansToSignedFraction(radians.atUncheckedIndex(i), extendB0); // exterior fraction sign based on extend flags
322
+ const fArcNearest01 = data.sweep.fractionToSignedPeriodicFraction(fArc, undefined); // exterior fraction sign based on proximity to 0/1
323
+ const fLine = SmallSystem.lineSegment3dXYClosestPointUnbounded(pointA0Local, pointA1Local, arcPt);
324
+ if (fLine !== undefined) {
325
+ const linePt = pointA0Local.interpolate(fLine, pointA1Local);
326
+ roots.push({ fArc, fArcNearest01, fLine, arcPt, linePt });
331
327
  }
332
- if (numAccepted === 0 && !arc.sweep.isFullCircle) {
333
- // check for endpoint intersection beyond parametric tolerance but within point tolerance
334
- const pointB0H = this._worldToLocalPerspective.multiplyPoint3d(arc.startPoint(), 1);
335
- const pointB1H = this._worldToLocalPerspective.multiplyPoint3d(arc.endPoint(), 1);
336
- const dist2 = [];
337
- let d2 = pointA0H.realDistanceSquaredXY(pointB0H);
338
- if (d2 !== undefined)
339
- dist2.push({ d2, fA: 0, fB: 0 });
340
- d2 = pointA0H.realDistanceSquaredXY(pointB1H);
341
- if (d2 !== undefined)
342
- dist2.push({ d2, fA: 0, fB: 1 });
343
- d2 = pointA1H.realDistanceSquaredXY(pointB0H);
344
- if (d2 !== undefined)
345
- dist2.push({ d2, fA: 1, fB: 0 });
346
- d2 = pointA1H.realDistanceSquaredXY(pointB1H);
347
- if (d2 !== undefined)
348
- dist2.push({ d2, fA: 1, fB: 1 });
349
- dist2.sort((a, b) => a.d2 - b.d2);
350
- if (Geometry.isDistanceWithinTol(dist2[0].d2, tol2))
351
- this.recordPointWithLocalFractions(dist2[0].fA, cpA, fractionA0, fractionA1, dist2[0].fB, arc, 0, 1, reversed);
328
+ }
329
+ // range-test and record roots
330
+ for (const root of roots) {
331
+ const { fArc, fArcNearest01, fLine, arcPt, linePt } = root;
332
+ if (closeApproach && !Geometry.isDistanceWithinTol(arcPt.distanceSquaredXY(linePt), tol2)) {
333
+ continue;
334
+ }
335
+ if (this.acceptFraction(extendA0, fLine, extendA1) && this.acceptFraction(extendB0, fArc, extendB1)) {
336
+ this.recordPointWithLocalFractions(fLine, cpA, fractionA0, fractionA1, fArc, arc, 0, 1, reversed);
337
+ continue;
352
338
  }
339
+ // since line-arc chains are so common, look for near-endpoint intersections using distance tol, which is often relatively looser than fraction tol
340
+ const inArcRange = extendB0 || extendB1 || Geometry.isIn01(fArc);
341
+ const inLineRange = (extendA0 && fLine < 0) || (extendA1 && fLine > 1) || Geometry.isIn01(fLine);
342
+ let accepted = false;
343
+ if (inArcRange && !inLineRange) // check tangency near line endpoint
344
+ accepted = recordIfNearIntersection(fLine < 0 ? 0 : 1, fArc, (fLine < 0 ? pointA0Local : pointA1Local).distanceSquaredXY(arcPt));
345
+ else if (!inArcRange && inLineRange) // check tangency near arc endpoint
346
+ accepted = recordIfNearIntersection(fLine, fArcNearest01 < 0 ? 0 : 1, linePt.distanceSquaredXY(fArcNearest01 < 0 ? pointB0Local : pointB1Local));
347
+ if (!accepted) // check intersection between nearest endpoints
348
+ recordIfNearIntersection(fLine < 0.5 ? 0 : 1, fArcNearest01 < 0.5 ? 0 : 1, (fLine < 0.5 ? pointA0Local : pointA1Local).distanceSquaredXY(fArcNearest01 < 0.5 ? pointB0Local : pointB1Local));
353
349
  }
354
- else {
355
- const data = arc.toTransformedVectors(this._worldToLocalAffine);
356
- let pointA0Local = pointA0;
357
- let pointA1Local = pointA1;
358
- if (this._worldToLocalAffine) {
359
- pointA0Local = this._worldToLocalAffine.multiplyPoint3d(pointA0);
360
- pointA1Local = this._worldToLocalAffine.multiplyPoint3d(pointA1);
350
+ }
351
+ /** Implementation of dispatchSegmentArc under perspective transform. KEEP IN SYNC WITH computeSegmentArc. */
352
+ computeSegmentArcHomogeneous(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, arc, extendB0, extendB1, reversed) {
353
+ if (!this._worldToLocalPerspective)
354
+ return;
355
+ const tol2 = this._coincidentGeometryContext.tolerance * this._coincidentGeometryContext.tolerance;
356
+ const cosines = new GrowableFloat64Array(2);
357
+ const sines = new GrowableFloat64Array(2);
358
+ const radians = new GrowableFloat64Array(2);
359
+ const recordIfNearIntersection = (fA, fB, d2) => {
360
+ if (d2 === undefined || !Geometry.isDistanceWithinTol(d2, tol2))
361
+ return false;
362
+ this.recordPointWithLocalFractions(fA, cpA, fractionA0, fractionA1, fB, arc, 0, 1, reversed);
363
+ return true;
364
+ };
365
+ const data = arc.toTransformedPoint4d(this._worldToLocalPerspective);
366
+ const pointA0H = this._worldToLocalPerspective.multiplyPoint3d(pointA0, 1);
367
+ const pointA1H = this._worldToLocalPerspective.multiplyPoint3d(pointA1, 1);
368
+ const pointB0H = this._worldToLocalPerspective.multiplyPoint3d(arc.startPoint(), 1);
369
+ const pointB1H = this._worldToLocalPerspective.multiplyPoint3d(arc.endPoint(), 1);
370
+ const alpha = Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.center);
371
+ const beta = Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.vector0);
372
+ const gamma = Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.vector90);
373
+ // note really tight tol for tangency since below we check closest approach also
374
+ let numRoots = AnalyticRoots.appendImplicitLineUnitCircleIntersections(alpha, beta, gamma, cosines, sines, radians, tol2);
375
+ const closeApproach = (0 === numRoots);
376
+ if (closeApproach)
377
+ numRoots = 1; // we returned the closest approach instead: first entry is the arc point, which is all we need
378
+ const roots = [];
379
+ for (let i = 0; i < numRoots; i++) {
380
+ const arcPt = data.center.plus2Scaled(data.vector0, cosines.atUncheckedIndex(i), data.vector90, sines.atUncheckedIndex(i));
381
+ const fArc = data.sweep.radiansToSignedFraction(radians.atUncheckedIndex(i), extendB0); // exterior fraction sign based on extend flags
382
+ const fArcNearest01 = data.sweep.fractionToSignedPeriodicFraction(fArc, undefined); // exterior fraction sign based on proximity to 0/1
383
+ const fLine = SmallSystem.lineSegment3dHXYClosestPointUnbounded(pointA0H, pointA1H, arcPt);
384
+ if (fLine !== undefined) {
385
+ const linePt = pointA0H.interpolate(fLine, pointA1H);
386
+ roots.push({ fArc, fArcNearest01, fLine, arcPt, linePt });
361
387
  }
362
- const alpha = Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.center, 1);
363
- const beta = Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.vector0, 0);
364
- const gamma = Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.vector90, 0);
365
- let numRoots = AnalyticRoots.appendImplicitLineUnitCircleIntersections(alpha, beta, gamma, cosines, sines, radians, tol2);
366
- const closeApproach = (0 === numRoots);
367
- if (closeApproach)
368
- numRoots = 1; // arc closest approach is the first entry
369
- let numAccepted = 0;
370
- for (let i = 0; i < numRoots; i++) {
371
- const arcPoint = data.center.plus2Scaled(data.vector0, cosines.atUncheckedIndex(i), data.vector90, sines.atUncheckedIndex(i));
372
- const fArc = data.sweep.radiansToSignedFraction(radians.atUncheckedIndex(i), extendB0);
373
- const fLine = SmallSystem.lineSegment3dXYClosestPointUnbounded(pointA0Local, pointA1Local, arcPoint);
374
- if (fLine === undefined)
388
+ }
389
+ // range-test and record roots
390
+ for (const root of roots) {
391
+ const { fArc, fArcNearest01, fLine, arcPt, linePt } = root;
392
+ if (closeApproach) {
393
+ const dist2 = arcPt.realDistanceSquaredXY(linePt);
394
+ if (dist2 === undefined || !Geometry.isDistanceWithinTol(dist2, tol2))
375
395
  continue;
376
- if (closeApproach) {
377
- const linePoint = pointA0Local.interpolate(fLine, pointA1Local);
378
- if (!Geometry.isDistanceWithinTol(arcPoint.distanceSquaredXY(linePoint), tol2))
379
- continue;
380
- }
381
- if (this.acceptFraction(extendA0, fLine, extendA1) && this.acceptFraction(extendB0, fArc, extendB1)) {
382
- this.recordPointWithLocalFractions(fLine, cpA, fractionA0, fractionA1, fArc, arc, 0, 1, reversed);
383
- numAccepted++;
384
- }
385
396
  }
386
- if (numAccepted === 0 && !arc.sweep.isFullCircle) {
387
- // check for endpoint intersection beyond parametric tolerance but within point tolerance
388
- const pointB0Local = arc.startPoint();
389
- const pointB1Local = arc.endPoint();
390
- if (this._worldToLocalAffine) {
391
- this._worldToLocalAffine.multiplyXYAndZInPlace(pointB0Local);
392
- this._worldToLocalAffine.multiplyXYAndZInPlace(pointB1Local);
393
- }
394
- const dist2 = [];
395
- dist2.push({ d2: pointA0Local.distanceSquaredXY(pointB0Local), fA: 0, fB: 0 });
396
- dist2.push({ d2: pointA0Local.distanceSquaredXY(pointB1Local), fA: 0, fB: 1 });
397
- dist2.push({ d2: pointA1Local.distanceSquaredXY(pointB0Local), fA: 1, fB: 0 });
398
- dist2.push({ d2: pointA1Local.distanceSquaredXY(pointB1Local), fA: 1, fB: 1 });
399
- dist2.sort((a, b) => a.d2 - b.d2);
400
- if (Geometry.isDistanceWithinTol(dist2[0].d2, tol2))
401
- this.recordPointWithLocalFractions(dist2[0].fA, cpA, fractionA0, fractionA1, dist2[0].fB, arc, 0, 1, reversed);
397
+ if (this.acceptFraction(extendA0, fLine, extendA1) && this.acceptFraction(extendB0, fArc, extendB1)) {
398
+ this.recordPointWithLocalFractions(fLine, cpA, fractionA0, fractionA1, fArc, arc, 0, 1, reversed);
399
+ continue;
402
400
  }
401
+ // since line-arc chains are so common, look for near-endpoint intersections using distance tol, which is often relatively looser than fraction tol
402
+ const inArcRange = extendB0 || extendB1 || Geometry.isIn01(fArc);
403
+ const inLineRange = (extendA0 && fLine < 0) || (extendA1 && fLine > 1) || Geometry.isIn01(fLine);
404
+ let accepted = false;
405
+ if (inArcRange && !inLineRange) // check tangency near line endpoint
406
+ accepted = recordIfNearIntersection(fLine < 0 ? 0 : 1, fArc, (fLine < 0 ? pointA0H : pointA1H).realDistanceSquaredXY(arcPt));
407
+ else if (!inArcRange && inLineRange) // check tangency near arc endpoint
408
+ accepted = recordIfNearIntersection(fLine, fArcNearest01 < 0 ? 0 : 1, linePt.realDistanceSquaredXY(fArcNearest01 < 0 ? pointB0H : pointB1H));
409
+ if (!accepted) // check intersection between nearest endpoints
410
+ recordIfNearIntersection(fLine < 0.5 ? 0 : 1, fArcNearest01 < 0.5 ? 0 : 1, (fLine < 0.5 ? pointA0H : pointA1H).realDistanceSquaredXY(fArcNearest01 < 0.5 ? pointB0H : pointB1H));
403
411
  }
404
412
  }
413
+ // Caller accesses data from a linestring or segment and passes it here.
414
+ // The line segment in question might be (a) a full line segment or (b) a fragment within a linestring.
415
+ // The fraction and extend parameters allow all combinations to be passed in.
416
+ dispatchSegmentArc(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, arc, extendB0, extendB1, reversed) {
417
+ // Arc: X(theta) = C + cos(theta)·U + sin(theta)·V
418
+ // Line: contains points A0,A1
419
+ // Arc point X is colinear (intersects) with line if det(A0, A1, X) = 0 with homogeneous xyw points and vectors
420
+ // This leads to
421
+ // det(A0, A1, C) + cos(theta) det(A0, A1, U) + sin(theta) det(A0, A1, V) = 0
422
+ // or
423
+ // alpha + beta cos(theta) + gamma sin(theta) = 0
424
+ // solve for theta; evaluate points; project back to line
425
+ if (this._worldToLocalPerspective)
426
+ this.computeSegmentArcHomogeneous(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, arc, extendB0, extendB1, reversed);
427
+ else
428
+ this.computeSegmentArc(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, arc, extendB0, extendB1, reversed);
429
+ }
405
430
  /**
406
431
  * Compute the intersection of two xy-arcs.
407
432
  * * Each matrix has [U V C] in (x,y,w) form from homogeneous projection (local to world).
@@ -896,7 +921,8 @@ export class CurveCurveIntersectXY extends RecurseToCurvesGeometryHandler {
896
921
  let converged = newtonSearcher.runIterations();
897
922
  const fractionA = newtonSearcher.getU();
898
923
  const fractionB = newtonSearcher.getV();
899
- if (this.acceptFraction(extendA0, fractionA, extendA1) && this.acceptFraction(false, fractionB, false)) {
924
+ // note looser tol for spiral, which can have high order tangency at start/end
925
+ if (this.acceptFraction(extendA0, fractionA, extendA1) && this.acceptFraction(false, fractionB, false, Geometry.smallFraction)) {
900
926
  const pointA = curveA.fractionToPoint(fractionA, CurveCurveIntersectXY._workPointA0);
901
927
  const pointB = spiralB.fractionToPoint(fractionB, CurveCurveIntersectXY._workPointB0);
902
928
  if (!converged) // Newton may have found close points even if it didn't converge parametrically