okgeometry-api 1.26.1 → 1.27.0

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 (99) hide show
  1. package/dist/Arc.js +1 -1
  2. package/dist/Arc.js.map +1 -1
  3. package/dist/Brep.js +1 -1
  4. package/dist/Brep.js.map +1 -1
  5. package/dist/Circle.js +1 -1
  6. package/dist/Circle.js.map +1 -1
  7. package/dist/Line.js +1 -1
  8. package/dist/Line.js.map +1 -1
  9. package/dist/Mesh.d.ts +41 -0
  10. package/dist/Mesh.d.ts.map +1 -1
  11. package/dist/Mesh.js +235 -21
  12. package/dist/Mesh.js.map +1 -1
  13. package/dist/MeshSurface.js +1 -1
  14. package/dist/MeshSurface.js.map +1 -1
  15. package/dist/NurbsCurve.js +1 -1
  16. package/dist/NurbsCurve.js.map +1 -1
  17. package/dist/NurbsSurface.js +1 -1
  18. package/dist/NurbsSurface.js.map +1 -1
  19. package/dist/PolyCurve.js +1 -1
  20. package/dist/PolyCurve.js.map +1 -1
  21. package/dist/Polyline.js +1 -1
  22. package/dist/Polyline.js.map +1 -1
  23. package/dist/Ray.js +1 -1
  24. package/dist/Ray.js.map +1 -1
  25. package/dist/SculptSession.js +1 -1
  26. package/dist/SculptSession.js.map +1 -1
  27. package/dist/engine-threaded.d.ts +2 -0
  28. package/dist/engine-threaded.d.ts.map +1 -0
  29. package/dist/engine-threaded.js +33 -0
  30. package/dist/engine-threaded.js.map +1 -0
  31. package/dist/engine.d.ts +21 -0
  32. package/dist/engine.d.ts.map +1 -1
  33. package/dist/engine.js +42 -0
  34. package/dist/engine.js.map +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/mesh-boolean.pool.d.ts +23 -1
  40. package/dist/mesh-boolean.pool.d.ts.map +1 -1
  41. package/dist/mesh-boolean.pool.js +97 -12
  42. package/dist/mesh-boolean.pool.js.map +1 -1
  43. package/dist/mesh-boolean.protocol.d.ts +21 -1
  44. package/dist/mesh-boolean.protocol.d.ts.map +1 -1
  45. package/dist/mesh-boolean.protocol.js.map +1 -1
  46. package/dist/mesh-boolean.worker.d.ts.map +1 -1
  47. package/dist/mesh-boolean.worker.js +94 -1
  48. package/dist/mesh-boolean.worker.js.map +1 -1
  49. package/dist/wasm-active.d.ts +20 -0
  50. package/dist/wasm-active.d.ts.map +1 -0
  51. package/dist/wasm-active.js +26 -0
  52. package/dist/wasm-active.js.map +1 -0
  53. package/dist/wasm-base64-threaded.d.ts +2 -0
  54. package/dist/wasm-base64-threaded.d.ts.map +1 -0
  55. package/dist/wasm-base64-threaded.js +3 -0
  56. package/dist/wasm-base64-threaded.js.map +1 -0
  57. package/dist/wasm-base64.d.ts +1 -1
  58. package/dist/wasm-base64.d.ts.map +1 -1
  59. package/dist/wasm-base64.js +1 -1
  60. package/dist/wasm-base64.js.map +1 -1
  61. package/dist/wasm-bindings-threaded.d.ts +2664 -0
  62. package/dist/wasm-bindings-threaded.d.ts.map +1 -0
  63. package/dist/wasm-bindings-threaded.js +5008 -0
  64. package/dist/wasm-bindings-threaded.js.map +1 -0
  65. package/dist/wasm-bindings.d.ts +130 -0
  66. package/dist/wasm-bindings.d.ts.map +1 -1
  67. package/dist/wasm-bindings.js +208 -0
  68. package/dist/wasm-bindings.js.map +1 -1
  69. package/dist/workerHelpers-threaded.d.ts +2 -0
  70. package/dist/workerHelpers-threaded.d.ts.map +1 -0
  71. package/dist/workerHelpers-threaded.js +100 -0
  72. package/dist/workerHelpers-threaded.js.map +1 -0
  73. package/package.json +8 -3
  74. package/src/Arc.ts +117 -117
  75. package/src/Brep.ts +1 -1
  76. package/src/Circle.ts +166 -166
  77. package/src/Line.ts +171 -171
  78. package/src/Mesh.ts +300 -45
  79. package/src/MeshSurface.ts +1 -1
  80. package/src/NurbsCurve.ts +655 -655
  81. package/src/NurbsSurface.ts +721 -721
  82. package/src/PolyCurve.ts +203 -203
  83. package/src/Polyline.ts +316 -316
  84. package/src/Ray.ts +90 -90
  85. package/src/SculptSession.ts +377 -377
  86. package/src/engine-threaded.ts +39 -0
  87. package/src/engine.ts +46 -0
  88. package/src/index.ts +1 -1
  89. package/src/mesh-boolean.pool.ts +208 -86
  90. package/src/mesh-boolean.protocol.ts +44 -1
  91. package/src/mesh-boolean.worker.ts +170 -56
  92. package/src/wasm-active.ts +31 -0
  93. package/src/wasm-base64-threaded.ts +2 -0
  94. package/src/wasm-base64.ts +1 -1
  95. package/src/wasm-bindings-threaded.d.ts +1989 -0
  96. package/src/wasm-bindings-threaded.js +5235 -0
  97. package/src/wasm-bindings.d.ts +104 -0
  98. package/src/wasm-bindings.js +225 -0
  99. package/src/workerHelpers-threaded.js +107 -0
