@itwin/core-geometry 4.9.0-dev.12 → 4.9.0-dev.14

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 (202) hide show
  1. package/CHANGELOG.md +6 -1
  2. package/lib/cjs/Geometry.d.ts +57 -46
  3. package/lib/cjs/Geometry.d.ts.map +1 -1
  4. package/lib/cjs/Geometry.js +73 -53
  5. package/lib/cjs/Geometry.js.map +1 -1
  6. package/lib/cjs/curve/Arc3d.d.ts +128 -34
  7. package/lib/cjs/curve/Arc3d.d.ts.map +1 -1
  8. package/lib/cjs/curve/Arc3d.js +174 -20
  9. package/lib/cjs/curve/Arc3d.js.map +1 -1
  10. package/lib/cjs/curve/CurveCollection.d.ts +2 -1
  11. package/lib/cjs/curve/CurveCollection.d.ts.map +1 -1
  12. package/lib/cjs/curve/CurveCollection.js +2 -1
  13. package/lib/cjs/curve/CurveCollection.js.map +1 -1
  14. package/lib/cjs/curve/CurveLocationDetail.d.ts +19 -1
  15. package/lib/cjs/curve/CurveLocationDetail.d.ts.map +1 -1
  16. package/lib/cjs/curve/CurveLocationDetail.js +39 -0
  17. package/lib/cjs/curve/CurveLocationDetail.js.map +1 -1
  18. package/lib/cjs/curve/LineString3d.js +1 -1
  19. package/lib/cjs/curve/LineString3d.js.map +1 -1
  20. package/lib/cjs/curve/OffsetOptions.d.ts +1 -1
  21. package/lib/cjs/curve/OffsetOptions.js +1 -1
  22. package/lib/cjs/curve/OffsetOptions.js.map +1 -1
  23. package/lib/cjs/curve/RegionOps.d.ts +2 -1
  24. package/lib/cjs/curve/RegionOps.d.ts.map +1 -1
  25. package/lib/cjs/curve/RegionOps.js +2 -1
  26. package/lib/cjs/curve/RegionOps.js.map +1 -1
  27. package/lib/cjs/curve/internalContexts/CurveCurveCloseApproachXY.d.ts +23 -7
  28. package/lib/cjs/curve/internalContexts/CurveCurveCloseApproachXY.d.ts.map +1 -1
  29. package/lib/cjs/curve/internalContexts/CurveCurveCloseApproachXY.js +43 -35
  30. package/lib/cjs/curve/internalContexts/CurveCurveCloseApproachXY.js.map +1 -1
  31. package/lib/cjs/curve/internalContexts/EllipticalArcApproximationContext.d.ts +211 -0
  32. package/lib/cjs/curve/internalContexts/EllipticalArcApproximationContext.d.ts.map +1 -0
  33. package/lib/cjs/curve/internalContexts/EllipticalArcApproximationContext.js +1000 -0
  34. package/lib/cjs/curve/internalContexts/EllipticalArcApproximationContext.js.map +1 -0
  35. package/lib/cjs/geometry3d/Angle.d.ts +18 -5
  36. package/lib/cjs/geometry3d/Angle.d.ts.map +1 -1
  37. package/lib/cjs/geometry3d/Angle.js +23 -7
  38. package/lib/cjs/geometry3d/Angle.js.map +1 -1
  39. package/lib/cjs/geometry3d/AngleSweep.d.ts +14 -1
  40. package/lib/cjs/geometry3d/AngleSweep.d.ts.map +1 -1
  41. package/lib/cjs/geometry3d/AngleSweep.js +47 -12
  42. package/lib/cjs/geometry3d/AngleSweep.js.map +1 -1
  43. package/lib/cjs/geometry3d/Matrix3d.d.ts +6 -4
  44. package/lib/cjs/geometry3d/Matrix3d.d.ts.map +1 -1
  45. package/lib/cjs/geometry3d/Matrix3d.js +6 -4
  46. package/lib/cjs/geometry3d/Matrix3d.js.map +1 -1
  47. package/lib/cjs/geometry3d/Point3dVector3d.d.ts +2 -3
  48. package/lib/cjs/geometry3d/Point3dVector3d.d.ts.map +1 -1
  49. package/lib/cjs/geometry3d/Point3dVector3d.js +2 -3
  50. package/lib/cjs/geometry3d/Point3dVector3d.js.map +1 -1
  51. package/lib/cjs/geometry3d/PointHelpers.d.ts +6 -5
  52. package/lib/cjs/geometry3d/PointHelpers.d.ts.map +1 -1
  53. package/lib/cjs/geometry3d/PointHelpers.js +11 -10
  54. package/lib/cjs/geometry3d/PointHelpers.js.map +1 -1
  55. package/lib/cjs/geometry3d/Range.d.ts +6 -1
  56. package/lib/cjs/geometry3d/Range.d.ts.map +1 -1
  57. package/lib/cjs/geometry3d/Range.js +9 -3
  58. package/lib/cjs/geometry3d/Range.js.map +1 -1
  59. package/lib/cjs/geometry3d/Transform.d.ts +1 -1
  60. package/lib/cjs/geometry3d/Transform.js +1 -1
  61. package/lib/cjs/geometry3d/Transform.js.map +1 -1
  62. package/lib/cjs/numerics/Newton.d.ts +3 -3
  63. package/lib/cjs/numerics/Newton.d.ts.map +1 -1
  64. package/lib/cjs/numerics/Newton.js +14 -16
  65. package/lib/cjs/numerics/Newton.js.map +1 -1
  66. package/lib/cjs/numerics/Polynomials.d.ts +2 -2
  67. package/lib/cjs/numerics/Polynomials.js +2 -2
  68. package/lib/cjs/numerics/Polynomials.js.map +1 -1
  69. package/lib/cjs/polyface/PolyfaceBuilder.d.ts +7 -4
  70. package/lib/cjs/polyface/PolyfaceBuilder.d.ts.map +1 -1
  71. package/lib/cjs/polyface/PolyfaceBuilder.js +8 -4
  72. package/lib/cjs/polyface/PolyfaceBuilder.js.map +1 -1
  73. package/lib/cjs/polyface/PolyfaceQuery.d.ts +3 -3
  74. package/lib/cjs/polyface/PolyfaceQuery.js +3 -3
  75. package/lib/cjs/polyface/PolyfaceQuery.js.map +1 -1
  76. package/lib/cjs/serialization/BGFBReader.js.map +1 -1
  77. package/lib/cjs/serialization/BGFBWriter.js +2 -2
  78. package/lib/cjs/serialization/BGFBWriter.js.map +1 -1
  79. package/lib/cjs/serialization/GeometrySamples.js.map +1 -1
  80. package/lib/cjs/topology/Graph.d.ts +1 -1
  81. package/lib/cjs/topology/Graph.js +2 -2
  82. package/lib/cjs/topology/Graph.js.map +1 -1
  83. package/lib/cjs/topology/HalfEdgeNodeXYZUV.d.ts +1 -1
  84. package/lib/cjs/topology/HalfEdgeNodeXYZUV.js +1 -1
  85. package/lib/cjs/topology/HalfEdgeNodeXYZUV.js.map +1 -1
  86. package/lib/cjs/topology/HalfEdgePointInGraphSearch.d.ts +57 -15
  87. package/lib/cjs/topology/HalfEdgePointInGraphSearch.d.ts.map +1 -1
  88. package/lib/cjs/topology/HalfEdgePointInGraphSearch.js +168 -127
  89. package/lib/cjs/topology/HalfEdgePointInGraphSearch.js.map +1 -1
  90. package/lib/cjs/topology/HalfEdgePositionDetail.d.ts +35 -35
  91. package/lib/cjs/topology/HalfEdgePositionDetail.d.ts.map +1 -1
  92. package/lib/cjs/topology/HalfEdgePositionDetail.js +63 -41
  93. package/lib/cjs/topology/HalfEdgePositionDetail.js.map +1 -1
  94. package/lib/cjs/topology/InsertAndRetriangulateContext.d.ts +64 -12
  95. package/lib/cjs/topology/InsertAndRetriangulateContext.d.ts.map +1 -1
  96. package/lib/cjs/topology/InsertAndRetriangulateContext.js +174 -75
  97. package/lib/cjs/topology/InsertAndRetriangulateContext.js.map +1 -1
  98. package/lib/cjs/topology/Triangulation.d.ts +16 -10
  99. package/lib/cjs/topology/Triangulation.d.ts.map +1 -1
  100. package/lib/cjs/topology/Triangulation.js +23 -30
  101. package/lib/cjs/topology/Triangulation.js.map +1 -1
  102. package/lib/esm/Geometry.d.ts +57 -46
  103. package/lib/esm/Geometry.d.ts.map +1 -1
  104. package/lib/esm/Geometry.js +73 -53
  105. package/lib/esm/Geometry.js.map +1 -1
  106. package/lib/esm/curve/Arc3d.d.ts +128 -34
  107. package/lib/esm/curve/Arc3d.d.ts.map +1 -1
  108. package/lib/esm/curve/Arc3d.js +172 -19
  109. package/lib/esm/curve/Arc3d.js.map +1 -1
  110. package/lib/esm/curve/CurveCollection.d.ts +2 -1
  111. package/lib/esm/curve/CurveCollection.d.ts.map +1 -1
  112. package/lib/esm/curve/CurveCollection.js +2 -1
  113. package/lib/esm/curve/CurveCollection.js.map +1 -1
  114. package/lib/esm/curve/CurveLocationDetail.d.ts +19 -1
  115. package/lib/esm/curve/CurveLocationDetail.d.ts.map +1 -1
  116. package/lib/esm/curve/CurveLocationDetail.js +39 -0
  117. package/lib/esm/curve/CurveLocationDetail.js.map +1 -1
  118. package/lib/esm/curve/LineString3d.js +1 -1
  119. package/lib/esm/curve/LineString3d.js.map +1 -1
  120. package/lib/esm/curve/OffsetOptions.d.ts +1 -1
  121. package/lib/esm/curve/OffsetOptions.js +1 -1
  122. package/lib/esm/curve/OffsetOptions.js.map +1 -1
  123. package/lib/esm/curve/RegionOps.d.ts +2 -1
  124. package/lib/esm/curve/RegionOps.d.ts.map +1 -1
  125. package/lib/esm/curve/RegionOps.js +2 -1
  126. package/lib/esm/curve/RegionOps.js.map +1 -1
  127. package/lib/esm/curve/internalContexts/CurveCurveCloseApproachXY.d.ts +23 -7
  128. package/lib/esm/curve/internalContexts/CurveCurveCloseApproachXY.d.ts.map +1 -1
  129. package/lib/esm/curve/internalContexts/CurveCurveCloseApproachXY.js +43 -35
  130. package/lib/esm/curve/internalContexts/CurveCurveCloseApproachXY.js.map +1 -1
  131. package/lib/esm/curve/internalContexts/EllipticalArcApproximationContext.d.ts +211 -0
  132. package/lib/esm/curve/internalContexts/EllipticalArcApproximationContext.d.ts.map +1 -0
  133. package/lib/esm/curve/internalContexts/EllipticalArcApproximationContext.js +995 -0
  134. package/lib/esm/curve/internalContexts/EllipticalArcApproximationContext.js.map +1 -0
  135. package/lib/esm/geometry3d/Angle.d.ts +18 -5
  136. package/lib/esm/geometry3d/Angle.d.ts.map +1 -1
  137. package/lib/esm/geometry3d/Angle.js +23 -7
  138. package/lib/esm/geometry3d/Angle.js.map +1 -1
  139. package/lib/esm/geometry3d/AngleSweep.d.ts +14 -1
  140. package/lib/esm/geometry3d/AngleSweep.d.ts.map +1 -1
  141. package/lib/esm/geometry3d/AngleSweep.js +47 -12
  142. package/lib/esm/geometry3d/AngleSweep.js.map +1 -1
  143. package/lib/esm/geometry3d/Matrix3d.d.ts +6 -4
  144. package/lib/esm/geometry3d/Matrix3d.d.ts.map +1 -1
  145. package/lib/esm/geometry3d/Matrix3d.js +6 -4
  146. package/lib/esm/geometry3d/Matrix3d.js.map +1 -1
  147. package/lib/esm/geometry3d/Point3dVector3d.d.ts +2 -3
  148. package/lib/esm/geometry3d/Point3dVector3d.d.ts.map +1 -1
  149. package/lib/esm/geometry3d/Point3dVector3d.js +2 -3
  150. package/lib/esm/geometry3d/Point3dVector3d.js.map +1 -1
  151. package/lib/esm/geometry3d/PointHelpers.d.ts +6 -5
  152. package/lib/esm/geometry3d/PointHelpers.d.ts.map +1 -1
  153. package/lib/esm/geometry3d/PointHelpers.js +11 -10
  154. package/lib/esm/geometry3d/PointHelpers.js.map +1 -1
  155. package/lib/esm/geometry3d/Range.d.ts +6 -1
  156. package/lib/esm/geometry3d/Range.d.ts.map +1 -1
  157. package/lib/esm/geometry3d/Range.js +9 -3
  158. package/lib/esm/geometry3d/Range.js.map +1 -1
  159. package/lib/esm/geometry3d/Transform.d.ts +1 -1
  160. package/lib/esm/geometry3d/Transform.js +1 -1
  161. package/lib/esm/geometry3d/Transform.js.map +1 -1
  162. package/lib/esm/numerics/Newton.d.ts +3 -3
  163. package/lib/esm/numerics/Newton.d.ts.map +1 -1
  164. package/lib/esm/numerics/Newton.js +14 -16
  165. package/lib/esm/numerics/Newton.js.map +1 -1
  166. package/lib/esm/numerics/Polynomials.d.ts +2 -2
  167. package/lib/esm/numerics/Polynomials.js +2 -2
  168. package/lib/esm/numerics/Polynomials.js.map +1 -1
  169. package/lib/esm/polyface/PolyfaceBuilder.d.ts +7 -4
  170. package/lib/esm/polyface/PolyfaceBuilder.d.ts.map +1 -1
  171. package/lib/esm/polyface/PolyfaceBuilder.js +8 -4
  172. package/lib/esm/polyface/PolyfaceBuilder.js.map +1 -1
  173. package/lib/esm/polyface/PolyfaceQuery.d.ts +3 -3
  174. package/lib/esm/polyface/PolyfaceQuery.js +3 -3
  175. package/lib/esm/polyface/PolyfaceQuery.js.map +1 -1
  176. package/lib/esm/serialization/BGFBReader.js.map +1 -1
  177. package/lib/esm/serialization/BGFBWriter.js +2 -2
  178. package/lib/esm/serialization/BGFBWriter.js.map +1 -1
  179. package/lib/esm/serialization/GeometrySamples.js.map +1 -1
  180. package/lib/esm/topology/Graph.d.ts +1 -1
  181. package/lib/esm/topology/Graph.js +2 -2
  182. package/lib/esm/topology/Graph.js.map +1 -1
  183. package/lib/esm/topology/HalfEdgeNodeXYZUV.d.ts +1 -1
  184. package/lib/esm/topology/HalfEdgeNodeXYZUV.js +1 -1
  185. package/lib/esm/topology/HalfEdgeNodeXYZUV.js.map +1 -1
  186. package/lib/esm/topology/HalfEdgePointInGraphSearch.d.ts +57 -15
  187. package/lib/esm/topology/HalfEdgePointInGraphSearch.d.ts.map +1 -1
  188. package/lib/esm/topology/HalfEdgePointInGraphSearch.js +168 -127
  189. package/lib/esm/topology/HalfEdgePointInGraphSearch.js.map +1 -1
  190. package/lib/esm/topology/HalfEdgePositionDetail.d.ts +35 -35
  191. package/lib/esm/topology/HalfEdgePositionDetail.d.ts.map +1 -1
  192. package/lib/esm/topology/HalfEdgePositionDetail.js +63 -41
  193. package/lib/esm/topology/HalfEdgePositionDetail.js.map +1 -1
  194. package/lib/esm/topology/InsertAndRetriangulateContext.d.ts +64 -12
  195. package/lib/esm/topology/InsertAndRetriangulateContext.d.ts.map +1 -1
  196. package/lib/esm/topology/InsertAndRetriangulateContext.js +173 -74
  197. package/lib/esm/topology/InsertAndRetriangulateContext.js.map +1 -1
  198. package/lib/esm/topology/Triangulation.d.ts +16 -10
  199. package/lib/esm/topology/Triangulation.d.ts.map +1 -1
  200. package/lib/esm/topology/Triangulation.js +24 -31
  201. package/lib/esm/topology/Triangulation.js.map +1 -1
  202. package/package.json +3 -3
