@itwin/core-geometry 5.12.0-dev.12 → 5.12.0-dev.13

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.
@@ -122,12 +122,14 @@ export class CurveCurveIntersectXY extends RecurseToCurvesGeometryHandler {
122
122
  return true;
123
123
  }
124
124
  /** Test the fraction by strict parameter, but allow toleranced distance test at ends. */
125
- acceptFractionOnLine(extend0, fraction, extend1, pointA, pointB, tolerance = Geometry.smallMetricDistance) {
125
+ acceptFractionOnLine(extend0, fraction, extend1, pointA, pointB) {
126
126
  if (!extend0 && fraction < 0) {
127
- return Geometry.isDistanceWithinTol(fraction * pointA.distanceXY(pointB), tolerance);
127
+ return Geometry.isDistanceWithinTol(fraction * fraction * pointA.distanceSquaredXY(pointB), this._coincidentGeometryContext.tolerance * this._coincidentGeometryContext.tolerance);
128
+ }
129
+ else if (!extend1 && fraction > 1.0) {
130
+ const f = fraction - 1;
131
+ return Geometry.isDistanceWithinTol(f * f * pointA.distanceSquaredXY(pointB), this._coincidentGeometryContext.tolerance * this._coincidentGeometryContext.tolerance);
128
132
  }
129
- else if (!extend1 && fraction > 1.0)
130
- return Geometry.isDistanceWithinTol((fraction - 1.0) * pointA.distanceXY(pointB), tolerance);
131
133
  return true;
132
134
  }
133
135
  /**
@@ -141,7 +143,7 @@ export class CurveCurveIntersectXY extends RecurseToCurvesGeometryHandler {
141
143
  return result;
142
144
  }
143
145
  /**
144
- * Record the pre-computed intersection between two curves. Filter by extension rules. Record with fraction mapping.
146
+ * Record the pre-computed intersection between two curves. Record with fraction mapping.
145
147
  * @param localFractionA intersection fraction local to the subcurve of cpA between fractionA0 and fractionA1
146
148
  * @param cpA the first curve
147
149
  * @param fractionA0 start of the subcurve of cpA
@@ -221,8 +223,7 @@ export class CurveCurveIntersectXY extends RecurseToCurvesGeometryHandler {
221
223
  computeSegmentSegment3D(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, cpB, extendB0, pointB0, fractionB0, pointB1, fractionB1, extendB1, reversed) {
222
224
  const aDir = { x: pointA1.x - pointA0.x, y: pointA1.y - pointA0.y };
223
225
  const bDir = { x: pointB1.x - pointB0.x, y: pointB1.y - pointB0.y };
224
- const tol = this._coincidentGeometryContext.tolerance;
225
- const fractions = SmallSystem.lineSegmentXYUVIntersectionUnbounded(pointA0, aDir, pointB0, bDir, tol);
226
+ const fractions = SmallSystem.lineSegmentXYUVIntersectionUnbounded(pointA0, aDir, pointB0, bDir, this._coincidentGeometryContext.tolerance);
226
227
  if (!fractions)
227
228
  return;
228
229
  if (fractions.f1) { // the lines are coincident
@@ -235,8 +236,8 @@ export class CurveCurveIntersectXY extends RecurseToCurvesGeometryHandler {
235
236
  this.recordPointWithLocalFractions(overlap.detailA.fraction, cpA, fractionA0, fractionA1, overlap.detailB.fraction, cpB, fractionB0, fractionB1, reversed, overlap);
236
237
  }
237
238
  else { // the lines have a transverse intersection
238
- if (this.acceptFractionOnLine(extendA0, fractions.f0.x, extendA1, pointA0, pointA1, tol)) {
239
- if (this.acceptFractionOnLine(extendB0, fractions.f0.y, extendB1, pointB0, pointB1, tol))
239
+ if (this.acceptFractionOnLine(extendA0, fractions.f0.x, extendA1, pointA0, pointA1)) {
240
+ if (this.acceptFractionOnLine(extendB0, fractions.f0.y, extendB1, pointB0, pointB1))
240
241
  this.recordPointWithLocalFractions(fractions.f0.x, cpA, fractionA0, fractionA1, fractions.f0.y, cpB, fractionB0, fractionB1, reversed);
241
242
  }
242
243
  }
@@ -301,10 +302,6 @@ export class CurveCurveIntersectXY extends RecurseToCurvesGeometryHandler {
301
302
  // solve for theta; evaluate points; project back to line
302
303
  if (this._worldToLocalPerspective) {
303
304
  const data = arc.toTransformedPoint4d(this._worldToLocalPerspective);
304
- const radians0 = data.sweep.fractionToRadians(0);
305
- const pointB0H = data.center.plus2Scaled(data.vector0, Math.cos(radians0), data.vector90, Math.sin(radians0));
306
- const radians1 = data.sweep.fractionToRadians(1);
307
- const pointB1H = data.center.plus2Scaled(data.vector0, Math.cos(radians1), data.vector90, Math.sin(radians1));
308
305
  const pointA0H = this._worldToLocalPerspective.multiplyPoint3d(pointA0, 1);
309
306
  const pointA1H = this._worldToLocalPerspective.multiplyPoint3d(pointA1, 1);
310
307
  const alpha = Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.center);
@@ -313,35 +310,49 @@ export class CurveCurveIntersectXY extends RecurseToCurvesGeometryHandler {
313
310
  let numRoots = AnalyticRoots.appendImplicitLineUnitCircleIntersections(alpha, beta, gamma, cosines, sines, radians, tol2);
314
311
  const closeApproach = (0 === numRoots);
315
312
  if (closeApproach)
316
- numRoots = 1; // we returned the arc's closest approach as the first "root"; if within tolerance and at endpoints, we record it
317
- const acceptSolution = (iRoot, checkOnlyEndPointDistance = false) => {
318
- const arcPoint = data.center.plus2Scaled(data.vector0, cosines.atUncheckedIndex(iRoot), data.vector90, sines.atUncheckedIndex(iRoot));
319
- let fArc = data.sweep.radiansToSignedFraction(radians.atUncheckedIndex(iRoot), extendB0);
320
- let fLine = SmallSystem.lineSegment3dHXYClosestPointUnbounded(pointA0H, pointA1H, arcPoint);
321
- if (fLine === undefined)
322
- return undefined;
323
- if (!checkOnlyEndPointDistance && this.acceptFraction(extendA0, fLine, extendA1) && this.acceptFraction(extendB0, fArc, extendB1))
324
- return { fLine, fArc };
325
- // check for an endpoint intersection that is beyond parametric tolerance but within point tolerance
326
- fLine = fLine < 0.5 ? 0 : 1;
327
- fArc = data.sweep.fractionToSignedPeriodicFraction(fArc) < 0.5 ? 0 : 1;
328
- const pointAH = fLine ? pointA1H : pointA0H;
329
- const pointBH = fArc ? pointB1H : pointB0H;
330
- const dist2 = pointAH.realDistanceSquaredXY(pointBH);
331
- return (dist2 !== undefined && Geometry.isDistanceWithinTol(dist2, tol2)) ? { fLine, fArc } : undefined;
332
- };
313
+ numRoots = 1; // arc closest approach is the first entry
314
+ let numAccepted = 0;
333
315
  for (let i = 0; i < numRoots; i++) {
334
- const result = acceptSolution(i, closeApproach);
335
- if (result)
336
- this.recordPointWithLocalFractions(result.fLine, cpA, fractionA0, fractionA1, result.fArc, arc, 0, 1, reversed);
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
+ }
331
+ }
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);
337
352
  }
338
353
  }
339
354
  else {
340
355
  const data = arc.toTransformedVectors(this._worldToLocalAffine);
341
- const radians0 = data.sweep.fractionToRadians(0);
342
- const pointB0Local = data.center.plus2Scaled(data.vector0, Math.cos(radians0), data.vector90, Math.sin(radians0));
343
- const radians1 = data.sweep.fractionToRadians(1);
344
- const pointB1Local = data.center.plus2Scaled(data.vector0, Math.cos(radians1), data.vector90, Math.sin(radians1));
345
356
  let pointA0Local = pointA0;
346
357
  let pointA1Local = pointA1;
347
358
  if (this._worldToLocalAffine) {
@@ -354,27 +365,40 @@ export class CurveCurveIntersectXY extends RecurseToCurvesGeometryHandler {
354
365
  let numRoots = AnalyticRoots.appendImplicitLineUnitCircleIntersections(alpha, beta, gamma, cosines, sines, radians, tol2);
355
366
  const closeApproach = (0 === numRoots);
356
367
  if (closeApproach)
357
- numRoots = 1; // we returned the arc's closest approach as the first "root"; if within tolerance and at endpoints, we record it
358
- const acceptSolution = (iRoot, checkOnlyEndPointDistance = false) => {
359
- const arcPoint = data.center.plus2Scaled(data.vector0, cosines.atUncheckedIndex(iRoot), data.vector90, sines.atUncheckedIndex(iRoot));
360
- let fArc = data.sweep.radiansToSignedFraction(radians.atUncheckedIndex(iRoot), extendB0);
361
- let fLine = SmallSystem.lineSegment3dXYClosestPointUnbounded(pointA0Local, pointA1Local, arcPoint);
362
- if (fLine === undefined)
363
- return undefined;
364
- if (!checkOnlyEndPointDistance && this.acceptFraction(extendA0, fLine, extendA1) && this.acceptFraction(extendB0, fArc, extendB1))
365
- return { fLine, fArc };
366
- // check for an endpoint intersection that is beyond parametric tolerance but within point tolerance
367
- fLine = fLine < 0.5 ? 0 : 1;
368
- fArc = data.sweep.fractionToSignedPeriodicFraction(fArc) < 0.5 ? 0 : 1;
369
- const pointALocal = fLine ? pointA1Local : pointA0Local;
370
- const pointBLocal = fArc ? pointB1Local : pointB0Local;
371
- const dist2 = pointALocal.distanceSquaredXY(pointBLocal);
372
- return Geometry.isDistanceWithinTol(dist2, tol2) ? { fLine, fArc } : undefined;
373
- };
368
+ numRoots = 1; // arc closest approach is the first entry
369
+ let numAccepted = 0;
374
370
  for (let i = 0; i < numRoots; i++) {
375
- const result = acceptSolution(i, closeApproach);
376
- if (result)
377
- this.recordPointWithLocalFractions(result.fLine, cpA, fractionA0, fractionA1, result.fArc, arc, 0, 1, reversed);
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)
375
+ 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
+ }
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);
378
402
  }
379
403
  }
380
404
  }