@mlightcad/geometry-engine 3.2.38 → 3.2.40

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 (54) hide show
  1. package/dist/geometry-engine.umd.cjs +1 -1
  2. package/lib/geometry/AcGeCircArc3d.d.ts +7 -0
  3. package/lib/geometry/AcGeCircArc3d.d.ts.map +1 -1
  4. package/lib/geometry/AcGeCircArc3d.js +12 -0
  5. package/lib/geometry/AcGeCircArc3d.js.map +1 -1
  6. package/lib/geometry/AcGeEllipseArc3d.d.ts +7 -0
  7. package/lib/geometry/AcGeEllipseArc3d.d.ts.map +1 -1
  8. package/lib/geometry/AcGeEllipseArc3d.js +13 -0
  9. package/lib/geometry/AcGeEllipseArc3d.js.map +1 -1
  10. package/lib/geometry/AcGeLine3d.d.ts +7 -0
  11. package/lib/geometry/AcGeLine3d.d.ts.map +1 -1
  12. package/lib/geometry/AcGeLine3d.js +16 -0
  13. package/lib/geometry/AcGeLine3d.js.map +1 -1
  14. package/lib/geometry/AcGeNurbsCurve.d.ts +55 -1
  15. package/lib/geometry/AcGeNurbsCurve.d.ts.map +1 -1
  16. package/lib/geometry/AcGeNurbsCurve.js +146 -4
  17. package/lib/geometry/AcGeNurbsCurve.js.map +1 -1
  18. package/lib/geometry/AcGePolyline2d.d.ts +7 -0
  19. package/lib/geometry/AcGePolyline2d.d.ts.map +1 -1
  20. package/lib/geometry/AcGePolyline2d.js +10 -0
  21. package/lib/geometry/AcGePolyline2d.js.map +1 -1
  22. package/lib/geometry/AcGePolyline2dOffset.d.ts +28 -0
  23. package/lib/geometry/AcGePolyline2dOffset.d.ts.map +1 -0
  24. package/lib/geometry/AcGePolyline2dOffset.js +886 -0
  25. package/lib/geometry/AcGePolyline2dOffset.js.map +1 -0
  26. package/lib/geometry/AcGeSpline3d.d.ts +9 -1
  27. package/lib/geometry/AcGeSpline3d.d.ts.map +1 -1
  28. package/lib/geometry/AcGeSpline3d.js +7 -0
  29. package/lib/geometry/AcGeSpline3d.js.map +1 -1
  30. package/lib/index.d.ts +1 -1
  31. package/lib/index.d.ts.map +1 -1
  32. package/lib/index.js +1 -1
  33. package/lib/index.js.map +1 -1
  34. package/lib/util/AcGeCurveOffsetUtil.d.ts +22 -0
  35. package/lib/util/AcGeCurveOffsetUtil.d.ts.map +1 -0
  36. package/lib/util/AcGeCurveOffsetUtil.js +37 -0
  37. package/lib/util/AcGeCurveOffsetUtil.js.map +1 -0
  38. package/lib/util/AcGeNurbsUtil.d.ts +13 -0
  39. package/lib/util/AcGeNurbsUtil.d.ts.map +1 -1
  40. package/lib/util/AcGeNurbsUtil.js +105 -0
  41. package/lib/util/AcGeNurbsUtil.js.map +1 -1
  42. package/lib/util/AcGeSampledCurveOffsetUtil.d.ts +21 -0
  43. package/lib/util/AcGeSampledCurveOffsetUtil.d.ts.map +1 -0
  44. package/lib/util/AcGeSampledCurveOffsetUtil.js +302 -0
  45. package/lib/util/AcGeSampledCurveOffsetUtil.js.map +1 -0
  46. package/lib/util/AcGeTol.d.ts +8 -0
  47. package/lib/util/AcGeTol.d.ts.map +1 -1
  48. package/lib/util/AcGeTol.js +14 -0
  49. package/lib/util/AcGeTol.js.map +1 -1
  50. package/lib/util/index.d.ts +3 -1
  51. package/lib/util/index.d.ts.map +1 -1
  52. package/lib/util/index.js +3 -1
  53. package/lib/util/index.js.map +1 -1
  54. package/package.json +2 -2
