@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
@@ -287,124 +287,149 @@ class CurveCurveIntersectXY extends GeometryHandler_1.RecurseToCurvesGeometryHan
287
287
  this.computeSegmentSegment3D(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, cpB, extendB0, pointB0, fractionB0, pointB1, fractionB1, extendB1, reversed);
288
288
  }
289
289
  }
290
- // Caller accesses data from a linestring or segment and passes it here.
291
- // The line segment in question might be (a) a full line segment or (b) a fragment within a linestring.
292
- // The fraction and extend parameters allow all combinations to be passed in.
293
- dispatchSegmentArc(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, arc, extendB0, extendB1, reversed) {
290
+ /** Implementation of dispatchSegmentArc under affine transform. KEEP IN SYNC WITH computeSegmentArcHomogeneous. */
291
+ computeSegmentArc(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, arc, extendB0, extendB1, reversed) {
294
292
  const tol2 = this._coincidentGeometryContext.tolerance * this._coincidentGeometryContext.tolerance;
295
293
  const cosines = new GrowableFloat64Array_1.GrowableFloat64Array(2);
296
294
  const sines = new GrowableFloat64Array_1.GrowableFloat64Array(2);
297
295
  const radians = new GrowableFloat64Array_1.GrowableFloat64Array(2);
298
- // Arc: X(theta) = C + cos(theta)·U + sin(theta)·V
299
- // Line: contains points A0,A1
300
- // Arc point X is colinear (intersects) with line if det(A0, A1, X) = 0 with homogeneous xyw points and vectors
301
- // This leads to
302
- // det(A0, A1, C) + cos(theta) det(A0, A1, U) + sin(theta) det(A0, A1, V) = 0
303
- // or
304
- // alpha + beta cos(theta) + gamma sin(theta) = 0
305
- // solve for theta; evaluate points; project back to line
306
- if (this._worldToLocalPerspective) {
307
- const data = arc.toTransformedPoint4d(this._worldToLocalPerspective);
308
- const pointA0H = this._worldToLocalPerspective.multiplyPoint3d(pointA0, 1);
309
- const pointA1H = this._worldToLocalPerspective.multiplyPoint3d(pointA1, 1);
310
- const alpha = Geometry_1.Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.center);
311
- const beta = Geometry_1.Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.vector0);
312
- const gamma = Geometry_1.Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.vector90);
313
- let numRoots = Polynomials_1.AnalyticRoots.appendImplicitLineUnitCircleIntersections(alpha, beta, gamma, cosines, sines, radians, tol2);
314
- const closeApproach = (0 === numRoots);
315
- if (closeApproach)
316
- numRoots = 1; // arc closest approach is the first entry
317
- let numAccepted = 0;
318
- for (let i = 0; i < numRoots; i++) {
319
- const arcPoint = data.center.plus2Scaled(data.vector0, cosines.atUncheckedIndex(i), data.vector90, sines.atUncheckedIndex(i));
320
- const fArc = data.sweep.radiansToSignedFraction(radians.atUncheckedIndex(i), extendB0);
321
- const fLine = SmallSystem_1.SmallSystem.lineSegment3dHXYClosestPointUnbounded(pointA0H, pointA1H, arcPoint);
322
- if (fLine === undefined)
323
- continue;
324
- if (closeApproach) {
325
- const linePoint = pointA0H.interpolate(fLine, pointA1H);
326
- const dist2 = arcPoint.realDistanceSquaredXY(linePoint);
327
- if (dist2 === undefined || !Geometry_1.Geometry.isDistanceWithinTol(dist2, tol2))
328
- continue;
329
- }
330
- if (this.acceptFraction(extendA0, fLine, extendA1) && this.acceptFraction(extendB0, fArc, extendB1)) {
331
- this.recordPointWithLocalFractions(fLine, cpA, fractionA0, fractionA1, fArc, arc, 0, 1, reversed);
332
- numAccepted++;
333
- }
296
+ const recordIfNearIntersection = (fA, fB, d2) => {
297
+ if (!Geometry_1.Geometry.isDistanceWithinTol(d2, tol2))
298
+ return false;
299
+ this.recordPointWithLocalFractions(fA, cpA, fractionA0, fractionA1, fB, arc, 0, 1, reversed);
300
+ return true;
301
+ };
302
+ const data = arc.toTransformedVectors(this._worldToLocalAffine);
303
+ let pointA0Local = pointA0;
304
+ let pointA1Local = pointA1;
305
+ const pointB0Local = arc.startPoint();
306
+ const pointB1Local = arc.endPoint();
307
+ if (this._worldToLocalAffine) {
308
+ pointA0Local = this._worldToLocalAffine.multiplyPoint3d(pointA0);
309
+ pointA1Local = this._worldToLocalAffine.multiplyPoint3d(pointA1);
310
+ this._worldToLocalAffine.multiplyXYAndZInPlace(pointB0Local);
311
+ this._worldToLocalAffine.multiplyXYAndZInPlace(pointB1Local);
312
+ }
313
+ const alpha = Geometry_1.Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.center, 1);
314
+ const beta = Geometry_1.Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.vector0, 0);
315
+ const gamma = Geometry_1.Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.vector90, 0);
316
+ // note really tight tol for tangency since below we check closest approach also
317
+ let numRoots = Polynomials_1.AnalyticRoots.appendImplicitLineUnitCircleIntersections(alpha, beta, gamma, cosines, sines, radians, tol2);
318
+ const closeApproach = (0 === numRoots);
319
+ if (closeApproach)
320
+ numRoots = 1; // we returned the closest approach instead: first entry is the arc point, which is all we need
321
+ const roots = [];
322
+ for (let i = 0; i < numRoots; i++) {
323
+ const arcPt = data.center.plus2Scaled(data.vector0, cosines.atUncheckedIndex(i), data.vector90, sines.atUncheckedIndex(i));
324
+ const fArc = data.sweep.radiansToSignedFraction(radians.atUncheckedIndex(i), extendB0); // exterior fraction sign based on extend flags
325
+ const fArcNearest01 = data.sweep.fractionToSignedPeriodicFraction(fArc, undefined); // exterior fraction sign based on proximity to 0/1
326
+ const fLine = SmallSystem_1.SmallSystem.lineSegment3dXYClosestPointUnbounded(pointA0Local, pointA1Local, arcPt);
327
+ if (fLine !== undefined) {
328
+ const linePt = pointA0Local.interpolate(fLine, pointA1Local);
329
+ roots.push({ fArc, fArcNearest01, fLine, arcPt, linePt });
334
330
  }
335
- if (numAccepted === 0 && !arc.sweep.isFullCircle) {
336
- // check for endpoint intersection beyond parametric tolerance but within point tolerance
337
- const pointB0H = this._worldToLocalPerspective.multiplyPoint3d(arc.startPoint(), 1);
338
- const pointB1H = this._worldToLocalPerspective.multiplyPoint3d(arc.endPoint(), 1);
339
- const dist2 = [];
340
- let d2 = pointA0H.realDistanceSquaredXY(pointB0H);
341
- if (d2 !== undefined)
342
- dist2.push({ d2, fA: 0, fB: 0 });
343
- d2 = pointA0H.realDistanceSquaredXY(pointB1H);
344
- if (d2 !== undefined)
345
- dist2.push({ d2, fA: 0, fB: 1 });
346
- d2 = pointA1H.realDistanceSquaredXY(pointB0H);
347
- if (d2 !== undefined)
348
- dist2.push({ d2, fA: 1, fB: 0 });
349
- d2 = pointA1H.realDistanceSquaredXY(pointB1H);
350
- if (d2 !== undefined)
351
- dist2.push({ d2, fA: 1, fB: 1 });
352
- dist2.sort((a, b) => a.d2 - b.d2);
353
- if (Geometry_1.Geometry.isDistanceWithinTol(dist2[0].d2, tol2))
354
- this.recordPointWithLocalFractions(dist2[0].fA, cpA, fractionA0, fractionA1, dist2[0].fB, arc, 0, 1, reversed);
331
+ }
332
+ // range-test and record roots
333
+ for (const root of roots) {
334
+ const { fArc, fArcNearest01, fLine, arcPt, linePt } = root;
335
+ if (closeApproach && !Geometry_1.Geometry.isDistanceWithinTol(arcPt.distanceSquaredXY(linePt), tol2)) {
336
+ continue;
337
+ }
338
+ if (this.acceptFraction(extendA0, fLine, extendA1) && this.acceptFraction(extendB0, fArc, extendB1)) {
339
+ this.recordPointWithLocalFractions(fLine, cpA, fractionA0, fractionA1, fArc, arc, 0, 1, reversed);
340
+ continue;
355
341
  }
342
+ // since line-arc chains are so common, look for near-endpoint intersections using distance tol, which is often relatively looser than fraction tol
343
+ const inArcRange = extendB0 || extendB1 || Geometry_1.Geometry.isIn01(fArc);
344
+ const inLineRange = (extendA0 && fLine < 0) || (extendA1 && fLine > 1) || Geometry_1.Geometry.isIn01(fLine);
345
+ let accepted = false;
346
+ if (inArcRange && !inLineRange) // check tangency near line endpoint
347
+ accepted = recordIfNearIntersection(fLine < 0 ? 0 : 1, fArc, (fLine < 0 ? pointA0Local : pointA1Local).distanceSquaredXY(arcPt));
348
+ else if (!inArcRange && inLineRange) // check tangency near arc endpoint
349
+ accepted = recordIfNearIntersection(fLine, fArcNearest01 < 0 ? 0 : 1, linePt.distanceSquaredXY(fArcNearest01 < 0 ? pointB0Local : pointB1Local));
350
+ if (!accepted) // check intersection between nearest endpoints
351
+ recordIfNearIntersection(fLine < 0.5 ? 0 : 1, fArcNearest01 < 0.5 ? 0 : 1, (fLine < 0.5 ? pointA0Local : pointA1Local).distanceSquaredXY(fArcNearest01 < 0.5 ? pointB0Local : pointB1Local));
356
352
  }
357
- else {
358
- const data = arc.toTransformedVectors(this._worldToLocalAffine);
359
- let pointA0Local = pointA0;
360
- let pointA1Local = pointA1;
361
- if (this._worldToLocalAffine) {
362
- pointA0Local = this._worldToLocalAffine.multiplyPoint3d(pointA0);
363
- pointA1Local = this._worldToLocalAffine.multiplyPoint3d(pointA1);
353
+ }
354
+ /** Implementation of dispatchSegmentArc under perspective transform. KEEP IN SYNC WITH computeSegmentArc. */
355
+ computeSegmentArcHomogeneous(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, arc, extendB0, extendB1, reversed) {
356
+ if (!this._worldToLocalPerspective)
357
+ return;
358
+ const tol2 = this._coincidentGeometryContext.tolerance * this._coincidentGeometryContext.tolerance;
359
+ const cosines = new GrowableFloat64Array_1.GrowableFloat64Array(2);
360
+ const sines = new GrowableFloat64Array_1.GrowableFloat64Array(2);
361
+ const radians = new GrowableFloat64Array_1.GrowableFloat64Array(2);
362
+ const recordIfNearIntersection = (fA, fB, d2) => {
363
+ if (d2 === undefined || !Geometry_1.Geometry.isDistanceWithinTol(d2, tol2))
364
+ return false;
365
+ this.recordPointWithLocalFractions(fA, cpA, fractionA0, fractionA1, fB, arc, 0, 1, reversed);
366
+ return true;
367
+ };
368
+ const data = arc.toTransformedPoint4d(this._worldToLocalPerspective);
369
+ const pointA0H = this._worldToLocalPerspective.multiplyPoint3d(pointA0, 1);
370
+ const pointA1H = this._worldToLocalPerspective.multiplyPoint3d(pointA1, 1);
371
+ const pointB0H = this._worldToLocalPerspective.multiplyPoint3d(arc.startPoint(), 1);
372
+ const pointB1H = this._worldToLocalPerspective.multiplyPoint3d(arc.endPoint(), 1);
373
+ const alpha = Geometry_1.Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.center);
374
+ const beta = Geometry_1.Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.vector0);
375
+ const gamma = Geometry_1.Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.vector90);
376
+ // note really tight tol for tangency since below we check closest approach also
377
+ let numRoots = Polynomials_1.AnalyticRoots.appendImplicitLineUnitCircleIntersections(alpha, beta, gamma, cosines, sines, radians, tol2);
378
+ const closeApproach = (0 === numRoots);
379
+ if (closeApproach)
380
+ numRoots = 1; // we returned the closest approach instead: first entry is the arc point, which is all we need
381
+ const roots = [];
382
+ for (let i = 0; i < numRoots; i++) {
383
+ const arcPt = data.center.plus2Scaled(data.vector0, cosines.atUncheckedIndex(i), data.vector90, sines.atUncheckedIndex(i));
384
+ const fArc = data.sweep.radiansToSignedFraction(radians.atUncheckedIndex(i), extendB0); // exterior fraction sign based on extend flags
385
+ const fArcNearest01 = data.sweep.fractionToSignedPeriodicFraction(fArc, undefined); // exterior fraction sign based on proximity to 0/1
386
+ const fLine = SmallSystem_1.SmallSystem.lineSegment3dHXYClosestPointUnbounded(pointA0H, pointA1H, arcPt);
387
+ if (fLine !== undefined) {
388
+ const linePt = pointA0H.interpolate(fLine, pointA1H);
389
+ roots.push({ fArc, fArcNearest01, fLine, arcPt, linePt });
364
390
  }
365
- const alpha = Geometry_1.Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.center, 1);
366
- const beta = Geometry_1.Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.vector0, 0);
367
- const gamma = Geometry_1.Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.vector90, 0);
368
- let numRoots = Polynomials_1.AnalyticRoots.appendImplicitLineUnitCircleIntersections(alpha, beta, gamma, cosines, sines, radians, tol2);
369
- const closeApproach = (0 === numRoots);
370
- if (closeApproach)
371
- numRoots = 1; // arc closest approach is the first entry
372
- let numAccepted = 0;
373
- for (let i = 0; i < numRoots; i++) {
374
- const arcPoint = data.center.plus2Scaled(data.vector0, cosines.atUncheckedIndex(i), data.vector90, sines.atUncheckedIndex(i));
375
- const fArc = data.sweep.radiansToSignedFraction(radians.atUncheckedIndex(i), extendB0);
376
- const fLine = SmallSystem_1.SmallSystem.lineSegment3dXYClosestPointUnbounded(pointA0Local, pointA1Local, arcPoint);
377
- if (fLine === undefined)
391
+ }
392
+ // range-test and record roots
393
+ for (const root of roots) {
394
+ const { fArc, fArcNearest01, fLine, arcPt, linePt } = root;
395
+ if (closeApproach) {
396
+ const dist2 = arcPt.realDistanceSquaredXY(linePt);
397
+ if (dist2 === undefined || !Geometry_1.Geometry.isDistanceWithinTol(dist2, tol2))
378
398
  continue;
379
- if (closeApproach) {
380
- const linePoint = pointA0Local.interpolate(fLine, pointA1Local);
381
- if (!Geometry_1.Geometry.isDistanceWithinTol(arcPoint.distanceSquaredXY(linePoint), tol2))
382
- continue;
383
- }
384
- if (this.acceptFraction(extendA0, fLine, extendA1) && this.acceptFraction(extendB0, fArc, extendB1)) {
385
- this.recordPointWithLocalFractions(fLine, cpA, fractionA0, fractionA1, fArc, arc, 0, 1, reversed);
386
- numAccepted++;
387
- }
388
399
  }
389
- if (numAccepted === 0 && !arc.sweep.isFullCircle) {
390
- // check for endpoint intersection beyond parametric tolerance but within point tolerance
391
- const pointB0Local = arc.startPoint();
392
- const pointB1Local = arc.endPoint();
393
- if (this._worldToLocalAffine) {
394
- this._worldToLocalAffine.multiplyXYAndZInPlace(pointB0Local);
395
- this._worldToLocalAffine.multiplyXYAndZInPlace(pointB1Local);
396
- }
397
- const dist2 = [];
398
- dist2.push({ d2: pointA0Local.distanceSquaredXY(pointB0Local), fA: 0, fB: 0 });
399
- dist2.push({ d2: pointA0Local.distanceSquaredXY(pointB1Local), fA: 0, fB: 1 });
400
- dist2.push({ d2: pointA1Local.distanceSquaredXY(pointB0Local), fA: 1, fB: 0 });
401
- dist2.push({ d2: pointA1Local.distanceSquaredXY(pointB1Local), fA: 1, fB: 1 });
402
- dist2.sort((a, b) => a.d2 - b.d2);
403
- if (Geometry_1.Geometry.isDistanceWithinTol(dist2[0].d2, tol2))
404
- this.recordPointWithLocalFractions(dist2[0].fA, cpA, fractionA0, fractionA1, dist2[0].fB, arc, 0, 1, reversed);
400
+ if (this.acceptFraction(extendA0, fLine, extendA1) && this.acceptFraction(extendB0, fArc, extendB1)) {
401
+ this.recordPointWithLocalFractions(fLine, cpA, fractionA0, fractionA1, fArc, arc, 0, 1, reversed);
402
+ continue;
405
403
  }
404
+ // since line-arc chains are so common, look for near-endpoint intersections using distance tol, which is often relatively looser than fraction tol
405
+ const inArcRange = extendB0 || extendB1 || Geometry_1.Geometry.isIn01(fArc);
406
+ const inLineRange = (extendA0 && fLine < 0) || (extendA1 && fLine > 1) || Geometry_1.Geometry.isIn01(fLine);
407
+ let accepted = false;
408
+ if (inArcRange && !inLineRange) // check tangency near line endpoint
409
+ accepted = recordIfNearIntersection(fLine < 0 ? 0 : 1, fArc, (fLine < 0 ? pointA0H : pointA1H).realDistanceSquaredXY(arcPt));
410
+ else if (!inArcRange && inLineRange) // check tangency near arc endpoint
411
+ accepted = recordIfNearIntersection(fLine, fArcNearest01 < 0 ? 0 : 1, linePt.realDistanceSquaredXY(fArcNearest01 < 0 ? pointB0H : pointB1H));
412
+ if (!accepted) // check intersection between nearest endpoints
413
+ recordIfNearIntersection(fLine < 0.5 ? 0 : 1, fArcNearest01 < 0.5 ? 0 : 1, (fLine < 0.5 ? pointA0H : pointA1H).realDistanceSquaredXY(fArcNearest01 < 0.5 ? pointB0H : pointB1H));
406
414
  }
407
415
  }