package/src/Polyline.ts CHANGED
@@ -1,316 +1,316 @@
1
- import { ensureInit } from "./engine.js";
2
- import { Point } from "./Point.js";
3
- import { Vec3 } from "./Vec3.js";
4
- import { Mesh } from "./Mesh.js";
5
- import { Line } from "./Line.js";
6
- import { PolyCurve } from "./PolyCurve.js";
7
- import type { Plane } from "./Plane.js";
8
- import type { CurveOffsetOptions, RotationAxis } from "./types.js";
9
- import { pointsToCoords } from "./BufferCodec.js";
10
- import * as wasm from "./wasm-bindings.js";
11
-
12
- /**
13
- * Open or closed polyline defined by a sequence of points.
14
- * Supports length, evaluation, transformation, and extrusion operations.
15
- */
16
- export class Polyline {
17
- public readonly points: Point[];
18
-
19
- constructor(points: Point[]) {
20
- this.points = points;
21
- }
22
-
23
- /**
24
- * Get the total length of this polyline.
25
- * @returns Sum of segment lengths
26
- */
27
- length(): number {
28
- ensureInit();
29
- return wasm.polyline_length(this.toCoords());
30
- }
31
-
32
- /**
33
- * Evaluate a point on the polyline at normalized parameter t.
34
- * @param t - Parameter in [0, 1] (0 = first point, 1 = last point)
35
- * @returns Point on polyline at parameter t
36
- */
37
- pointAt(t: number): Point {
38
- ensureInit();
39
- const r = wasm.polyline_point_at(this.toCoords(), t);
40
- return new Point(r[0], r[1], r[2]);
41
- }
42
-
43
- /**
44
- * Check if this polyline is closed.
45
- * @param eps - Tolerance for comparing first and last points
46
- * @returns True if first and last points coincide
47
- */
48
- isClosed(eps = 1e-10): boolean {
49
- if (this.points.length < 2) return false;
50
- return this.points[0].equals(this.points[this.points.length - 1], eps);
51
- }
52
-
53
- /**
54
- * Compute all transversal crossing points of this polyline with a triangle
55
- * mesh. Works on OPEN meshes (sheets) — the mesh is not required to be a
56
- * closed volume. Hits are deduplicated and sorted along the polyline;
57
- * coplanar sliding overlaps yield no hits.
58
- *
59
- * @param mesh - Mesh to intersect with (open or closed)
60
- * @returns Crossings with the 3D point, segment index/parameter in [0, 1],
61
- * and triangle index
62
- */
63
- intersectMesh(
64
- mesh: Mesh,
65
- ): { point: Point; segmentIndex: number; segmentT: number; triangleIndex: number }[] {
66
- return mesh.intersectPolyline(this);
67
- }
68
-
69
- /**
70
- * Split this polyline into pieces at every crossing with a mesh (open
71
- * sheets allowed). Crossing points become the shared endpoints of adjacent
72
- * pieces; a closed loop is opened at the first crossing (N crossings → N
73
- * pieces), an open polyline yields N + 1 pieces. With no crossings the
74
- * original polyline comes back as one piece. Pieces are NOT classified —
75
- * see `clipBySheet` / `Mesh.clipPolyline` for classification.
76
- *
77
- * @param mesh - Mesh to split against (open or closed)
78
- * @returns Polyline pieces in order along this polyline
79
- */
80
- splitByMesh(mesh: Mesh): Polyline[] {
81
- return mesh.splitPolyline(this);
82
- }
83
-
84
- /**
85
- * Clip this polyline by an OPEN oriented sheet: split it at every sheet
86
- * crossing and classify each piece by the SIGNED SIDE of the sheet —
87
- * `inside` = in FRONT of the sheet normal, `outside` = behind it (the
88
- * sheet's stored winding defines the normals).
89
- *
90
- * This is side classification, NOT containment — unlike
91
- * `Mesh.clipPolyline`, which requires a CLOSED volume: the sheet may be
92
- * open, and pieces far from it still classify deterministically by the
93
- * nearest facet's normal. Adjacent same-side pieces (a touch, not a
94
- * crossing) merge back into one run; closed loops merge the run spanning
95
- * the seam.
96
- *
97
- * @param sheet - Oriented sheet mesh (open or closed)
98
- */
99
- clipBySheet(sheet: Mesh): { inside: Polyline[]; outside: Polyline[] } {
100
- return sheet.clipPolylineBySheet(this);
101
- }
102
-
103
- /**
104
- * Extrude this polyline along a direction vector.
105
- * @param direction - Extrusion direction and magnitude
106
- * @param segments - Number of segments along extrusion (default 1)
107
- * @param caps - Whether to cap the ends (default false)
108
- * @returns Mesh representing the extruded surface
109
- */
110
- extrude(direction: Vec3, segments = 1, caps = false): Mesh {
111
- ensureInit();
112
- const buf = wasm.extrude_polyline(
113
- this.toCoords(),
114
- direction.x, direction.y, direction.z,
115
- segments, caps
116
- );
117
- return Mesh.fromBuffer(buf);
118
- }
119
-
120
- /**
121
- * Extrude this closed polyline into a trusted solid suitable for booleans.
122
- * @param direction - Extrusion direction and magnitude
123
- * @returns Closed mesh solid ready for boolean operations
124
- */
125
- extrudeAsSolid(direction: Vec3): Mesh {
126
- return Mesh.extrudeCurveAsSolid(this, direction);
127
- }
128
-
129
- /**
130
- * Translate this polyline by an offset vector.
131
- * @param offset - Translation vector
132
- * @returns New translated polyline
133
- */
134
- translate(offset: Vec3): Polyline {
135
- return new Polyline(this.points.map(p => p.add(offset)));
136
- }
137
-
138
- /**
139
- * Apply a row-major 4x4 matrix to every vertex.
140
- * @param matrix - Row-major 4x4 matrix (16 entries)
141
- * @returns New transformed polyline
142
- */
143
- applyMatrix(matrix: number[] | Float64Array): Polyline {
144
- return new Polyline(this.points.map(p => p.applyMatrix(matrix)));
145
- }
146
-
147
- /**
148
- * Rotate this polyline around an axis.
149
- * @param axis - Rotation axis (Vec3 through origin, or Line for arbitrary axis)
150
- * @param angle - Rotation angle in radians
151
- * @returns New rotated polyline
152
- */
153
- rotate(axis: RotationAxis, angle: number): Polyline {
154
- return new Polyline(this.points.map(p => p.rotate(axis, angle)));
155
- }
156
-
157
- /**
158
- * Offset this polyline perpendicular to its segments.
159
- * @param distance - Offset distance
160
- * @param normal - Reference normal for determining offset direction
161
- * @param arcSamples - Number of samples per arc join in the returned polyline(s)
162
- * @param options - Offset options such as join style
163
- * @returns New offset polyline
164
- */
165
- offset(
166
- distance: number,
167
- normal?: Vec3,
168
- arcSamples = 32,
169
- options: CurveOffsetOptions = {},
170
- ): Polyline {
171
- const results = this.offsetAll(distance, normal, arcSamples, options);
172
- if (results.length === 1) return results[0];
173
- if (results.length === 0) {
174
- throw new Error("Polyline.offset() collapsed and produced no valid result polylines");
175
- }
176
- throw new Error(
177
- `Polyline.offset() produced ${results.length} result polylines. Use Polyline.offsetAll() instead.`,
178
- );
179
- }
180
-
181
- /**
182
- * Offset this polyline and return every sampled result polyline.
183
- * @param distance - Offset distance
184
- * @param normal - Reference normal for determining offset direction
185
- * @param arcSamples - Number of samples per exact arc segment in the returned polylines
186
- * @param options - Offset options such as join style
187
- * @returns Every valid offset polyline
188
- */
189
- offsetAll(
190
- distance: number,
191
- normal?: Vec3,
192
- arcSamples = 32,
193
- options: CurveOffsetOptions = {},
194
- ): Polyline[] {
195
- const exact = this.toPolyCurve().offsetAll(distance, normal, options);
196
- const samples = Number.isFinite(arcSamples) ? Math.max(3, Math.floor(arcSamples)) : 32;
197
- return exact.map(curve => new Polyline(curve.sample(samples)));
198
- }
199
-
200
- /**
201
- * Thicken this open polyline into a closed PolyCurve suitable for extrusion.
202
- * When `bothSides` is false, the source polyline forms one side of the result.
203
- * When true, the thickness is added on each side of the source polyline.
204
- */
205
- thicken(
206
- distance: number,
207
- bothSides = false,
208
- normal?: Vec3,
209
- options: CurveOffsetOptions = {},
210
- ): PolyCurve {
211
- if (this.points.length < 2) {
212
- throw new Error("Polyline.thicken() requires at least 2 points");
213
- }
214
- if (this.isClosed()) {
215
- throw new Error("Polyline.thicken() requires an open polyline");
216
- }
217
- return this.toPolyCurve().thicken(distance, bothSides, normal, options);
218
- }
219
-
220
- /**
221
- * Project this polyline onto a plane.
222
- * @param plane - Target plane
223
- * @param direction - Optional projection direction (default: perpendicular to plane)
224
- * @returns New projected polyline
225
- */
226
- projectOntoPlane(plane: Plane, direction?: Vec3): Polyline {
227
- const proj = (p: Point) => direction ? plane.projectPointAlongDirection(p, direction) : plane.projectPoint(p);
228
- return new Polyline(this.points.map(proj));
229
- }
230
-
231
- /**
232
- * Fillet corners of this polyline with arcs of the given radius.
233
- * @param radius - Fillet radius
234
- * @param normal - Reference normal for arc orientation (auto-detected if not provided)
235
- * @returns PolyCurve with Line and Arc segments
236
- */
237
- fillet(radius: number, normal?: Vec3): PolyCurve {
238
- ensureInit();
239
- if (this.points.length < 3) {
240
- throw new Error("Polyline.fillet() requires at least 3 points");
241
- }
242
-
243
- const nx = normal?.x ?? 0, ny = normal?.y ?? 0, nz = normal?.z ?? 0;
244
- const buf = wasm.fillet_polycurve(this.toCoords(), radius, nx, ny, nz);
245
- if (buf.length < 1) {
246
- throw new Error("Polyline.fillet() failed - WASM returned empty result");
247
- }
248
-
249
- return PolyCurve.fromSegmentData(buf);
250
- }
251
-
252
- /**
253
- * Chamfer corners of this polyline with straight cuts of the given
254
- * setback distance (clamped per corner to half the shorter adjacent
255
- * segment). Closed polylines (duplicated end point) chamfer every corner
256
- * including the seam and stay closed; open polylines never touch their
257
- * endpoints. `options.corners` selects vertex indices to chamfer
258
- * (indices into the unique point list, seam duplicate excluded).
259
- */
260
- chamfer(distance: number, options: { corners?: number[] } = {}): Polyline {
261
- ensureInit();
262
- const pts = this.points;
263
- if (pts.length < 3) {
264
- throw new Error("Polyline.chamfer() requires at least 3 points");
265
- }
266
- const first = pts[0];
267
- const last = pts[pts.length - 1];
268
- const closed =
269
- pts.length >= 4 &&
270
- Math.abs(first.x - last.x) < 1e-10 &&
271
- Math.abs(first.y - last.y) < 1e-10 &&
272
- Math.abs(first.z - last.z) < 1e-10;
273
- const unique = closed ? pts.slice(0, -1) : pts;
274
- const coords = new Float64Array(unique.length * 3);
275
- unique.forEach((p, i) => {
276
- coords[i * 3] = p.x;
277
- coords[i * 3 + 1] = p.y;
278
- coords[i * 3 + 2] = p.z;
279
- });
280
- const cornerBuf = new Float64Array(options.corners ?? []);
281
- const buf = wasm.chamfer_polyline_corners(coords, closed, distance, cornerBuf);
282
- if (buf.length < 6) {
283
- throw new Error(
284
- `Polyline.chamfer() failed: ${wasm.geometry_last_error() || "unknown error"}`,
285
- );
286
- }
287
- const out: Point[] = [];
288
- for (let i = 0; i + 2 < buf.length; i += 3) {
289
- out.push(new Point(buf[i], buf[i + 1], buf[i + 2]));
290
- }
291
- if (closed && out.length > 0) {
292
- out.push(out[0]);
293
- }
294
- return new Polyline(out);
295
- }
296
-
297
- /**
298
- * Convert this polyline to a PolyCurve (line segments only).
299
- * @returns PolyCurve with Line segments connecting consecutive points
300
- */
301
- toPolyCurve(): PolyCurve {
302
- const segs: Line[] = [];
303
- for (let i = 0; i < this.points.length - 1; i++) {
304
- segs.push(new Line(this.points[i], this.points[i + 1]));
305
- }
306
- return new PolyCurve(segs);
307
- }
308
-
309
- /**
310
- * Convert points to flat coordinate array for WASM.
311
- * @returns Float64Array [x1,y1,z1, x2,y2,z2, ...]
312
- */
313
- private toCoords(): Float64Array {
314
- return pointsToCoords(this.points);
315
- }
316
- }
1
+ import { ensureInit } from "./engine.js";
2
+ import { Point } from "./Point.js";
3
+ import { Vec3 } from "./Vec3.js";
4
+ import { Mesh } from "./Mesh.js";
5
+ import { Line } from "./Line.js";
6
+ import { PolyCurve } from "./PolyCurve.js";
7
+ import type { Plane } from "./Plane.js";
8
+ import type { CurveOffsetOptions, RotationAxis } from "./types.js";
9
+ import { pointsToCoords } from "./BufferCodec.js";
10
+ import { wasm } from "./wasm-active.js";
11
+
12
+ /**
13
+ * Open or closed polyline defined by a sequence of points.
14
+ * Supports length, evaluation, transformation, and extrusion operations.
15
+ */
16
+ export class Polyline {
17
+ public readonly points: Point[];
18
+
19
+ constructor(points: Point[]) {
20
+ this.points = points;
21
+ }
22
+
23
+ /**
24
+ * Get the total length of this polyline.
25
+ * @returns Sum of segment lengths
26
+ */
27
+ length(): number {
28
+ ensureInit();
29
+ return wasm.polyline_length(this.toCoords());
30
+ }
31
+
32
+ /**
33
+ * Evaluate a point on the polyline at normalized parameter t.
34
+ * @param t - Parameter in [0, 1] (0 = first point, 1 = last point)
35
+ * @returns Point on polyline at parameter t
36
+ */
37
+ pointAt(t: number): Point {
38
+ ensureInit();
39
+ const r = wasm.polyline_point_at(this.toCoords(), t);
40
+ return new Point(r[0], r[1], r[2]);
41
+ }
42
+
43
+ /**
44
+ * Check if this polyline is closed.
45
+ * @param eps - Tolerance for comparing first and last points
46
+ * @returns True if first and last points coincide
47
+ */
48
+ isClosed(eps = 1e-10): boolean {
49
+ if (this.points.length < 2) return false;
50
+ return this.points[0].equals(this.points[this.points.length - 1], eps);
51
+ }
52
+
53
+ /**
54
+ * Compute all transversal crossing points of this polyline with a triangle
55
+ * mesh. Works on OPEN meshes (sheets) — the mesh is not required to be a
56
+ * closed volume. Hits are deduplicated and sorted along the polyline;
57
+ * coplanar sliding overlaps yield no hits.
58
+ *
59
+ * @param mesh - Mesh to intersect with (open or closed)
60
+ * @returns Crossings with the 3D point, segment index/parameter in [0, 1],
61
+ * and triangle index
62
+ */
63
+ intersectMesh(
64
+ mesh: Mesh,
65
+ ): { point: Point; segmentIndex: number; segmentT: number; triangleIndex: number }[] {
66
+ return mesh.intersectPolyline(this);
67
+ }
68
+
69
+ /**
70
+ * Split this polyline into pieces at every crossing with a mesh (open
71
+ * sheets allowed). Crossing points become the shared endpoints of adjacent
72
+ * pieces; a closed loop is opened at the first crossing (N crossings → N
73
+ * pieces), an open polyline yields N + 1 pieces. With no crossings the
74
+ * original polyline comes back as one piece. Pieces are NOT classified —
75
+ * see `clipBySheet` / `Mesh.clipPolyline` for classification.
76
+ *
77
+ * @param mesh - Mesh to split against (open or closed)
78
+ * @returns Polyline pieces in order along this polyline
79
+ */
80
+ splitByMesh(mesh: Mesh): Polyline[] {
81
+ return mesh.splitPolyline(this);
82
+ }
83
+
84
+ /**
85
+ * Clip this polyline by an OPEN oriented sheet: split it at every sheet
86
+ * crossing and classify each piece by the SIGNED SIDE of the sheet —
87
+ * `inside` = in FRONT of the sheet normal, `outside` = behind it (the
88
+ * sheet's stored winding defines the normals).
89
+ *
90
+ * This is side classification, NOT containment — unlike
91
+ * `Mesh.clipPolyline`, which requires a CLOSED volume: the sheet may be
92
+ * open, and pieces far from it still classify deterministically by the
93
+ * nearest facet's normal. Adjacent same-side pieces (a touch, not a
94
+ * crossing) merge back into one run; closed loops merge the run spanning
95
+ * the seam.
96
+ *
97
+ * @param sheet - Oriented sheet mesh (open or closed)
98
+ */
99
+ clipBySheet(sheet: Mesh): { inside: Polyline[]; outside: Polyline[] } {
100
+ return sheet.clipPolylineBySheet(this);
101
+ }
102
+
103
+ /**
104
+ * Extrude this polyline along a direction vector.
105
+ * @param direction - Extrusion direction and magnitude
106
+ * @param segments - Number of segments along extrusion (default 1)
107
+ * @param caps - Whether to cap the ends (default false)
108
+ * @returns Mesh representing the extruded surface
109
+ */
110
+ extrude(direction: Vec3, segments = 1, caps = false): Mesh {
111
+ ensureInit();
112
+ const buf = wasm.extrude_polyline(
113
+ this.toCoords(),
114
+ direction.x, direction.y, direction.z,
115
+ segments, caps
116
+ );
117
+ return Mesh.fromBuffer(buf);
118
+ }
119
+
120
+ /**
121
+ * Extrude this closed polyline into a trusted solid suitable for booleans.
122
+ * @param direction - Extrusion direction and magnitude
123
+ * @returns Closed mesh solid ready for boolean operations
124
+ */
125
+ extrudeAsSolid(direction: Vec3): Mesh {
126
+ return Mesh.extrudeCurveAsSolid(this, direction);
127
+ }
128
+
129
+ /**
130
+ * Translate this polyline by an offset vector.
131
+ * @param offset - Translation vector
132
+ * @returns New translated polyline
133
+ */
134
+ translate(offset: Vec3): Polyline {
135
+ return new Polyline(this.points.map(p => p.add(offset)));
136
+ }
137
+
138
+ /**
139
+ * Apply a row-major 4x4 matrix to every vertex.
140
+ * @param matrix - Row-major 4x4 matrix (16 entries)
141
+ * @returns New transformed polyline
142
+ */
143
+ applyMatrix(matrix: number[] | Float64Array): Polyline {
144
+ return new Polyline(this.points.map(p => p.applyMatrix(matrix)));
145
+ }
146
+
147
+ /**
148
+ * Rotate this polyline around an axis.
149
+ * @param axis - Rotation axis (Vec3 through origin, or Line for arbitrary axis)
150
+ * @param angle - Rotation angle in radians
151
+ * @returns New rotated polyline
152
+ */
153
+ rotate(axis: RotationAxis, angle: number): Polyline {
154
+ return new Polyline(this.points.map(p => p.rotate(axis, angle)));
155
+ }
156
+
157
+ /**
158
+ * Offset this polyline perpendicular to its segments.
159
+ * @param distance - Offset distance
160
+ * @param normal - Reference normal for determining offset direction
161
+ * @param arcSamples - Number of samples per arc join in the returned polyline(s)
162
+ * @param options - Offset options such as join style
163
+ * @returns New offset polyline
164
+ */
165
+ offset(
166
+ distance: number,
167
+ normal?: Vec3,
168
+ arcSamples = 32,
169
+ options: CurveOffsetOptions = {},
170
+ ): Polyline {
171
+ const results = this.offsetAll(distance, normal, arcSamples, options);
172
+ if (results.length === 1) return results[0];
173
+ if (results.length === 0) {
174
+ throw new Error("Polyline.offset() collapsed and produced no valid result polylines");
175
+ }
176
+ throw new Error(
177
+ `Polyline.offset() produced ${results.length} result polylines. Use Polyline.offsetAll() instead.`,
178
+ );
179
+ }
180
+
181
+ /**
182
+ * Offset this polyline and return every sampled result polyline.
183
+ * @param distance - Offset distance
184
+ * @param normal - Reference normal for determining offset direction
185
+ * @param arcSamples - Number of samples per exact arc segment in the returned polylines
186
+ * @param options - Offset options such as join style
187
+ * @returns Every valid offset polyline
188
+ */
189
+ offsetAll(
190
+ distance: number,
191
+ normal?: Vec3,
192
+ arcSamples = 32,
193
+ options: CurveOffsetOptions = {},
194
+ ): Polyline[] {
195
+ const exact = this.toPolyCurve().offsetAll(distance, normal, options);
196
+ const samples = Number.isFinite(arcSamples) ? Math.max(3, Math.floor(arcSamples)) : 32;
197
+ return exact.map(curve => new Polyline(curve.sample(samples)));
198
+ }
199
+
200
+ /**
201
+ * Thicken this open polyline into a closed PolyCurve suitable for extrusion.
202
+ * When `bothSides` is false, the source polyline forms one side of the result.
203
+ * When true, the thickness is added on each side of the source polyline.
204
+ */
205
+ thicken(
206
+ distance: number,
207
+ bothSides = false,
208
+ normal?: Vec3,
209
+ options: CurveOffsetOptions = {},
210
+ ): PolyCurve {
211
+ if (this.points.length < 2) {
212
+ throw new Error("Polyline.thicken() requires at least 2 points");
213
+ }
214
+ if (this.isClosed()) {
215
+ throw new Error("Polyline.thicken() requires an open polyline");
216
+ }
217
+ return this.toPolyCurve().thicken(distance, bothSides, normal, options);
218
+ }
219
+
220
+ /**
221
+ * Project this polyline onto a plane.
222
+ * @param plane - Target plane
223
+ * @param direction - Optional projection direction (default: perpendicular to plane)
224
+ * @returns New projected polyline
225
+ */
226
+ projectOntoPlane(plane: Plane, direction?: Vec3): Polyline {
227
+ const proj = (p: Point) => direction ? plane.projectPointAlongDirection(p, direction) : plane.projectPoint(p);
228
+ return new Polyline(this.points.map(proj));
229
+ }
230
+
231
+ /**
232
+ * Fillet corners of this polyline with arcs of the given radius.
233
+ * @param radius - Fillet radius
234
+ * @param normal - Reference normal for arc orientation (auto-detected if not provided)
235
+ * @returns PolyCurve with Line and Arc segments
236
+ */
237
+ fillet(radius: number, normal?: Vec3): PolyCurve {
238
+ ensureInit();
239
+ if (this.points.length < 3) {
240
+ throw new Error("Polyline.fillet() requires at least 3 points");
241
+ }
242
+
243
+ const nx = normal?.x ?? 0, ny = normal?.y ?? 0, nz = normal?.z ?? 0;
244
+ const buf = wasm.fillet_polycurve(this.toCoords(), radius, nx, ny, nz);
245
+ if (buf.length < 1) {
246
+ throw new Error("Polyline.fillet() failed - WASM returned empty result");
247
+ }
248
+
249
+ return PolyCurve.fromSegmentData(buf);
250
+ }
251
+
252
+ /**
253
+ * Chamfer corners of this polyline with straight cuts of the given
254
+ * setback distance (clamped per corner to half the shorter adjacent
255
+ * segment). Closed polylines (duplicated end point) chamfer every corner
256
+ * including the seam and stay closed; open polylines never touch their
257
+ * endpoints. `options.corners` selects vertex indices to chamfer
258
+ * (indices into the unique point list, seam duplicate excluded).
259
+ */
260
+ chamfer(distance: number, options: { corners?: number[] } = {}): Polyline {
261
+ ensureInit();
262
+ const pts = this.points;
263
+ if (pts.length < 3) {
264
+ throw new Error("Polyline.chamfer() requires at least 3 points");
265
+ }
266
+ const first = pts[0];
267
+ const last = pts[pts.length - 1];
268
+ const closed =
269
+ pts.length >= 4 &&
270
+ Math.abs(first.x - last.x) < 1e-10 &&
271
+ Math.abs(first.y - last.y) < 1e-10 &&
272
+ Math.abs(first.z - last.z) < 1e-10;
273
+ const unique = closed ? pts.slice(0, -1) : pts;
274
+ const coords = new Float64Array(unique.length * 3);
275
+ unique.forEach((p, i) => {
276
+ coords[i * 3] = p.x;
277
+ coords[i * 3 + 1] = p.y;
278
+ coords[i * 3 + 2] = p.z;
279
+ });
280
+ const cornerBuf = new Float64Array(options.corners ?? []);
281
+ const buf = wasm.chamfer_polyline_corners(coords, closed, distance, cornerBuf);
282
+ if (buf.length < 6) {
283
+ throw new Error(
284
+ `Polyline.chamfer() failed: ${wasm.geometry_last_error() || "unknown error"}`,
285
+ );
286
+ }
287
+ const out: Point[] = [];
288
+ for (let i = 0; i + 2 < buf.length; i += 3) {
289
+ out.push(new Point(buf[i], buf[i + 1], buf[i + 2]));
290
+ }
291
+ if (closed && out.length > 0) {
292
+ out.push(out[0]);
293
+ }
294
+ return new Polyline(out);
295
+ }
296
+
297
+ /**
298
+ * Convert this polyline to a PolyCurve (line segments only).
299
+ * @returns PolyCurve with Line segments connecting consecutive points
300
+ */
301
+ toPolyCurve(): PolyCurve {
302
+ const segs: Line[] = [];
303
+ for (let i = 0; i < this.points.length - 1; i++) {
304
+ segs.push(new Line(this.points[i], this.points[i + 1]));
305
+ }
306
+ return new PolyCurve(segs);
307
+ }
308
+
309
+ /**
310
+ * Convert points to flat coordinate array for WASM.
311
+ * @returns Float64Array [x1,y1,z1, x2,y2,z2, ...]
312
+ */
313
+ private toCoords(): Float64Array {
314
+ return pointsToCoords(this.points);
315
+ }
316
+ }