@@ -0,0 +1,995 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license terms and full copyright notice.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ import { assert, OrderedSet, SortedArray } from "@itwin/core-bentley";
6
+ import { Geometry } from "../../Geometry";
7
+ import { Angle } from "../../geometry3d/Angle";
8
+ import { AngleSweep } from "../../geometry3d/AngleSweep";
9
+ import { Point3d, Vector3d } from "../../geometry3d/Point3dVector3d";
10
+ import { Range1d } from "../../geometry3d/Range";
11
+ import { Ray3d } from "../../geometry3d/Ray3d";
12
+ import { Transform } from "../../geometry3d/Transform";
13
+ import { Arc3d, EllipticalArcApproximationOptions, EllipticalArcSampleMethod } from "../Arc3d";
14
+ import { Loop } from "../Loop";
15
+ import { Path } from "../Path";
16
+ import { CurveCurveCloseApproachXY } from "./CurveCurveCloseApproachXY";
17
+ /** @packageDocumentation
18
+ * @module Curve
19
+ */
20
+ /**
21
+ * Comparison callback to sort fractions in increasing order with suitable tolerance for equality.
22
+ * @internal
23
+ */
24
+ function compareFractionsIncreasing(f0, f1) {
25
+ if (Geometry.isAlmostEqualNumber(f0, f1, Geometry.smallFraction))
26
+ return 0;
27
+ return f0 < f1 ? -1 : 1;
28
+ }
29
+ ;
30
+ /**
31
+ * Comparison callback to sort fractions in decreasing order with suitable tolerance for equality.
32
+ * @internal
33
+ */
34
+ function compareFractionsDecreasing(f0, f1) {
35
+ if (Geometry.isAlmostEqualNumber(f0, f1, Geometry.smallFraction))
36
+ return 0;
37
+ return f0 < f1 ? 1 : -1;
38
+ }
39
+ ;
40
+ /**
41
+ * Structured data carrier used by the elliptical arc sampler.
42
+ * @internal
43
+ */
44
+ export class QuadrantFractions {
45
+ constructor(quadrant, fractions, interpolateStartTangent, interpolateEndTangent) {
46
+ this._quadrant = quadrant;
47
+ this._fractions = fractions;
48
+ this._interpolateStartTangent = interpolateStartTangent;
49
+ this._interpolateEndTangent = interpolateEndTangent;
50
+ this._averageAdded = false;
51
+ }
52
+ /** Constructor, captures the array. */
53
+ static create(quadrant, fractions = [], interpolateStartTangent = true, interpolateEndTangent = true) {
54
+ return new QuadrantFractions(quadrant, fractions, interpolateStartTangent, interpolateEndTangent);
55
+ }
56
+ /**
57
+ * Quadrant of the full ellipse containing the samples.
58
+ * * Quadrants are labeled proceeding in counterclockwise angular sweeps of length pi/2 starting at vector0.
59
+ * * For example, Quadrant 1 starts at vector0 and ends at vector90, and Quadrant 4 ends at vector0.
60
+ * * For purposes of angle classification, quadrants are half-open intervals, closed at their start angle,
61
+ * as determined by the ellipse's sweep direction.
62
+ */
63
+ get quadrant() {
64
+ return this._quadrant;
65
+ }
66
+ /** Sample locations in this quadrant of the elliptical arc, as fractions of its sweep. */
67
+ get fractions() {
68
+ return this._fractions;
69
+ }
70
+ set fractions(f) {
71
+ this._fractions = f;
72
+ }
73
+ /**
74
+ * Whether to interpolate the elliptical arc tangent at the first fraction.
75
+ * * If true (default), the first approximating arc is computed from the first two fractions and approximates the
76
+ * elliptical arc between the first and second fractions, interpolating both point and tangent at the first fraction,
77
+ * and point at the second fraction.
78
+ * * If false, the first approximating arc is computed from the first three fractions and approximates the
79
+ * elliptical arc between the second and third fractions, where it interpolates position only.
80
+ */
81
+ get interpolateStartTangent() {
82
+ return this._interpolateStartTangent;
83
+ }
84
+ set interpolateStartTangent(interpolate) {
85
+ this._interpolateStartTangent = interpolate;
86
+ }
87
+ /**
88
+ * Whether to interpolate the elliptical arc tangent at the last fraction.
89
+ * * If true (default), the last approximating arc is computed from the last two fractions and approximates the
90
+ * elliptical arc between the penultimate and last fractions, interpolating point at the penultimate fraction, and
91
+ * both point and tangent at the last fraction.
92
+ * * If false, the last approximating arc is computed from the last three fractions and approximates the
93
+ * elliptical arc between the penultimate and last fractions, where it interpolates position only.
94
+ */
95
+ get interpolateEndTangent() {
96
+ return this._interpolateEndTangent;
97
+ }
98
+ set interpolateEndTangent(interpolate) {
99
+ this._interpolateEndTangent = interpolate;
100
+ }
101
+ /**
102
+ * Whether the average of the first and last fractions was added to satisfy a minimum fractions array length of three.
103
+ * * There are always at least two fractions per quadrant, but three are needed to interpolate both end tangents
104
+ * with circular arcs.
105
+ * * This flag is set if a given sample method/arc yielded only two fractions, so their average was inserted in the
106
+ * fractions array to meet this minimum three-sample requirement.
107
+ */
108
+ get averageAdded() {
109
+ return this._fractions.length === 3 ? this._averageAdded : false;
110
+ }
111
+ set averageAdded(added) {
112
+ this._averageAdded = added;
113
+ }
114
+ /**
115
+ * Compute quadrant data for the given angles.
116
+ * @param radians0 first radian angle
117
+ * @param radians1 second radian angle
118
+ * @return quadrant number and start/end radian angles for the quadrant that contains both input angles, or
119
+ * undefined if no such quadrant.
120
+ * * The returned sweep is always counterclockwise: angle0 < angle1.
121
+ */
122
+ static getQuadrantRadians(radians0, radians1) {
123
+ if (AngleSweep.isRadiansInStartEnd(radians0, 0, Angle.piOver2Radians)
124
+ && AngleSweep.isRadiansInStartEnd(radians1, 0, Angle.piOver2Radians))
125
+ return { quadrant: 1, angle0: 0, angle1: Angle.piOver2Radians };
126
+ if (AngleSweep.isRadiansInStartEnd(radians0, Angle.piOver2Radians, Angle.piRadians)
127
+ && AngleSweep.isRadiansInStartEnd(radians1, Angle.piOver2Radians, Angle.piRadians))
128
+ return { quadrant: 2, angle0: Angle.piOver2Radians, angle1: Angle.piRadians };
129
+ if (AngleSweep.isRadiansInStartEnd(radians0, Angle.piRadians, Angle.pi3Over2Radians)
130
+ && AngleSweep.isRadiansInStartEnd(radians1, Angle.piRadians, Angle.pi3Over2Radians))
131
+ return { quadrant: 3, angle0: Angle.piRadians, angle1: Angle.pi3Over2Radians };
132
+ if (AngleSweep.isRadiansInStartEnd(radians0, Angle.pi3Over2Radians, Angle.pi2Radians)
133
+ && AngleSweep.isRadiansInStartEnd(radians1, Angle.pi3Over2Radians, Angle.pi2Radians))
134
+ return { quadrant: 4, angle0: Angle.pi3Over2Radians, angle1: Angle.pi2Radians };
135
+ return undefined;
136
+ }
137
+ /** Compute the fractional range of Quadrant 1 for the given sweep. */
138
+ static getQ1FractionalRange(sweep) {
139
+ const angle0 = 0;
140
+ const angle90 = 0.5 * Math.PI;
141
+ let f0 = sweep.radiansToSignedPeriodicFraction(angle0);
142
+ let f1 = sweep.radiansToSignedPeriodicFraction(angle90);
143
+ if (!sweep.isCCW)
144
+ [f0, f1] = [f1, f0];
145
+ if (f1 < f0)
146
+ f1 += 1;
147
+ return Range1d.createXX(f0, f1);
148
+ }
149
+ /** Reverse the fractions array and flags. */
150
+ reverse() {
151
+ this._fractions.reverse();
152
+ [this._interpolateStartTangent, this._interpolateEndTangent] = [this._interpolateEndTangent, this._interpolateStartTangent];
153
+ }
154
+ }
155
+ ;
156
+ /**
157
+ * Base class specifying callbacks for processing samples of an elliptical arc.
158
+ * @internal
159
+ */
160
+ class QuadrantFractionsProcessor {
161
+ /**
162
+ * Announce the beginning of processing for quadrant `q`.
163
+ * @param _reversed whether `q.reverse()` was invoked before this call for symmetry reasons. If so, arcs will be
164
+ * announced in the opposite order and with the opposite orientation.
165
+ * @return whether to process `q`
166
+ */
167
+ announceQuadrantBegin(_q, _reversed) { return true; }
168
+ /**
169
+ * Announce a circular arc approximating the elliptical arc E between the given fractions.
170
+ * * The given fractions are different. If `announceQuadrantBegin` was invoked with `reversed === false` then
171
+ * `fPrev < f0 < f1`; otherwise, `fPrev > f0 > f1`.
172
+ * @param _arc circular arc that interpolates E at f0 and f1. Processor can capture `arc`; it is unused afterwards.
173
+ * @param _fPrev fractional parameter of E used to define the 3-point parent circle through the points at `fPrev`,
174
+ * `f0`, and `f1` from which `arc` was constructed. If undefined, `arc` was generated from the circle defined by
175
+ * the points at `f0` and `f1` and one of their tangents.
176
+ * @param _f0 fractional parameter of E at which point `arc` starts
177
+ * @param _f1 fractional parameter of E at which point `arc` ends
178
+ */
179
+ announceArc(_arc, _fPrev, _f0, _f1) { }
180
+ /**
181
+ * Announce the end of processing for quadrant `q`.
182
+ * @param _reversed whether `q.reverse()` was invoked before processing (see [[announceQuadrantBegin]]). If so,
183
+ * after this call `q.reverse()` is invoked again.
184
+ */
185
+ announceQuadrantEnd(_q, _reversed) { }
186
+ }
187
+ ;
188
+ /**
189
+ * Processor for computing the error of a sample-based arc chain approximation.
190
+ * @internal
191
+ */
192
+ class ArcChainErrorProcessor extends QuadrantFractionsProcessor {
193
+ constructor(ellipticalArc) {
194
+ super();
195
+ this._ellipticalArc = ellipticalArc;
196
+ this._maxPerpendicular = undefined;
197
+ }
198
+ static create(ellipticalArc) {
199
+ return new ArcChainErrorProcessor(ellipticalArc);
200
+ }
201
+ get ellipticalArc() {
202
+ return this._ellipticalArc;
203
+ }
204
+ /**
205
+ * Compute the maximum xy-distance between an elliptical arc and its approximation.
206
+ * * Inputs should be in horizontal plane(s), as z-coordinates are ignored.
207
+ * @param circularArc circular arc approximant. Assumed to start and end on the elliptical arc.
208
+ * @param ellipticalArc elliptical arc being approximated.
209
+ * For best results, `f0` and `f1` should correspond to the start/end of `circularArc`
210
+ * @param f0 optional `ellipticalArc` start fraction to restrict its sweep
211
+ * @param f1 optional `ellipticalArc` end fraction to restrict its sweep
212
+ * @return details of the perpendicular measuring the max approximation error, or undefined if no such perpendicular.
213
+ * For each of `detailA` (refers to `circularArc`) and `detailB` (refers to unrestricted `ellipticalArc`):
214
+ * * `point` is the end of the perpendicular on each curve
215
+ * * `fraction` is the curve parameter of the point
216
+ * * `a` is the distance between the points
217
+ */
218
+ static computePrimitiveErrorXY(circularArc, ellipticalArc, f0, f1) {
219
+ const handler = new CurveCurveCloseApproachXY();
220
+ handler.maxDistanceToAccept = circularArc.quickLength() / 2;
221
+ const trimEllipse = undefined !== f0 && undefined !== f1;
222
+ const trimmedEllipticalArc = trimEllipse ? ellipticalArc.clonePartialCurve(f0, f1) : ellipticalArc;
223
+ // We expect only one perpendicular, not near an endpoint.
224
+ handler.allPerpendicularsArcArcBounded(circularArc, trimmedEllipticalArc);
225
+ let maxPerp;
226
+ for (const perp of handler.grabPairedResults()) {
227
+ if (Geometry.isAlmostEqualEitherNumber(perp.detailA.fraction, 0, 1, Geometry.smallFraction))
228
+ continue; // rule out perpendiculars on circular arc ends
229
+ if (Geometry.isAlmostEqualEitherNumber(perp.detailB.fraction, 0, 1, Geometry.smallFraction))
230
+ continue; // rule out perpendiculars on elliptical arc ends
231
+ const error = perp.detailA.point.distanceXY(perp.detailB.point);
232
+ if (!maxPerp || maxPerp.detailA.a < error) {
233
+ if (trimEllipse) { // reset ellipticalArc fraction to unrestricted range
234
+ perp.detailB.fraction = Geometry.interpolate(f0, perp.detailB.fraction, f1);
235
+ perp.detailB.setCurve(ellipticalArc);
236
+ }
237
+ perp.detailA.a = perp.detailB.a = error;
238
+ maxPerp = perp;
239
+ }
240
+ }
241
+ return maxPerp;
242
+ }
243
+ get maxPerpendicular() {
244
+ return this._maxPerpendicular;
245
+ }
246
+ set maxPerpendicular(newMaxPerp) {
247
+ this._maxPerpendicular = newMaxPerp;
248
+ }
249
+ /**
250
+ * Update the chain approximation error for a given chain child that approximates the elliptical arc between the
251
+ * given fractions.
252
+ * * Fractional sweep [f0, f1] of the elliptical arc is the smaller of the cyclic sweeps.
253
+ */
254
+ updateMaxPerpendicular(childApproximation, f0, f1) {
255
+ const childPerp = ArcChainErrorProcessor.computePrimitiveErrorXY(childApproximation, this.ellipticalArc, f0, f1);
256
+ if (childPerp && (!this.maxPerpendicular || this.maxPerpendicular.detailA.a < childPerp.detailA.a))
257
+ this.maxPerpendicular = childPerp;
258
+ }
259
+ ;
260
+ announceArc(arc, _fPrev, f0, f1) {
261
+ this.updateMaxPerpendicular(arc, f0, f1);
262
+ }
263
+ }
264
+ /**
265
+ * Processor for refining a single Q1 ordered interval [f0,f1] by perturbing an interior fraction f.
266
+ * * This processor expects to repeatedly process a QuadrantFractions `q` wth `q.quadrant` = 1 and fractions array
267
+ * [fPrev, f0, f, f1], where fPrev is from the previously processed (possibly refined) adjacent interval; however, if
268
+ * `q.interpolateStartTangent === true`, then no fPrev is necessary and [f0, f, f1] is expected.
269
+ * * This is enough info to compute the two circular arcs spanning [f0,f] and [f,f1] and compare their approximation
270
+ * errors.
271
+ * * The basic idea is to perturb f so that the difference in the two arcs' errors is minimized.
272
+ * * This processor keeps track of a bracket containing f so that when the caller repeatedly processes `q` via
273
+ * [[EllipticalArcApproximationContext.processQuadrantFractions]], a bisection algorithm plays out, informed by the
274
+ * heuristic that moving f toward one end of its bracket decreases the error of the approximating arc on that side of f.
275
+ * @internal
276
+ */
277
+ class AdaptiveSubdivisionQ1IntervalErrorProcessor extends QuadrantFractionsProcessor {
278
+ constructor(fullEllipseXY, f0, f, f1) {
279
+ super();
280
+ this._fullEllipseXY = fullEllipseXY;
281
+ this._bracket0 = f0;
282
+ this._f = f;
283
+ this._bracket1 = f1;
284
+ this._error0 = this._error1 = Geometry.largeCoordinateResult;
285
+ }
286
+ static create(fullEllipseXY, f0, f, f1) {
287
+ return new AdaptiveSubdivisionQ1IntervalErrorProcessor(fullEllipseXY, f0, f, f1);
288
+ }
289
+ /**
290
+ * The arc to approximate, transformed to local coordinates, and with full sweep.
291
+ * * Local coordinates allows us to ignore z in determining approximation error.
292
+ * * Full sweep guarantees we have the first quadrant in which to do our computations.
293
+ */
294
+ get fullEllipseXY() {
295
+ return this._fullEllipseXY;
296
+ }
297
+ get f() {
298
+ return this._f;
299
+ }
300
+ get isConverged() {
301
+ if (Geometry.isSmallMetricDistance(this._error0 - this._error1))
302
+ return true;
303
+ if (Geometry.isSmallRelative(this._bracket0 - this._bracket1))
304
+ return true;
305
+ return false;
306
+ }
307
+ /** Remember the initial value of the fraction f to be perturbed. */
308
+ announceQuadrantBegin(q, reversed) {
309
+ assert(q.quadrant === 1);
310
+ assert(!reversed); // ASSUME bracket and q.fractions have same ordering
311
+ // the first fraction might be an extra point for computing the first 3-pt arc.
312
+ assert(q.fractions.length === 4 || (q.fractions.length === 3 && q.interpolateStartTangent));
313
+ this._error0 = this._error1 = Geometry.largeCoordinateResult;
314
+ return true;
315
+ }
316
+ /** Compute approximation error over the interval adjacent to f. */
317
+ announceArc(arc, _fPrev, f0, f1) {
318
+ if (Geometry.isAlmostEqualEitherNumber(this.f, f0, f1, 0)) {
319
+ const perp = ArcChainErrorProcessor.computePrimitiveErrorXY(arc, this.fullEllipseXY, f0, f1);
320
+ if (perp) {
321
+ if (this.f === f1)
322
+ this._error0 = perp.detailA.a; // first arc error
323
+ else // f === f0
324
+ this._error1 = perp.detailA.a; // second arc error
325
+ }
326
+ }
327
+ }
328
+ /** Update `q.fractions` with a perturbed value of f that is expected to decrease error delta. */
329
+ announceQuadrantEnd(q, _reversed) {
330
+ if (Geometry.isLargeCoordinateResult(this._error0) || Geometry.isLargeCoordinateResult(this._error1))
331
+ return;
332
+ if (this.isConverged)
333
+ return;
334
+ // set up for next call to processQuadrantFractions
335
+ const n = q.fractions.length;
336
+ if (this._error0 < this._error1)
337
+ this._bracket0 = this._f; // HEURISTIC: move f toward f1 to decrease e1
338
+ else
339
+ this._bracket1 = this._f; // HEURISTIC: move f toward f0 to decrease e0
340
+ this._f = q.fractions[n - 2] = Geometry.interpolate(this._bracket0, 0.5, this._bracket1);
341
+ }
342
+ }
343
+ /**
344
+ * Processor for computing samples in Q1 for a subdivision-based arc chain approximation.
345
+ * * The basic idea is to build a refinement of `q.fractions` for a QuadrantFractions q with q.quadrant = 1.
346
+ * * Start off the refinement with a copy of `q.fractions`.
347
+ * * When an announced arc exceeds a given maximum approximation error, compute a fraction f in the span
348
+ * such that the error of arcs on either side of f would be almost equal, then add f to the refinement.
349
+ * * If the announced arc does not exceed the maxError, its associated fraction span remains unchanged---no
350
+ * additional samples are needed to decrease approximation error.
351
+ * * After `q` processing completes, `q.fractions` is updated in place with the computed refinement.
352
+ * * The caller typically re-processes `q` until `isRefined` returns false, at which point construction of an
353
+ * approximation that is guaranteed not to exceed the desired error can commence.
354
+ * @internal
355
+ */
356
+ class AdaptiveSubdivisionQ1ErrorProcessor extends QuadrantFractionsProcessor {
357
+ constructor(fullEllipseXY, maxError) {
358
+ super();
359
+ this._fullEllipseXY = fullEllipseXY;
360
+ this._fractionRangeQ1 = QuadrantFractions.getQ1FractionalRange(fullEllipseXY.sweep);
361
+ this._maxError = maxError > 0 ? maxError : EllipticalArcApproximationOptions.defaultMaxError;
362
+ this._originalRefinementCount = 0;
363
+ }
364
+ static create(fullEllipseXY, maxError) {
365
+ return new AdaptiveSubdivisionQ1ErrorProcessor(fullEllipseXY, maxError);
366
+ }
367
+ /**
368
+ * The arc to approximate, transformed to local coordinates, and with full sweep.
369
+ * * Local coordinates allows us to ignore z in determining approximation error.
370
+ * * Full sweep guarantees we have the first quadrant in which to do our computations.
371
+ */
372
+ get fullEllipseXY() {
373
+ return this._fullEllipseXY;
374
+ }
375
+ /** Whether the processor refined the current `QuadrantFractions` fractions array to decrease approximation error. */
376
+ get isRefined() {
377
+ if (undefined === this._refinement || 0 === this._refinement.length)
378
+ return false;
379
+ return this._originalRefinementCount < this._refinement.length;
380
+ }
381
+ /** Initialize the refinement from the quadrant fractions array. */
382
+ announceQuadrantBegin(q, reversed) {
383
+ assert(q.quadrant === 1);
384
+ this._refinement = new SortedArray(reversed ? compareFractionsDecreasing : compareFractionsIncreasing, false);
385
+ for (const f of q.fractions) {
386
+ if (this._fractionRangeQ1.containsX(f))
387
+ this._refinement.insert(f);
388
+ }
389
+ return 2 <= (this._originalRefinementCount = this._refinement.length);
390
+ }
391
+ /**
392
+ * Return the adjacent fraction from the previously refined interval.
393
+ * * This is used to refine the interval of an inner arc, which depends on the most recent refinement of the
394
+ * previous interval.
395
+ */
396
+ getPreviousFraction(f0) {
397
+ if (undefined === this._refinement)
398
+ return undefined;
399
+ const iPrev = this._refinement.indexOf(f0);
400
+ return (iPrev >= 1) ? this._refinement.get(iPrev - 1) : undefined;
401
+ }
402
+ /** If this arc needs to be refined, add a refinement point. */
403
+ announceArc(arc, fPrev, f0, f1) {
404
+ if (undefined === this._refinement)
405
+ return;
406
+ if (this._originalRefinementCount > 2) { // no early out for a single interval; it gets refined below
407
+ const perp = ArcChainErrorProcessor.computePrimitiveErrorXY(arc, this.fullEllipseXY, f0, f1);
408
+ if (!perp || perp.detailA.a <= this._maxError)
409
+ return;
410
+ }
411
+ // throughout this function, f0 and f1 may be in either order
412
+ const f = Geometry.interpolate(f0, 0.5, f1);
413
+ const interpolateStartTangent = Geometry.isAlmostEqualEitherNumber(f0, this._fractionRangeQ1.low, this._fractionRangeQ1.high, 0);
414
+ const interpolateEndTangent = Geometry.isAlmostEqualEitherNumber(f1, this._fractionRangeQ1.low, this._fractionRangeQ1.high, 0);
415
+ if (!interpolateStartTangent && undefined === fPrev)
416
+ fPrev = this.getPreviousFraction(f0); // createLastArc caller doesn't supply fPrev
417
+ const fractions = (undefined === fPrev) ? [f0, f, f1] : [fPrev, f0, f, f1];
418
+ const q1 = [QuadrantFractions.create(1, fractions, interpolateStartTangent, interpolateEndTangent)];
419
+ const processor = AdaptiveSubdivisionQ1IntervalErrorProcessor.create(this.fullEllipseXY, f0, f, f1);
420
+ let iter = 0;
421
+ do { // bisect to refine f (starting at avg) to balance the approx error of the arcs on either side
422
+ EllipticalArcApproximationContext.processQuadrantFractions(this.fullEllipseXY, q1, processor);
423
+ } while (iter++ < AdaptiveSubdivisionQ1ErrorProcessor._maxIters && !processor.isConverged);
424
+ this._refinement.insert(processor.f);
425
+ }
426
+ /** Update the quadrant fractions array with the current refinement. */
427
+ announceQuadrantEnd(q, _reversed) {
428
+ if (this._refinement)
429
+ q.fractions = [...this._refinement];
430
+ }
431
+ /**
432
+ * Compute radian angles for the fractions in the current refinement that are strictly inside Q1.
433
+ * @param result optional preallocated array to clear and populate
434
+ * @return angles suitable for output from [[EllipticalArcSampler.computeRadiansStrictlyInsideQuadrant1]].
435
+ */
436
+ getRefinedInteriorQ1Angles(result) {
437
+ if (!result)
438
+ result = [];
439
+ else
440
+ result.length = 0;
441
+ if (this._refinement) {
442
+ for (const f of this._refinement) {
443
+ if (this._fractionRangeQ1.containsXOpen(f))
444
+ result.push(this.fullEllipseXY.sweep.fractionToRadians(f));
445
+ }
446
+ }
447
+ return result;
448
+ }
449
+ }
450
+ AdaptiveSubdivisionQ1ErrorProcessor._maxIters = 50;
451
+ ;
452
+ /**
453
+ * Implementation for method `EllipticalArcSampleMethod.UniformParameter`
454
+ * @internal
455
+ */
456
+ class UniformParameterSampler {
457
+ constructor(c, o) {
458
+ this._context = c;
459
+ this._options = o;
460
+ }
461
+ static create(context, options) {
462
+ return new UniformParameterSampler(context, options);
463
+ }
464
+ computeRadiansStrictlyInsideQuadrant1(result) {
465
+ if (!result)
466
+ result = [];
467
+ if (this._context.isValidEllipticalArc) {
468
+ const aDelta = Angle.piOver2Radians / (this._options.numSamplesInQuadrant - 1);
469
+ for (let i = 1; i < this._options.numSamplesInQuadrant - 1; ++i)
470
+ result.push(i * aDelta);
471
+ }
472
+ return result;
473
+ }
474
+ }
475
+ ;
476
+ /**
477
+ * Implementation for method `EllipticalArcSampleMethod.NonUniformCurvature`
478
+ * @internal
479
+ */
480
+ class NonUniformCurvatureSampler {
481
+ constructor(c, o) {
482
+ this._context = c;
483
+ this._options = o;
484
+ this._xMag2 = c.ellipticalArc.matrixRef.columnXMagnitudeSquared();
485
+ this._yMag2 = c.ellipticalArc.matrixRef.columnYMagnitudeSquared();
486
+ // extreme curvatures occur at the ellipse's axis points because its axes are perpendicular
487
+ this._curvatureRange = Range1d.createXX(Math.sqrt(this._xMag2) / this._yMag2, Math.sqrt(this._yMag2) / this._xMag2);
488
+ }
489
+ static create(context, options) {
490
+ return new NonUniformCurvatureSampler(context, options);
491
+ }
492
+ /**
493
+ * Compute the angle corresponding to the point in the ellipse's first quadrant with the given curvature.
494
+ * * The elliptical arc is assumed to be non-circular and have perpendicular axes of positive length; its sweep is ignored.
495
+ * * This is a scaled inverse of [[Arc3d.fractionToCurvature]] restricted to fractions in [0, 1/4].
496
+ * @return radian angle in [0, pi/2] or undefined if the ellipse is invalid, or does not attain the given curvature.
497
+ */
498
+ curvatureToRadians(curvature) {
499
+ /*
500
+ Let the elliptical arc be parameterized with axes u,v of different length and u.v = 0:
501
+ f(t) = c + u cos(t) + v sin(t),
502
+ f'(t) = -u sin(t) + v cos(t),
503
+ f"(t) = -u cos(t) - v sin(t)
504
+ We seek a formula for t(K), the inverse of the standard curvature formula
505
+ K(t) := ||f'(t) x f"(t)|| / ||f'(t)||^3
506
+ for a parametric function f(t):R->R^3. We'll restrict K to Q1 (i.e., t in [0, pi/2]), where K is monotonic.
507
+ By linearity of the cross product and the above formulas, the numerator of K(t) reduces to ||u x v||, and so:
508
+ cbrt(||u x v||/K) = ||f'(t)|| = sqrt(f'(t).f'(t))
509
+ Leveraging u,v perpendicularity we can define:
510
+ lambda(K) := (||u x v||/K)^(2/3) = (||u|| ||v|| / K)^(2/3) = cbrt(u.u v.v / K^2)
511
+ Then substituting and using perpendicularity again:
512
+ lambda(K) = f'(t).f'(t)
513
+ = sin^2(t)u.u + cos^2(t)v.v - 2sin(t)cos(t)u.v
514
+ = u.u + cos^2(t)(v.v - u.u)
515
+ Taking the positive root because cos(t)>=0 in Q1, and relying on u,v having different lengths:
516
+ cos(t) = sqrt((lambda(K) - u.u)/(v.v - u.u))
517
+ Solving for t yields the formula for t(K).
518
+ */
519
+ if (!this._curvatureRange.containsX(curvature))
520
+ return undefined; // ellipse does not attain this curvature
521
+ const lambda = Math.cbrt((this._xMag2 * this._yMag2) / (curvature * curvature));
522
+ const cosTheta = Math.sqrt(Math.abs((lambda - this._xMag2) / (this._yMag2 - this._xMag2)));
523
+ return Math.acos(cosTheta);
524
+ }
525
+ computeRadiansStrictlyInsideQuadrant1(result) {
526
+ if (!result)
527
+ result = [];
528
+ if (this._context.isValidEllipticalArc) {
529
+ const tDelta = 1.0 / (this._options.numSamplesInQuadrant - 1);
530
+ for (let i = 1; i < this._options.numSamplesInQuadrant - 1; ++i) {
531
+ const j = this._options.remapFunction(i * tDelta);
532
+ const curvature = (1 - j) * this._curvatureRange.low + j * this._curvatureRange.high;
533
+ const angle = this.curvatureToRadians(curvature);
534
+ if (undefined !== angle)
535
+ result.push(angle);
536
+ }
537
+ }
538
+ return result;
539
+ }
540
+ }
541
+ ;
542
+ /**
543
+ * Implementation for method `EllipticalArcSampleMethod.UniformCurvature`.
544
+ * * Basically this is just `NonUniformCurvature` method with uniformity preserved via identity remap function.
545
+ * @internal
546
+ */
547
+ class UniformCurvatureSampler extends NonUniformCurvatureSampler {
548
+ constructor(c, o) {
549
+ super(c, o.clone());
550
+ this._options.remapFunction = (x) => x; // identity map
551
+ }
552
+ static create(context, options) {
553
+ return new UniformCurvatureSampler(context, options);
554
+ }
555
+ }
556
+ ;
557
+ /**
558
+ * Implementation for method `EllipticalArcSampleMethod.AdaptiveSubdivision`
559
+ * @internal
560
+ */
561
+ class AdaptiveSubdivisionSampler {
562
+ constructor(c, o) {
563
+ this._context = c;
564
+ this._options = o;
565
+ this._fullEllipseXY = c.cloneLocalArc(true) ?? Arc3d.createUnitCircle();
566
+ }
567
+ static create(context, options) {
568
+ return new AdaptiveSubdivisionSampler(context, options);
569
+ }
570
+ /**
571
+ * Return a copy of the arc to approximate, transformed to local coordinates, and with full sweep.
572
+ * * Local coordinates allows us to ignore z in determining approximation error.
573
+ * * Full sweep guarantees we have the first quadrant in which to do our computations.
574
+ */
575
+ get fullEllipseXY() {
576
+ return this._fullEllipseXY;
577
+ }
578
+ computeRadiansStrictlyInsideQuadrant1(result) {
579
+ if (!this._context.isValidEllipticalArc)
580
+ return [];
581
+ const rangeQ1 = QuadrantFractions.getQ1FractionalRange(this.fullEllipseXY.sweep);
582
+ const q1 = [QuadrantFractions.create(1, [rangeQ1.low, rangeQ1.high], true, true)];
583
+ const processor = AdaptiveSubdivisionQ1ErrorProcessor.create(this.fullEllipseXY, this._options.maxError);
584
+ do {
585
+ EllipticalArcApproximationContext.processQuadrantFractions(this.fullEllipseXY, q1, processor);
586
+ } while (processor.isRefined);
587
+ return processor.getRefinedInteriorQ1Angles(result);
588
+ }
589
+ }
590
+ ;
591
+ /**
592
+ * Processor for constructing a sample-based circular arc chain approximation.
593
+ * @internal
594
+ */
595
+ class ArcChainConstructionProcessor extends QuadrantFractionsProcessor {
596
+ constructor(ellipticalArc, forcePath) {
597
+ super();
598
+ this._chain = (ellipticalArc.sweep.isFullCircle && !forcePath) ? Loop.create() : Path.create();
599
+ }
600
+ static create(ellipticalArc, forcePath = false) {
601
+ return new ArcChainConstructionProcessor(ellipticalArc, forcePath);
602
+ }
603
+ get chain() {
604
+ return this._chain.children.length > 0 ? this._chain : undefined;
605
+ }
606
+ announceQuadrantBegin(_q, _reversed) {
607
+ this._quadrantChain = undefined;
608
+ return true;
609
+ }
610
+ announceArc(arc, _fPrev, _f0, _f1) {
611
+ if (!this._quadrantChain)
612
+ this._quadrantChain = Path.create(); // the arc chain in a quadrant is always open
613
+ this._quadrantChain.tryAddChild(arc); // captured!
614
+ }
615
+ announceQuadrantEnd(_q, reversed) {
616
+ if (this._quadrantChain) {
617
+ if (reversed)
618
+ this._quadrantChain.reverseChildrenInPlace();
619
+ for (const child of this._quadrantChain.children)
620
+ this._chain.tryAddChild(child); // captured!
621
+ }
622
+ }
623
+ }
624
+ ;
625
+ /**
626
+ * Context for sampling a non-circular Arc3d and for constructing an approximation to it based on interpolation
627
+ * of the samples.
628
+ * * [[EllipticalArcApproximationContext.constructCircularArcChainApproximation]] constructs a `CurveChain`
629
+ * approximation consisting of circular arcs.
630
+ * * Various sample methods are supported, cf. [[EllipticalArcApproximationOptions]].
631
+ * @internal
632
+ */
633
+ export class EllipticalArcApproximationContext {
634
+ /** Constructor, captures input */
635
+ constructor(ellipticalArc) {
636
+ this._isValidEllipticalArc = false;
637
+ const data = ellipticalArc.toScaledMatrix3d();
638
+ this._ellipticalArc = Arc3d.createScaledXYColumns(data.center, data.axes, data.r0, data.r90, data.sweep);
639
+ this._localToWorld = Transform.createRefs(data.center, data.axes);
640
+ if (this._localToWorld.matrix.isSingular())
641
+ return;
642
+ if (this._ellipticalArc.sweep.isEmpty)
643
+ return; // ellipse must have a nonzero sweep
644
+ const xMag2 = ellipticalArc.matrixRef.columnXMagnitudeSquared();
645
+ const yMag2 = ellipticalArc.matrixRef.columnYMagnitudeSquared();
646
+ if (Geometry.isSmallMetricDistanceSquared(xMag2) || Geometry.isSmallMetricDistanceSquared(yMag2))
647
+ return; // ellipse must have positive radii
648
+ if (Geometry.isSameCoordinateSquared(xMag2, yMag2))
649
+ return; // ellipse must not be circular
650
+ this._isValidEllipticalArc = true;
651
+ }
652
+ /** Constructor, clones input. */
653
+ static create(ellipticalArc) {
654
+ return new EllipticalArcApproximationContext(ellipticalArc);
655
+ }
656
+ /**
657
+ * The arc to be sampled.
658
+ * * Its axes are forced to be perpendicular.
659
+ * * It is stored in world coordinates.
660
+ */
661
+ get ellipticalArc() {
662
+ return this._ellipticalArc;
663
+ }
664
+ /**
665
+ * The rigid transformation that maps `ellipticalArc` from local coordinates to world coordinates.
666
+ * * In local coordinates, the arc center lies at the origin and its (perpendicular) axes of symmetry lie along
667
+ * the positive x- and y-axes.
668
+ */
669
+ get localToWorld() {
670
+ return this._localToWorld;
671
+ }
672
+ /**
673
+ * Whether the elliptical arc is amenable to sampling.
674
+ * * The arc is valid if it is non-circular, has nonzero sweep, and has positive radii (nonsingular matrix).
675
+ */
676
+ get isValidEllipticalArc() {
677
+ return this._isValidEllipticalArc;
678
+ }
679
+ /**
680
+ * Create a clone of the context's arc in local coordinates.
681
+ * @param fullSweep Optionally set full sweep on the returned local arc. Start angle is preserved.
682
+ * @returns local arc, or undefined if the arc is invalid
683
+ */
684
+ cloneLocalArc(fullSweep) {
685
+ if (!this.isValidEllipticalArc)
686
+ return undefined;
687
+ const worldToLocal = this.localToWorld.inverse();
688
+ if (!worldToLocal)
689
+ return undefined;
690
+ const arcXY = this.ellipticalArc.cloneTransformed(worldToLocal);
691
+ if (fullSweep) {
692
+ let sweep = 2 * Math.PI;
693
+ if (!arcXY.sweep.isCCW)
694
+ sweep = -sweep;
695
+ arcXY.sweep.setStartEndRadians(arcXY.sweep.startRadians, arcXY.sweep.startRadians + sweep);
696
+ }
697
+ return arcXY;
698
+ }
699
+ /**
700
+ * Process structured sample data for the given elliptical arc.
701
+ * * Circular arcs are announced to the processor for each sample interval in each quadrant.
702
+ * * Each quadrant is processed separately to allow the elliptical arc's axis points and tangents to be interpolated.
703
+ * * A 2-point plus tangent construction is used to create the first and last circular arc in each quadrant.
704
+ * * Symmetry of the announced circular arcs matching that of a multi-quadrant spanning elliptical arc is ensured by
705
+ * processing the samples consistently, starting along the elliptical arc's major axis in each quadrant.
706
+ * @param ellipticalArc source arc to approximate
707
+ * @param quadrants structured samples, may be temporarily reversed for symmetry
708
+ * @param processor callbacks for handling the constructed arcs
709
+ * @internal
710
+ */
711
+ static processQuadrantFractions(ellipticalArc, quadrants, processor) {
712
+ const pt0 = this.workPt0;
713
+ const pt1 = this.workPt1;
714
+ const pt2 = this.workPt2;
715
+ const ray = this.workRay;
716
+ const arcBetween2Samples = (arcStart, arcEnd, reverse) => {
717
+ // assume non-colinear inputs
718
+ const myArc = Arc3d.createCircularStartTangentEnd(arcStart.origin, arcStart.direction, arcEnd);
719
+ if (!(myArc instanceof Arc3d))
720
+ return undefined;
721
+ if (reverse)
722
+ myArc.reverseInPlace();
723
+ return myArc;
724
+ };
725
+ const arcBetweenLast2Of3Samples = (p0, arcStart, arcEnd) => {
726
+ // assume non-colinear inputs; initial arc starts at p0, ends at arcEnd
727
+ const arc = Arc3d.createCircularStartMiddleEnd(p0, arcStart, arcEnd);
728
+ if (!(arc instanceof Arc3d))
729
+ return undefined; // colinear?
730
+ const startAngle = arc.vector0.signedAngleTo(Vector3d.createStartEnd(arc.center, arcStart), arc.matrixRef.columnZ());
731
+ arc.sweep.setStartEndRadians(startAngle.radians, arc.sweep.endRadians);
732
+ return arc; // returned arc starts at arcStart, ends at arcEnd
733
+ };
734
+ const createFirstArc = (f0, f1, reverse) => {
735
+ // This arc starts at the first sample f0 and ends at f1.
736
+ ellipticalArc.fractionToPointAndDerivative(f0, ray);
737
+ ellipticalArc.fractionToPoint(f1, pt1);
738
+ if (reverse)
739
+ ray.direction.scaleInPlace(-1);
740
+ const arc = arcBetween2Samples(ray, pt1, false);
741
+ if (arc)
742
+ processor.announceArc(arc, undefined, f0, f1);
743
+ };
744
+ const createInnerArc = (f0, f1, f2) => {
745
+ let fPrev = f0;
746
+ if (processor.getPreviousFraction)
747
+ fPrev = processor.getPreviousFraction(f1) ?? f0;
748
+ ellipticalArc.fractionToPoint(fPrev, pt0);
749
+ ellipticalArc.fractionToPoint(f1, pt1);
750
+ ellipticalArc.fractionToPoint(f2, pt2);
751
+ const arc = arcBetweenLast2Of3Samples(pt0, pt1, pt2);
752
+ if (arc)
753
+ processor.announceArc(arc, fPrev, f1, f2);
754
+ };
755
+ const createLastArc = (f0, f1, reverse) => {
756
+ // This arc starts at f0 and ends at the last sample f1. It is the only arc to use f1.
757
+ ellipticalArc.fractionToPoint(f0, pt0);
758
+ ellipticalArc.fractionToPointAndDerivative(f1, ray);
759
+ if (!reverse)
760
+ ray.direction.scaleInPlace(-1);
761
+ const arc = arcBetween2Samples(ray, pt0, true);
762
+ if (arc)
763
+ processor.announceArc(arc, undefined, f0, f1);
764
+ };
765
+ const reverseFractionsForSymmetry = (q) => {
766
+ // If q interpolates an axis, we process q.fractions in a consistent direction (increasing or decreasing) so that the
767
+ // approximating arc chain exhibits fourfold axial symmetry. We do this by ensuring q.fractions starts along the
768
+ // major axis (or ends along the minor axis). This choice is arbitrary, but consistently made across all quadrants.
769
+ if (!q.interpolateStartTangent && !q.interpolateEndTangent)
770
+ return false;
771
+ const n = q.fractions.length;
772
+ if (n < 2)
773
+ return false;
774
+ const xAxisIsMajor = ellipticalArc.vector0.magnitudeSquared() > ellipticalArc.vector90.magnitudeSquared();
775
+ const processCCWQuadrantInReverse = xAxisIsMajor ? (q.quadrant === 2 || q.quadrant === 4) : (q.quadrant === 1 || q.quadrant === 3);
776
+ const isAlreadyReversed = q.fractions[0] > q.fractions[n - 1];
777
+ const doReverse = !isAlreadyReversed && (processCCWQuadrantInReverse === ellipticalArc.sweep.isCCW);
778
+ if (doReverse)
779
+ q.reverse(); // for symmetry we sometimes process decreasing fractions. This creates slightly different arcs.
780
+ return doReverse;
781
+ };
782
+ for (const q of quadrants) {
783
+ const n = q.fractions.length;
784
+ if (n < 2)
785
+ continue;
786
+ const reversed = reverseFractionsForSymmetry(q);
787
+ if (!processor.announceQuadrantBegin(q, reversed))
788
+ continue;
789
+ if (q.interpolateStartTangent)
790
+ createFirstArc(q.fractions[0], q.fractions[1], reversed);
791
+ // the first inner arc approximates the ellipse over [f[1],f[2]]; the last inner arc, over [f[n-3],f[n-2]]
792
+ for (let i = 0; i + 2 < n - 1; ++i)
793
+ createInnerArc(q.fractions[i], q.fractions[i + 1], q.fractions[i + 2]);
794
+ if (n > 2) { // the final arc approximates [f[n-2],f[n-1]]
795
+ if (q.interpolateEndTangent)
796
+ createLastArc(q.fractions[n - 2], q.fractions[n - 1], reversed);
797
+ else
798
+ createInnerArc(q.fractions[n - 3], q.fractions[n - 2], q.fractions[n - 1]);
799
+ }
800
+ processor.announceQuadrantEnd(q, reversed);
801
+ if (reversed)
802
+ q.reverse(); // undo the reverse above
803
+ }
804
+ }
805
+ /**
806
+ * Compute the maximum error of the circular arc chain approximation determined by the given samples.
807
+ * * This is measured by the longest perpendicular between the elliptical arc and its approximation.
808
+ * @param samples structured sample data from the instance's elliptical arc.
809
+ * @return details of the perpendicular measuring the max approximation error, or undefined if no such perpendicular.
810
+ * For each of `detailA` and `detailB`:
811
+ * * `point` is the end of the perpendicular on each curve
812
+ * * `fraction` is the curve parameter of the point
813
+ * * `a` is the distance between the points.
814
+ * @internal
815
+ */
816
+ computeApproximationError(samples) {
817
+ const arcXY = this.cloneLocalArc();
818
+ if (!arcXY)
819
+ return undefined;
820
+ const processor = ArcChainErrorProcessor.create(arcXY);
821
+ EllipticalArcApproximationContext.processQuadrantFractions(arcXY, samples, processor);
822
+ const maxError = processor.maxPerpendicular;
823
+ return (maxError && maxError.tryTransformInPlace(this.localToWorld)) ? maxError : undefined;
824
+ }
825
+ /**
826
+ * Compute samples for the elliptical arc as fraction parameters.
827
+ * * This method houses the sampling framework for all sampling methods, which are customized via implementations
828
+ * of the [[EllipticalArcSampler]] interface.
829
+ * * Note that the returned samples are fractions in the parameterization of the context's arc (whose axes have been
830
+ * forced to be perpendicular), not the input arc passed into the context's constructor.
831
+ * @param options options that determine how the elliptical arc is sampled.
832
+ * @param structuredOutput flag indicating output format as follows:
833
+ * * If false (default), return all fractions in one sorted (increasing), deduplicated array (a full ellipse includes
834
+ * both 0 and 1).
835
+ * * If true, fractions are assembled by quadrants:
836
+ * * Each [[QuadrantFractions]] object holds at least three sorted (increasing), deduplicated fractions in a
837
+ * specified quadrant of the arc.
838
+ * * If only two fractions would be computed for a given `QuadrantFractions`, their midpoint is inserted to enable
839
+ * tangent interpolation at both ends. Such a quadrant `q` is marked with `q.averageAdded = true`.
840
+ * * The `QuadrantFractions` objects themselves are sorted by increasing order of the fractions they contain.
841
+ * * If the arc sweep spans adjacent quadrants, the fraction bordering the quadrants appears in both `QuadrantFractions`.
842
+ * * If the arc starts and ends in the same quadrant, two `QuadrantFractions` objects can be returned.
843
+ * * This means there are between 1 and 5 objects in the `QuadrantFractions` array.
844
+ * @internal
845
+ */
846
+ computeSampleFractions(options, structuredOutput = false) {
847
+ if (!this.isValidEllipticalArc)
848
+ return [];
849
+ const compareRadiansIncreasing = (a0, a1) => {
850
+ if (Geometry.isAlmostEqualNumber(a0, a1, Geometry.smallAngleRadians))
851
+ return 0;
852
+ return a0 < a1 ? -1 : 1;
853
+ };
854
+ const compareRadiansDecreasing = (a0, a1) => {
855
+ if (Geometry.isAlmostEqualNumber(a0, a1, Geometry.smallAngleRadians))
856
+ return 0;
857
+ return a0 < a1 ? 1 : -1;
858
+ };
859
+ const compareQuadrantFractions = (q0, q1) => {
860
+ // ASSUME QuadrantFractions.fractions arrays are sorted (increasing) and have only trivial overlap
861
+ if (compareFractionsIncreasing(q0.fractions[q0.fractions.length - 1], q1.fractions[0]) <= 0)
862
+ return -1;
863
+ if (compareFractionsIncreasing(q1.fractions[q1.fractions.length - 1], q0.fractions[0]) <= 0)
864
+ return 1;
865
+ return 0;
866
+ };
867
+ const shiftRadiansToSweep = (angle, sweep) => {
868
+ const inSweep = sweep.isRadiansInSweep(angle, true);
869
+ if (inSweep) {
870
+ const fraction = sweep.radiansToSignedPeriodicFraction(angle);
871
+ if (Geometry.isIn01(fraction))
872
+ angle = sweep.fractionToRadians(fraction);
873
+ }
874
+ return { angle, inSweep };
875
+ };
876
+ const convertAndAddRadiansToFractionInRange = (dest, radians, sweep, f0, f1) => {
877
+ if (undefined === f0)
878
+ f0 = 0;
879
+ if (undefined === f1)
880
+ f1 = 1;
881
+ if (f0 > f1)
882
+ return convertAndAddRadiansToFractionInRange(dest, radians, sweep, f1, f0);
883
+ const fraction = sweep.radiansToSignedPeriodicFraction(radians);
884
+ if (fraction < (f0 - Geometry.smallFraction) || (f1 + Geometry.smallFraction) < fraction)
885
+ return undefined; // angle is outside sweep
886
+ Geometry.restrictToInterval(fraction, 0, 1);
887
+ dest.add(fraction);
888
+ return fraction;
889
+ };
890
+ const convertQ1RadiansInSweepToQuadrantFractions = (anglesInQ1, angle0, angle1, sweep) => {
891
+ if (angle0 > angle1)
892
+ return convertQ1RadiansInSweepToQuadrantFractions(anglesInQ1, angle1, angle0, sweep);
893
+ if (Angle.isAlmostEqualRadiansNoPeriodShift(angle0, angle1))
894
+ return undefined; // empty sweep
895
+ const qData = QuadrantFractions.getQuadrantRadians(angle0, angle1);
896
+ if (undefined === qData)
897
+ return undefined; // no containing quadrant
898
+ const qFractions = new OrderedSet(compareFractionsIncreasing);
899
+ const f0 = convertAndAddRadiansToFractionInRange(qFractions, angle0, sweep);
900
+ const f1 = convertAndAddRadiansToFractionInRange(qFractions, angle1, sweep);
901
+ if (undefined === f0 || undefined === f1)
902
+ return undefined;
903
+ for (const a0 of anglesInQ1) {
904
+ let angle = a0;
905
+ if (2 === qData.quadrant)
906
+ angle = Angle.piRadians - angle;
907
+ else if (3 === qData.quadrant)
908
+ angle = Angle.piRadians + angle;
909
+ else if (4 === qData.quadrant)
910
+ angle = Angle.pi2Radians - angle;
911
+ convertAndAddRadiansToFractionInRange(qFractions, angle, sweep, f0, f1);
912
+ }
913
+ const qf = QuadrantFractions.create(qData.quadrant, [...qFractions]);
914
+ const n = qf.fractions.length;
915
+ if (2 === n) { // e.g. elliptical arc is so small it contains no interior samples in this quadrant
916
+ qf.fractions.splice(1, 0, Geometry.interpolate(qf.fractions[0], 0.5, qf.fractions[1]));
917
+ qf.averageAdded = true;
918
+ }
919
+ return qf;
920
+ };
921
+ const computeStructuredOutput = (anglesInQ1, arcSweep) => {
922
+ const qEndAngles = new OrderedSet(arcSweep.isCCW ? compareRadiansIncreasing : compareRadiansDecreasing);
923
+ qEndAngles.add(arcSweep.endRadians);
924
+ for (const qAngle of [0, Angle.piOver2Radians, Angle.piRadians, Angle.pi3Over2Radians, Angle.pi2Radians]) {
925
+ const shifted = shiftRadiansToSweep(qAngle, arcSweep);
926
+ if (shifted.inSweep)
927
+ qEndAngles.add(shifted.angle);
928
+ }
929
+ const quadrants = new OrderedSet(compareQuadrantFractions);
930
+ let a0 = arcSweep.startRadians;
931
+ for (const a1 of qEndAngles) {
932
+ const quadrant = convertQ1RadiansInSweepToQuadrantFractions(anglesInQ1, a0, a1, arcSweep);
933
+ if (quadrant)
934
+ quadrants.add(quadrant);
935
+ a0 = a1;
936
+ }
937
+ return [...quadrants];
938
+ };
939
+ const computeFlatOutput = (anglesInQ1, arcSweep) => {
940
+ // first add the quadrant fractions so the set prefers them over nearby interior fractions
941
+ const fractions = new OrderedSet(compareFractionsIncreasing);
942
+ fractions.add(0);
943
+ fractions.add(1);
944
+ for (const angle of [0, Angle.piOver2Radians, Angle.piRadians, Angle.pi3Over2Radians])
945
+ convertAndAddRadiansToFractionInRange(fractions, angle, arcSweep);
946
+ // add interior Q1 fractions, reflect to the other quadrants, filter by sweep and extant entry
947
+ for (const angle0 of anglesInQ1) {
948
+ for (const angle of [angle0, Angle.piRadians - angle0, Angle.piRadians + angle0, Angle.pi2Radians - angle0])
949
+ convertAndAddRadiansToFractionInRange(fractions, angle, arcSweep);
950
+ }
951
+ return [...fractions];
952
+ };
953
+ // sample the (full) ellipse as angles in strict interior of Quadrant 1
954
+ const radiansQ1 = []; // unordered
955
+ switch (options.sampleMethod) {
956
+ case EllipticalArcSampleMethod.UniformParameter: {
957
+ UniformParameterSampler.create(this, options).computeRadiansStrictlyInsideQuadrant1(radiansQ1);
958
+ break;
959
+ }
960
+ case EllipticalArcSampleMethod.UniformCurvature: {
961
+ UniformCurvatureSampler.create(this, options).computeRadiansStrictlyInsideQuadrant1(radiansQ1);
962
+ break;
963
+ }
964
+ case EllipticalArcSampleMethod.NonUniformCurvature: {
965
+ NonUniformCurvatureSampler.create(this, options).computeRadiansStrictlyInsideQuadrant1(radiansQ1);
966
+ break;
967
+ }
968
+ case EllipticalArcSampleMethod.AdaptiveSubdivision: {
969
+ AdaptiveSubdivisionSampler.create(this, options).computeRadiansStrictlyInsideQuadrant1(radiansQ1);
970
+ break;
971
+ }
972
+ default:
973
+ break;
974
+ }
975
+ return structuredOutput ?
976
+ computeStructuredOutput(radiansQ1, this.ellipticalArc.sweep) :
977
+ computeFlatOutput(radiansQ1, this.ellipticalArc.sweep);
978
+ }
979
+ /** Construct a circular arc chain approximation to the elliptical arc. */
980
+ constructCircularArcChainApproximation(options) {
981
+ if (!this.isValidEllipticalArc)
982
+ return undefined;
983
+ if (!options)
984
+ options = EllipticalArcApproximationOptions.create();
985
+ const processor = ArcChainConstructionProcessor.create(this.ellipticalArc, options.forcePath);
986
+ const samples = this.computeSampleFractions(options, true);
987
+ EllipticalArcApproximationContext.processQuadrantFractions(this.ellipticalArc, samples, processor);
988
+ return processor.chain;
989
+ }
990
+ }
991
+ EllipticalArcApproximationContext.workPt0 = Point3d.createZero();
992
+ EllipticalArcApproximationContext.workPt1 = Point3d.createZero();
993
+ EllipticalArcApproximationContext.workPt2 = Point3d.createZero();
994
+ EllipticalArcApproximationContext.workRay = Ray3d.createZero();
995
+ //# sourceMappingURL=EllipticalArcApproximationContext.js.map