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