@@ -0,0 +1,886 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __values = (this && this.__values) || function(o) {
13
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
14
+ if (m) return m.call(o);
15
+ if (o && typeof o.length === "number") return {
16
+ next: function () {
17
+ if (o && i >= o.length) o = void 0;
18
+ return { value: o && o[i++], done: !o };
19
+ }
20
+ };
21
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
22
+ };
23
+ import { AcGePoint2d } from '../math/AcGePoint2d';
24
+ import { AcGeMathUtil, AcGeTol, TAU } from '../util';
25
+ import { AcGeCircArc2d } from './AcGeCircArc2d';
26
+ import { AcGePolyline2d } from './AcGePolyline2d';
27
+ /** Epsilon for circle–circle intersection and tangency tests during arc joins. */
28
+ var ROUND_JOIN_POINT_EPSILON = 1e-6;
29
+ /**
30
+ * Creates offset curves for a 2D polyline in the XY plane.
31
+ *
32
+ * Routing depends on polyline content:
33
+ * - Polylines with bulge (arc) segments use analytic line/arc offset and custom joins.
34
+ * - Vertex-only polylines (open or closed) use parallel edge offset with miter intersections.
35
+ *
36
+ * Sign convention: positive `offsetDist` offsets to the left of the travel direction
37
+ * along each segment (consistent with AutoCAD-style polyline offset).
38
+ *
39
+ * @param polyline - Source polyline; may be open or closed, with or without bulge arcs
40
+ * @param offsetDist - Signed offset distance in drawing units (left = positive)
41
+ * @returns One or more offset polylines; empty array when offsetting fails or input is degenerate
42
+ */
43
+ export function offsetAcGePolyline2d(polyline, offsetDist) {
44
+ if (hasArcSegments(polyline)) {
45
+ var result_1 = offsetPolylineWithArcSegments(polyline, offsetDist);
46
+ return result_1 ? [result_1] : [];
47
+ }
48
+ var source = normalizeVertexPolyline(polyline);
49
+ var n = source.numberOfVertices;
50
+ if (n < 2)
51
+ return [];
52
+ var result = createVertexOnlyPolylineOffset(source, offsetDist);
53
+ return result ? [result] : [];
54
+ }
55
+ /**
56
+ * Normalizes a vertex-only polyline before offset or other geometric processing.
57
+ *
58
+ * Removes consecutive duplicate vertices and, for closed paths, drops a closing
59
+ * vertex that coincides with the first vertex so downstream algorithms see a
60
+ * minimal vertex ring.
61
+ *
62
+ * @param polyline - Input polyline (typically straight segments only)
63
+ * @returns The same instance if already normalized, otherwise a new polyline with cleaned vertices
64
+ */
65
+ export function preparePolylineForOffset(polyline) {
66
+ return normalizeVertexPolyline(polyline);
67
+ }
68
+ /**
69
+ * Offsets a polyline that contains one or more bulge-defined arc segments.
70
+ *
71
+ * Each segment is offset individually (parallel line or concentric arc), then
72
+ * segment ends are joined at original vertices with miter, tangent, or round
73
+ * fallbacks as appropriate.
74
+ *
75
+ * @param polyline - Source polyline with at least one non-zero bulge
76
+ * @param offsetDist - Signed offset distance in drawing units
77
+ * @returns A single closed or open offset polyline, or `null` if any segment offset or join fails
78
+ */
79
+ function offsetPolylineWithArcSegments(polyline, offsetDist) {
80
+ var e_1, _a;
81
+ var segments = collectPlanarSegments(polyline);
82
+ if (segments.length === 0)
83
+ return null;
84
+ var offsetSegments = [];
85
+ try {
86
+ for (var segments_1 = __values(segments), segments_1_1 = segments_1.next(); !segments_1_1.done; segments_1_1 = segments_1.next()) {
87
+ var segment = segments_1_1.value;
88
+ var offsetSegment = offsetPlanarSegment(segment, offsetDist);
89
+ if (!offsetSegment)
90
+ return null;
91
+ offsetSegments.push(offsetSegment);
92
+ }
93
+ }
94
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
95
+ finally {
96
+ try {
97
+ if (segments_1_1 && !segments_1_1.done && (_a = segments_1.return)) _a.call(segments_1);
98
+ }
99
+ finally { if (e_1) throw e_1.error; }
100
+ }
101
+ var joinVertices = collectJoinVertices(polyline);
102
+ var points = buildJoinedOffsetPath(offsetSegments, joinVertices, polyline.closed, offsetDist);
103
+ if (points.length < 2)
104
+ return null;
105
+ var result = new AcGePolyline2d();
106
+ points.forEach(function (point, index) {
107
+ result.addVertexAt(index, { x: point.x, y: point.y });
108
+ });
109
+ result.closed = polyline.closed;
110
+ return result;
111
+ }
112
+ /**
113
+ * Decomposes a polyline into ordered planar segments in traversal order.
114
+ *
115
+ * Each edge from vertex `i` to `(i + 1) % n` becomes either a line chord (zero bulge)
116
+ * or a circular arc constructed from start vertex, end vertex, and bulge.
117
+ *
118
+ * @param polyline - Source polyline
119
+ * @returns Array of line and arc segments; empty when the polyline has fewer than two vertices (open) or one vertex (closed)
120
+ */
121
+ function collectPlanarSegments(polyline) {
122
+ var _a;
123
+ var segments = [];
124
+ var count = polyline.numberOfVertices;
125
+ var segmentCount = polyline.closed ? count : count - 1;
126
+ for (var i = 0; i < segmentCount; i++) {
127
+ var start = polyline.vertices[i];
128
+ var end = polyline.vertices[(i + 1) % count];
129
+ if (AcGeTol.isPositive(Math.abs((_a = start.bulge) !== null && _a !== void 0 ? _a : 0))) {
130
+ segments.push({
131
+ kind: 'arc',
132
+ arc: new AcGeCircArc2d(start, end, start.bulge)
133
+ });
134
+ }
135
+ else {
136
+ segments.push({
137
+ kind: 'line',
138
+ start: new AcGePoint2d(start.x, start.y),
139
+ end: new AcGePoint2d(end.x, end.y)
140
+ });
141
+ }
142
+ }
143
+ return segments;
144
+ }
145
+ /**
146
+ * Collects the 2D positions of all polyline vertices in index order.
147
+ *
148
+ * Used as join anchors when connecting offset segments at original corners.
149
+ *
150
+ * @param polyline - Source polyline
151
+ * @returns Clone-free point list matching `polyline.numberOfVertices` entries
152
+ */
153
+ function collectJoinVertices(polyline) {
154
+ var vertices = [];
155
+ for (var i = 0; i < polyline.numberOfVertices; i++) {
156
+ vertices.push(polyline.getPointAt(i));
157
+ }
158
+ return vertices;
159
+ }
160
+ /**
161
+ * Offsets a single planar segment by the signed distance.
162
+ *
163
+ * @param segment - Line chord or bulge arc segment
164
+ * @param offsetDist - Signed offset distance (left of travel = positive)
165
+ * @returns Offset line (with direction vector) or offset arc, or `null` if degenerate
166
+ */
167
+ function offsetPlanarSegment(segment, offsetDist) {
168
+ if (segment.kind === 'line') {
169
+ var offsetLine = offsetLineSegment(segment.start, segment.end, offsetDist);
170
+ if (!offsetLine)
171
+ return null;
172
+ return __assign({ kind: 'line' }, offsetLine);
173
+ }
174
+ var offsetArc = offsetCircArc2d(segment.arc, segment.arc.startPoint, segment.arc.endPoint, offsetDist);
175
+ if (!offsetArc)
176
+ return null;
177
+ return { kind: 'arc', arc: offsetArc };
178
+ }
179
+ /**
180
+ * Builds a concentric circular arc at the requested offset from a source arc.
181
+ *
182
+ * Radius change follows the same left-hand rule as line offset via {@link getArcRadiusDelta}.
183
+ *
184
+ * @param arc - Source circular arc
185
+ * @param start - Start point of the arc segment (chord start)
186
+ * @param end - End point of the arc segment (chord end)
187
+ * @param offsetDist - Signed offset distance
188
+ * @returns New arc sharing center and angles, or `null` if the offset radius is non-positive
189
+ */
190
+ function offsetCircArc2d(arc, start, end, offsetDist) {
191
+ var radiusDelta = getArcRadiusDelta(arc, start, end, offsetDist);
192
+ var radius = arc.radius + radiusDelta;
193
+ if (AcGeTol.isNonPositive(radius))
194
+ return null;
195
+ return new AcGeCircArc2d(arc.center, radius, arc.startAngle, arc.endAngle, arc.clockwise);
196
+ }
197
+ /**
198
+ * Computes the signed radius change for a concentric arc offset.
199
+ *
200
+ * Applies the same left-hand offset rule used for line segments: positive
201
+ * `offsetDist` shifts the arc to the left of its travel direction.
202
+ *
203
+ * Concentric offset direction depends on which side of the chord the center
204
+ * lies: when the center is on the left, shrink the radius; when on the right,
205
+ * expand it. Degenerate zero-length chords fall back to clockwise flag only.
206
+ *
207
+ * @param arc - Source circular arc
208
+ * @param start - Chord start point
209
+ * @param end - Chord end point
210
+ * @param offsetDist - Signed offset distance
211
+ * @returns Signed delta to add to `arc.radius` (not the new radius itself)
212
+ */
213
+ function getArcRadiusDelta(arc, start, end, offsetDist) {
214
+ var dx = end.x - start.x;
215
+ var dy = end.y - start.y;
216
+ var len = Math.hypot(dx, dy);
217
+ if (AcGeTol.isNonPositive(len)) {
218
+ return arc.clockwise ? -offsetDist : offsetDist;
219
+ }
220
+ var leftNx = -dy / len;
221
+ var leftNy = dx / len;
222
+ var toCenterX = arc.center.x - (start.x + end.x) / 2;
223
+ var toCenterY = arc.center.y - (start.y + end.y) / 2;
224
+ var centerOnLeft = toCenterX * leftNx + toCenterY * leftNy > 0;
225
+ return centerOnLeft ? -offsetDist : offsetDist;
226
+ }
227
+ /**
228
+ * Offsets a finite line segment parallel to itself.
229
+ *
230
+ * The offset direction is the left normal scaled by `offsetDist`. The returned
231
+ * `dx`/`dy` match the original edge direction for use in later miter joins.
232
+ *
233
+ * @param start - Segment start
234
+ * @param end - Segment end
235
+ * @param offsetDist - Signed offset distance
236
+ * @returns Offset endpoints plus original direction components, or `null` if the segment length is zero
237
+ */
238
+ function offsetLineSegment(start, end, offsetDist) {
239
+ var dx = end.x - start.x;
240
+ var dy = end.y - start.y;
241
+ var len = Math.hypot(dx, dy);
242
+ if (AcGeTol.isNonPositive(len))
243
+ return null;
244
+ var nx = (-dy / len) * offsetDist;
245
+ var ny = (dx / len) * offsetDist;
246
+ return {
247
+ start: new AcGePoint2d(start.x + nx, start.y + ny),
248
+ end: new AcGePoint2d(end.x + nx, end.y + ny),
249
+ dx: dx,
250
+ dy: dy
251
+ };
252
+ }
253
+ /**
254
+ * Assembles a continuous polyline path from offset segments and per-vertex join data.
255
+ *
256
+ * Walks segments in order, emitting join points, optional round fillet samples,
257
+ * and interior samples along offset arcs between join limits.
258
+ *
259
+ * @param offsetSegments - Per-edge offset geometry in path order
260
+ * @param joinVertices - Original polyline vertices used as join anchors
261
+ * @param closed - Whether the path wraps from last segment back to first
262
+ * @param offsetDist - Signed offset distance (used for round-join radius)
263
+ * @returns Ordered vertex chain with consecutive duplicates removed
264
+ */
265
+ function buildJoinedOffsetPath(offsetSegments, joinVertices, closed, offsetDist) {
266
+ var _a, _b, _c;
267
+ var count = offsetSegments.length;
268
+ if (count === 0)
269
+ return [];
270
+ var joins = computeSegmentJoins(offsetSegments, joinVertices, closed, offsetDist);
271
+ var points = [
272
+ (_b = (_a = joins[0].nextSegmentStart) === null || _a === void 0 ? void 0 : _a.clone()) !== null && _b !== void 0 ? _b : joins[0].point.clone()
273
+ ];
274
+ for (var i = 0; i < count; i++) {
275
+ var segment = offsetSegments[i];
276
+ var joinAtEnd = i === count - 1 && !closed
277
+ ? {
278
+ point: getOffsetSegmentEnd(segment)
279
+ }
280
+ : joins[(i + 1) % count];
281
+ if (i > 0 && joins[i].filletPoints) {
282
+ joins[i].filletPoints.forEach(function (point) { return points.push(point.clone()); });
283
+ }
284
+ if (segment.kind === 'arc') {
285
+ var arcStart = (_c = joins[i].nextSegmentStart) !== null && _c !== void 0 ? _c : joins[i].point;
286
+ var arcEnd = joinAtEnd.point;
287
+ var arcSamples = sampleArcBetweenPoints(segment.arc, arcStart, arcEnd, Math.max(16, 32));
288
+ for (var j = 1; j < arcSamples.length; j++) {
289
+ points.push(arcSamples[j]);
290
+ }
291
+ }
292
+ else {
293
+ points.push(joinAtEnd.point.clone());
294
+ }
295
+ }
296
+ return dedupeConsecutivePoints(points);
297
+ }
298
+ /**
299
+ * Computes join information at every vertex between consecutive offset segments.
300
+ *
301
+ * For closed polylines, index `0` joins the last and first offset segments at
302
+ * `joinVertices[0]`. For open polylines, the start uses the first segment's
303
+ * offset start without a prior join.
304
+ *
305
+ * @param offsetSegments - Offset segments in path order
306
+ * @param joinVertices - Original corner vertices
307
+ * @param closed - Whether the polyline is closed
308
+ * @param offsetDist - Signed offset distance for round joins
309
+ * @returns One {@link SegmentJoinInfo} per segment, aligned with segment indices
310
+ */
311
+ function computeSegmentJoins(offsetSegments, joinVertices, closed, offsetDist) {
312
+ var count = offsetSegments.length;
313
+ var joins = [];
314
+ for (var i = 0; i < count; i++) {
315
+ if (i === 0) {
316
+ joins.push(closed
317
+ ? joinOffsetSegments(offsetSegments[count - 1], offsetSegments[0], joinVertices[0], offsetDist)
318
+ : { point: getOffsetSegmentStart(offsetSegments[0]) });
319
+ }
320
+ else {
321
+ joins.push(joinOffsetSegments(offsetSegments[i - 1], offsetSegments[i], joinVertices[i], offsetDist));
322
+ }
323
+ }
324
+ return joins;
325
+ }
326
+ /**
327
+ * Samples points along a circular arc between two arbitrary points on the arc.
328
+ *
329
+ * Projects `start` and `end` onto the arc, measures sweep in internal angle
330
+ * space, and interpolates at uniform parameter steps. Endpoints are included.
331
+ *
332
+ * @param arc - Circular arc to sample
333
+ * @param start - Approximate start of the sampled span (need not lie exactly on the arc)
334
+ * @param end - Approximate end of the sampled span
335
+ * @param numPoints - Number of interior divisions (total samples = `numPoints + 1`)
336
+ * @returns Polyline of sampled points from start to end along the arc
337
+ */
338
+ function sampleArcBetweenPoints(arc, start, end, numPoints) {
339
+ var startOnArc = arc.nearestPoint(start);
340
+ var endOnArc = arc.nearestPoint(end);
341
+ var startInternal = internalAngleAtPoint(arc, startOnArc);
342
+ var endInternal = internalAngleAtPoint(arc, endOnArc);
343
+ var sweep = sweepAlongArc(arc, startInternal, endInternal);
344
+ var points = [];
345
+ for (var i = 0; i <= numPoints; i++) {
346
+ var t = i / numPoints;
347
+ var internalAngle = arc.clockwise
348
+ ? startInternal - sweep * t
349
+ : startInternal + sweep * t;
350
+ points.push(arc.getPointAtAngle(publicAngleFromInternal(arc, internalAngle)));
351
+ }
352
+ return points;
353
+ }
354
+ /**
355
+ * Returns the mathematical polar angle from the arc center to a point.
356
+ *
357
+ * @param arc - Circular arc providing the center
358
+ * @param point - Point in the arc plane
359
+ * @returns Angle in radians in `[-π, π]`, consistent with `Math.atan2`
360
+ */
361
+ function internalAngleAtPoint(arc, point) {
362
+ return Math.atan2(point.y - arc.center.y, point.x - arc.center.x);
363
+ }
364
+ /**
365
+ * Converts an internal (mathematical) angle to the public angle used by {@link AcGeCircArc2d}.
366
+ *
367
+ * Counterclockwise arcs use normalized mathematical angles; clockwise arcs apply
368
+ * the arc's mirrored angle convention.
369
+ *
370
+ * @param arc - Circular arc defining clockwise vs counterclockwise storage
371
+ * @param internalAngle - Angle in internal/mathematical space
372
+ * @returns Angle suitable for `arc.getPointAtAngle`
373
+ */
374
+ function publicAngleFromInternal(arc, internalAngle) {
375
+ var normalized = AcGeMathUtil.normalizeAngle(internalAngle);
376
+ return arc.clockwise ? mirrorAngle(normalized) : normalized;
377
+ }
378
+ /**
379
+ * Mirrors a mathematical angle into the clockwise arc angle convention.
380
+ *
381
+ * @param angle - Angle in radians (mathematical convention)
382
+ * @returns Mirrored angle in radians for clockwise arc parameterization
383
+ */
384
+ function mirrorAngle(angle) {
385
+ var degrees = (angle * 180) / Math.PI;
386
+ return ((360 - degrees) % 360) * (Math.PI / 180);
387
+ }
388
+ /**
389
+ * Computes the positive sweep from one internal angle to another along the arc.
390
+ *
391
+ * @param arc - Circular arc (direction from `clockwise`)
392
+ * @param fromInternal - Start internal angle
393
+ * @param toInternal - End internal angle
394
+ * @returns Positive sweep magnitude in radians along the arc direction
395
+ */
396
+ function sweepAlongArc(arc, fromInternal, toInternal) {
397
+ if (arc.clockwise) {
398
+ var sweep_1 = fromInternal - toInternal;
399
+ if (AcGeTol.isNonPositive(sweep_1)) {
400
+ sweep_1 += TAU;
401
+ }
402
+ return sweep_1;
403
+ }
404
+ var sweep = toInternal - fromInternal;
405
+ if (AcGeTol.isNonPositive(sweep)) {
406
+ sweep += TAU;
407
+ }
408
+ return sweep;
409
+ }
410
+ /**
411
+ * Returns the start point of an offset segment in path order.
412
+ *
413
+ * @param segment - Line or arc offset segment
414
+ * @returns Cloned start point of the offset geometry
415
+ */
416
+ function getOffsetSegmentStart(segment) {
417
+ return segment.kind === 'line'
418
+ ? segment.start.clone()
419
+ : segment.arc.startPoint.clone();
420
+ }
421
+ /**
422
+ * Returns the end point of an offset segment in path order.
423
+ *
424
+ * @param segment - Line or arc offset segment
425
+ * @returns Cloned end point of the offset geometry
426
+ */
427
+ function getOffsetSegmentEnd(segment) {
428
+ return segment.kind === 'line'
429
+ ? segment.end.clone()
430
+ : segment.arc.endPoint.clone();
431
+ }
432
+ /**
433
+ * Resolves the connection between two consecutive offset segments at a corner vertex.
434
+ *
435
+ * Dispatches to line–line miter, line–arc, arc–line, or arc–arc join logic.
436
+ * Falls back to the original vertex when segment kinds are unexpected.
437
+ *
438
+ * @param previous - Offset segment entering the vertex
439
+ * @param next - Offset segment leaving the vertex
440
+ * @param vertex - Original polyline corner
441
+ * @param offsetDist - Signed offset distance (magnitude used for round joins)
442
+ * @returns Join point and optional fillet bridging to the next segment
443
+ */
444
+ function joinOffsetSegments(previous, next, vertex, offsetDist) {
445
+ if (previous.kind === 'line' && next.kind === 'line') {
446
+ return {
447
+ point: intersectPolylineOffsetSegments({
448
+ start: previous.start,
449
+ end: previous.end,
450
+ dx: previous.dx,
451
+ dy: previous.dy
452
+ }, {
453
+ start: next.start,
454
+ end: next.end,
455
+ dx: next.dx,
456
+ dy: next.dy
457
+ })
458
+ };
459
+ }
460
+ if (previous.kind === 'line' && next.kind === 'arc') {
461
+ return joinLineWithArcOffset(previous, next.arc, vertex, offsetDist, true);
462
+ }
463
+ if (previous.kind === 'arc' && next.kind === 'line') {
464
+ return joinLineWithArcOffset(next, previous.arc, vertex, offsetDist, false);
465
+ }
466
+ if (previous.kind === 'arc' && next.kind === 'arc') {
467
+ return joinArcWithArcOffset(previous.arc, next.arc, vertex, offsetDist);
468
+ }
469
+ return { point: vertex.clone() };
470
+ }
471
+ /**
472
+ * Joins two offset circular arcs meeting at a common vertex.
473
+ *
474
+ * Prefers a point on both offset arcs near the gap midpoint (circle–circle
475
+ * intersection). If none exists, inserts a round join sampled around the vertex.
476
+ *
477
+ * @param previous - Offset arc ending at the vertex
478
+ * @param next - Offset arc starting at the vertex
479
+ * @param vertex - Original corner vertex
480
+ * @param offsetDist - Signed offset distance (absolute value = fillet radius)
481
+ * @returns Miter point on both arcs, or round join with separate segment endpoints
482
+ */
483
+ function joinArcWithArcOffset(previous, next, vertex, offsetDist) {
484
+ var near = midpoint(previous.endPoint, next.startPoint);
485
+ var candidates = intersectOffsetCircles(previous, next);
486
+ var onBoth = candidates.filter(function (candidate) {
487
+ return isPointOnArc(previous, candidate) && isPointOnArc(next, candidate);
488
+ });
489
+ if (onBoth.length > 0) {
490
+ return { point: pickClosestPoint(onBoth, near) };
491
+ }
492
+ var from = previous.endPoint.clone();
493
+ var to = next.startPoint.clone();
494
+ var filletPoints = sampleVertexRoundJoin(vertex, Math.abs(offsetDist), from, to);
495
+ return {
496
+ point: from,
497
+ filletPoints: filletPoints,
498
+ nextSegmentStart: to
499
+ };
500
+ }
501
+ /**
502
+ * Joins an offset line segment with an offset arc at a shared vertex.
503
+ *
504
+ * @param line - Offset line segment (includes direction `dx`/`dy`)
505
+ * @param arc - Offset circular arc on the other side of the vertex
506
+ * @param vertex - Original corner vertex
507
+ * @param offsetDist - Signed offset distance
508
+ * @param lineBeforeArc - `true` if `line` is the incoming segment and `arc` is outgoing
509
+ * @returns Intersection on both primitives, or round join with optional `nextSegmentStart`
510
+ */
511
+ function joinLineWithArcOffset(line, arc, vertex, offsetDist, lineBeforeArc) {
512
+ var near = midpoint(lineBeforeArc ? line.end : line.start, lineBeforeArc ? arc.startPoint : arc.endPoint);
513
+ var intersection = intersectLineWithCircle(line, arc, near);
514
+ if (isPointOnOffsetLine(line, intersection) &&
515
+ isPointOnArc(arc, intersection)) {
516
+ return { point: intersection };
517
+ }
518
+ var lineSide = getLineOffsetPointAtVertex(line, vertex, offsetDist);
519
+ var arcSide = lineBeforeArc ? arc.startPoint.clone() : arc.endPoint.clone();
520
+ var filletPoints = sampleVertexRoundJoin(vertex, Math.abs(offsetDist), lineBeforeArc ? lineSide : arcSide, lineBeforeArc ? arcSide : lineSide);
521
+ if (lineBeforeArc) {
522
+ return {
523
+ point: lineSide,
524
+ filletPoints: filletPoints,
525
+ nextSegmentStart: arcSide
526
+ };
527
+ }
528
+ return {
529
+ point: arcSide,
530
+ filletPoints: filletPoints,
531
+ nextSegmentStart: lineSide
532
+ };
533
+ }
534
+ /**
535
+ * Computes the offset line point closest to a vertex along the left normal.
536
+ *
537
+ * @param line - Offset line with original edge direction `dx`/`dy`
538
+ * @param vertex - Corner on the source polyline
539
+ * @param offsetDist - Signed offset distance
540
+ * @returns Point on the infinite offset line through the vertex
541
+ */
542
+ function getLineOffsetPointAtVertex(line, vertex, offsetDist) {
543
+ var len = Math.hypot(line.dx, line.dy);
544
+ var nx = (-line.dy / len) * offsetDist;
545
+ var ny = (line.dx / len) * offsetDist;
546
+ return new AcGePoint2d(vertex.x + nx, vertex.y + ny);
547
+ }
548
+ /**
549
+ * Tests whether a point lies on the infinite line supporting an offset segment.
550
+ *
551
+ * @param line - Offset line with start and direction
552
+ * @param point - Candidate point
553
+ * @returns `true` if perpendicular distance to the line is within tolerance
554
+ */
555
+ function isPointOnOffsetLine(line, point) {
556
+ var len = Math.hypot(line.dx, line.dy);
557
+ if (AcGeTol.isNonPositive(len))
558
+ return false;
559
+ var ux = line.dx / len;
560
+ var uy = line.dy / len;
561
+ var px = point.x - line.start.x;
562
+ var py = point.y - line.start.y;
563
+ var perp = Math.abs(px * uy - py * ux);
564
+ return AcGeTol.equalToZero(perp);
565
+ }
566
+ /**
567
+ * Samples interior points of a circular round join at a vertex.
568
+ *
569
+ * Arc spans the shorter rotation from `from` to `to` about `vertex` at `radius`.
570
+ * Endpoints are excluded from the returned list.
571
+ *
572
+ * @param vertex - Center of the round join
573
+ * @param radius - Fillet radius (positive)
574
+ * @param from - Start direction point on the fillet circle
575
+ * @param to - End direction point on the fillet circle
576
+ * @returns Interior fillet samples; empty when start and end directions coincide
577
+ */
578
+ function sampleVertexRoundJoin(vertex, radius, from, to) {
579
+ var a1 = Math.atan2(from.y - vertex.y, from.x - vertex.x);
580
+ var a2 = Math.atan2(to.y - vertex.y, to.x - vertex.x);
581
+ var sweep = shortestAngleSweep(a1, a2);
582
+ if (AcGeTol.equalToZero(Math.abs(sweep))) {
583
+ return [];
584
+ }
585
+ var points = [];
586
+ var steps = Math.max(2, Math.ceil((Math.abs(sweep) / Math.PI) * 16));
587
+ for (var i = 1; i < steps; i++) {
588
+ var t = i / steps;
589
+ var angle = a1 + sweep * t;
590
+ points.push(new AcGePoint2d(vertex.x + radius * Math.cos(angle), vertex.y + radius * Math.sin(angle)));
591
+ }
592
+ return points;
593
+ }
594
+ /**
595
+ * Returns the shortest signed angular sweep from one direction to another.
596
+ *
597
+ * @param from - Start angle in radians
598
+ * @param to - End angle in radians
599
+ * @returns Signed sweep in `(-π, π]`
600
+ */
601
+ function shortestAngleSweep(from, to) {
602
+ var sweep = to - from;
603
+ while (sweep > Math.PI) {
604
+ sweep -= TAU;
605
+ }
606
+ while (sweep <= -Math.PI) {
607
+ sweep += TAU;
608
+ }
609
+ return sweep;
610
+ }
611
+ /**
612
+ * Intersects an infinite offset line with a circle (offset arc boundary).
613
+ *
614
+ * @param line - Offset line segment defining origin and direction
615
+ * @param arc - Circular arc whose radius and center define the circle
616
+ * @param near - Preference point when choosing among multiple intersections
617
+ * @returns Closest valid intersection to `near`, or `near` when no real roots exist
618
+ */
619
+ function intersectLineWithCircle(line, arc, near) {
620
+ var dirLen = Math.hypot(line.dx, line.dy);
621
+ if (AcGeTol.isNonPositive(dirLen)) {
622
+ return near.clone();
623
+ }
624
+ var ux = line.dx / dirLen;
625
+ var uy = line.dy / dirLen;
626
+ var fx = line.start.x - arc.center.x;
627
+ var fy = line.start.y - arc.center.y;
628
+ var b = 2 * (fx * ux + fy * uy);
629
+ var c = fx * fx + fy * fy - arc.radius * arc.radius;
630
+ var discriminant = b * b - 4 * c;
631
+ if (discriminant < 0) {
632
+ return near.clone();
633
+ }
634
+ var sqrtDisc = Math.sqrt(discriminant);
635
+ var candidates = [(-b - sqrtDisc) / 2, (-b + sqrtDisc) / 2].map(function (t) { return new AcGePoint2d(line.start.x + ux * t, line.start.y + uy * t); });
636
+ var onArc = candidates.filter(function (candidate) { return isPointOnArc(arc, candidate); });
637
+ return pickClosestPoint(onArc.length > 0 ? onArc : candidates, near);
638
+ }
639
+ /**
640
+ * Tests whether a point lies on a circular arc segment (not merely on the full circle).
641
+ *
642
+ * @param arc - Circular arc with defined start/end and clockwise flag
643
+ * @param point - Candidate point
644
+ * @returns `true` if distance to center matches radius and angle is within the arc span
645
+ */
646
+ function isPointOnArc(arc, point) {
647
+ var radiusDelta = Math.abs(Math.hypot(point.x - arc.center.x, point.y - arc.center.y) - arc.radius);
648
+ if (AcGeTol.isPositive(radiusDelta)) {
649
+ return false;
650
+ }
651
+ var internalAngle = internalAngleAtPoint(arc, point);
652
+ var internalStart = arc.clockwise
653
+ ? mirrorAngle(AcGeMathUtil.normalizeAngle(arc.startAngle))
654
+ : AcGeMathUtil.normalizeAngle(arc.startAngle);
655
+ var internalEnd = arc.clockwise
656
+ ? mirrorAngle(AcGeMathUtil.normalizeAngle(arc.endAngle))
657
+ : AcGeMathUtil.normalizeAngle(arc.endAngle);
658
+ return AcGeMathUtil.isBetweenAngle(internalAngle, internalStart, internalEnd, arc.clockwise);
659
+ }
660
+ /**
661
+ * Intersects two circles defined by offset arc centers and radii.
662
+ *
663
+ * @param a - First offset arc (circle at `a.center` with radius `a.radius`)
664
+ * @param b - Second offset arc
665
+ * @returns Zero, one (tangent), or two intersection points; empty when circles are separated or nested beyond tolerance
666
+ */
667
+ function intersectOffsetCircles(a, b) {
668
+ var dx = b.center.x - a.center.x;
669
+ var dy = b.center.y - a.center.y;
670
+ var dist = Math.hypot(dx, dy);
671
+ if (AcGeTol.isNonPositive(dist)) {
672
+ return [];
673
+ }
674
+ var radiusSum = a.radius + b.radius;
675
+ var radiusDiff = Math.abs(a.radius - b.radius);
676
+ if (dist > radiusSum + ROUND_JOIN_POINT_EPSILON) {
677
+ return [];
678
+ }
679
+ if (dist < radiusDiff - ROUND_JOIN_POINT_EPSILON) {
680
+ return [];
681
+ }
682
+ var aTerm = (a.radius * a.radius - b.radius * b.radius + dist * dist) / (2 * dist);
683
+ var hSquared = a.radius * a.radius - aTerm * aTerm;
684
+ if (hSquared < -ROUND_JOIN_POINT_EPSILON) {
685
+ return [];
686
+ }
687
+ var mx = a.center.x + (aTerm * dx) / dist;
688
+ var my = a.center.y + (aTerm * dy) / dist;
689
+ if (hSquared <= ROUND_JOIN_POINT_EPSILON) {
690
+ return [new AcGePoint2d(mx, my)];
691
+ }
692
+ var h = Math.sqrt(hSquared);
693
+ var rx = (-dy * h) / dist;
694
+ var ry = (dx * h) / dist;
695
+ return [new AcGePoint2d(mx + rx, my + ry), new AcGePoint2d(mx - rx, my - ry)];
696
+ }
697
+ /**
698
+ * Selects the candidate point closest to a reference location.
699
+ *
700
+ * @param points - Non-empty list of candidates
701
+ * @param near - Reference point for distance comparison
702
+ * @returns Clone of the closest candidate
703
+ */
704
+ function pickClosestPoint(points, near) {
705
+ var best = points[0];
706
+ var bestDist = best.distanceToSquared(near);
707
+ for (var i = 1; i < points.length; i++) {
708
+ var dist = points[i].distanceToSquared(near);
709
+ if (dist < bestDist) {
710
+ best = points[i];
711
+ bestDist = dist;
712
+ }
713
+ }
714
+ return best.clone();
715
+ }
716
+ /**
717
+ * Computes the component-wise midpoint between two points.
718
+ *
719
+ * @param a - First point
720
+ * @param b - Second point
721
+ * @returns New point at `(a + b) / 2`
722
+ */
723
+ function midpoint(a, b) {
724
+ return new AcGePoint2d((a.x + b.x) / 2, (a.y + b.y) / 2);
725
+ }
726
+ /**
727
+ * Determines whether a polyline contains any bulge-defined arc segments.
728
+ *
729
+ * @param polyline - Polyline to inspect
730
+ * @returns `true` if any edge has a non-zero bulge within tolerance
731
+ */
732
+ function hasArcSegments(polyline) {
733
+ var _a;
734
+ var count = polyline.numberOfVertices;
735
+ var segmentCount = polyline.closed ? count : count - 1;
736
+ for (var i = 0; i < segmentCount; i++) {
737
+ var vertex = polyline.vertices[i];
738
+ if (AcGeTol.isPositive(Math.abs((_a = vertex.bulge) !== null && _a !== void 0 ? _a : 0))) {
739
+ return true;
740
+ }
741
+ }
742
+ return false;
743
+ }
744
+ /**
745
+ * Returns a vertex-only polyline with normalized path points.
746
+ *
747
+ * @param polyline - Input polyline
748
+ * @returns `polyline` unchanged when already normalized, else a new instance
749
+ */
750
+ function normalizeVertexPolyline(polyline) {
751
+ var points = normalizePathPoints(polyline.vertices.map(function (vertex) { return new AcGePoint2d(vertex.x, vertex.y); }), polyline.closed);
752
+ if (points.length === polyline.numberOfVertices) {
753
+ return polyline;
754
+ }
755
+ return new AcGePolyline2d(points.map(function (point) { return ({ x: point.x, y: point.y }); }), polyline.closed);
756
+ }
757
+ /**
758
+ * Deduplicates consecutive vertices and removes redundant closure for closed paths.
759
+ *
760
+ * @param points - Vertex chain in order
761
+ * @param closed - Whether the path is closed
762
+ * @returns Cleaned vertex list
763
+ */
764
+ function normalizePathPoints(points, closed) {
765
+ var deduped = dedupeConsecutivePoints(points);
766
+ if (!closed || deduped.length < 2)
767
+ return deduped;
768
+ var first = deduped[0];
769
+ var last = deduped[deduped.length - 1];
770
+ if (AcGeTol.isNonPositive(Math.hypot(first.x - last.x, first.y - last.y))) {
771
+ deduped.pop();
772
+ }
773
+ return deduped;
774
+ }
775
+ /**
776
+ * Removes consecutive vertices that coincide within geometric tolerance.
777
+ *
778
+ * @param points - Input vertex chain (may contain duplicates)
779
+ * @returns New array with only distinct consecutive points
780
+ */
781
+ function dedupeConsecutivePoints(points) {
782
+ var result = [];
783
+ points.forEach(function (point) {
784
+ var last = result[result.length - 1];
785
+ if (!last ||
786
+ AcGeTol.isPositive(Math.hypot(last.x - point.x, last.y - point.y))) {
787
+ result.push(new AcGePoint2d(point.x, point.y));
788
+ }
789
+ });
790
+ return result;
791
+ }
792
+ /**
793
+ * Offsets a vertex-only polyline (open or closed) composed of straight segments.
794
+ *
795
+ * Each edge is offset parallel to itself; consecutive offset lines are joined with
796
+ * miter intersections. Open paths keep raw offset starts/ends; closed paths join
797
+ * cyclically at every corner.
798
+ *
799
+ * @param polyline - Vertex-only polyline with at least two vertices
800
+ * @param offsetDist - Signed offset distance
801
+ * @returns Offset polyline, or `null` if no valid edges remain after skipping zero-length segments
802
+ */
803
+ function createVertexOnlyPolylineOffset(polyline, offsetDist) {
804
+ var sourcePoints = [];
805
+ for (var i = 0; i < polyline.numberOfVertices; i++) {
806
+ sourcePoints.push(polyline.getPointAt(i));
807
+ }
808
+ var offsetSegments = buildPolylineOffsetSegments(sourcePoints, polyline.closed, offsetDist);
809
+ if (offsetSegments.length === 0)
810
+ return null;
811
+ var resultPoints = [];
812
+ if (polyline.closed) {
813
+ if (offsetSegments.length < 2)
814
+ return null;
815
+ var count = offsetSegments.length;
816
+ for (var i = 0; i < count; i++) {
817
+ var prev = offsetSegments[(i - 1 + count) % count];
818
+ var curr = offsetSegments[i];
819
+ resultPoints.push(intersectPolylineOffsetSegments(prev, curr));
820
+ }
821
+ }
822
+ else {
823
+ resultPoints.push(offsetSegments[0].start);
824
+ for (var i = 1; i < offsetSegments.length; i++) {
825
+ resultPoints.push(intersectPolylineOffsetSegments(offsetSegments[i - 1], offsetSegments[i]));
826
+ }
827
+ resultPoints.push(offsetSegments[offsetSegments.length - 1].end);
828
+ }
829
+ var result = new AcGePolyline2d();
830
+ resultPoints.forEach(function (point, index) {
831
+ result.addVertexAt(index, { x: point.x, y: point.y });
832
+ });
833
+ result.closed = polyline.closed;
834
+ return result;
835
+ }
836
+ /**
837
+ * Builds parallel offset segments for each non-degenerate edge of a vertex path.
838
+ *
839
+ * @param sourcePoints - Vertices in path order
840
+ * @param closed - Whether edges wrap from the last vertex back to the first
841
+ * @param offsetDist - Signed offset distance (left of travel = positive)
842
+ * @returns One offset segment per valid edge, in path order
843
+ */
844
+ function buildPolylineOffsetSegments(sourcePoints, closed, offsetDist) {
845
+ var offsetSegments = [];
846
+ var edgeCount = closed ? sourcePoints.length : sourcePoints.length - 1;
847
+ for (var i = 0; i < edgeCount; i++) {
848
+ var start = sourcePoints[i];
849
+ var end = sourcePoints[closed ? (i + 1) % sourcePoints.length : i + 1];
850
+ var dx = end.x - start.x;
851
+ var dy = end.y - start.y;
852
+ var len = Math.hypot(dx, dy);
853
+ if (AcGeTol.isNonPositive(len))
854
+ continue;
855
+ var nx = (-dy / len) * offsetDist;
856
+ var ny = (dx / len) * offsetDist;
857
+ offsetSegments.push({
858
+ start: new AcGePoint2d(start.x + nx, start.y + ny),
859
+ end: new AcGePoint2d(end.x + nx, end.y + ny),
860
+ dx: dx,
861
+ dy: dy
862
+ });
863
+ }
864
+ return offsetSegments;
865
+ }
866
+ /**
867
+ * Computes the intersection of two infinite offset lines from consecutive polyline edges.
868
+ *
869
+ * Each segment supplies a point on the line (`start`) and direction (`dx`, `dy`).
870
+ * Parallel edges return the midpoint of the gap between segment ends as a fallback.
871
+ *
872
+ * @param a - First offset edge
873
+ * @param b - Second offset edge
874
+ * @returns Intersection point (miter) of the two supporting lines
875
+ */
876
+ function intersectPolylineOffsetSegments(a, b) {
877
+ var det = a.dx * b.dy - a.dy * b.dx;
878
+ if (AcGeTol.equalToZero(det)) {
879
+ return new AcGePoint2d((a.end.x + b.start.x) / 2, (a.end.y + b.start.y) / 2);
880
+ }
881
+ var qpx = b.start.x - a.start.x;
882
+ var qpy = b.start.y - a.start.y;
883
+ var t = (qpx * b.dy - qpy * b.dx) / det;
884
+ return new AcGePoint2d(a.start.x + t * a.dx, a.start.y + t * a.dy);
885
+ }
886
+ //# sourceMappingURL=AcGePolyline2dOffset.js.map