416
+ // Caller accesses data from a linestring or segment and passes it here.
417
+ // The line segment in question might be (a) a full line segment or (b) a fragment within a linestring.
418
+ // The fraction and extend parameters allow all combinations to be passed in.
419
+ dispatchSegmentArc(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, arc, extendB0, extendB1, reversed) {
420
+ // Arc: X(theta) = C + cos(theta)·U + sin(theta)·V
421
+ // Line: contains points A0,A1
422
+ // Arc point X is colinear (intersects) with line if det(A0, A1, X) = 0 with homogeneous xyw points and vectors
423
+ // This leads to
424
+ // det(A0, A1, C) + cos(theta) det(A0, A1, U) + sin(theta) det(A0, A1, V) = 0
425
+ // or
426
+ // alpha + beta cos(theta) + gamma sin(theta) = 0
427
+ // solve for theta; evaluate points; project back to line
428
+ if (this._worldToLocalPerspective)
429
+ this.computeSegmentArcHomogeneous(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, arc, extendB0, extendB1, reversed);
430
+ else
431
+ this.computeSegmentArc(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, arc, extendB0, extendB1, reversed);
432
+ }
408
433
  /**
409
434
  * Compute the intersection of two xy-arcs.
410
435
  * * Each matrix has [U V C] in (x,y,w) form from homogeneous projection (local to world).
@@ -899,7 +924,8 @@ class CurveCurveIntersectXY extends GeometryHandler_1.RecurseToCurvesGeometryHan
899
924
  let converged = newtonSearcher.runIterations();
900
925
  const fractionA = newtonSearcher.getU();
901
926
  const fractionB = newtonSearcher.getV();
902
- if (this.acceptFraction(extendA0, fractionA, extendA1) && this.acceptFraction(false, fractionB, false)) {
927
+ // note looser tol for spiral, which can have high order tangency at start/end
928
+ if (this.acceptFraction(extendA0, fractionA, extendA1) && this.acceptFraction(false, fractionB, false, Geometry_1.Geometry.smallFraction)) {
903
929
  const pointA = curveA.fractionToPoint(fractionA, CurveCurveIntersectXY._workPointA0);
904
930
  const pointB = spiralB.fractionToPoint(fractionB, CurveCurveIntersectXY._workPointB0);
905
931
  if (!converged) // Newton may have found close points even if it didn't converge parametrically