okgeometry-api 1.2.0 → 1.4.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 (65) hide show
  1. package/dist/Line.d.ts +10 -1
  2. package/dist/Line.d.ts.map +1 -1
  3. package/dist/Line.js +11 -0
  4. package/dist/Line.js.map +1 -1
  5. package/dist/Mesh.d.ts +123 -9
  6. package/dist/Mesh.d.ts.map +1 -1
  7. package/dist/Mesh.js +401 -26
  8. package/dist/Mesh.js.map +1 -1
  9. package/dist/MeshSurface.d.ts +32 -0
  10. package/dist/MeshSurface.d.ts.map +1 -0
  11. package/dist/MeshSurface.js +51 -0
  12. package/dist/MeshSurface.js.map +1 -0
  13. package/dist/NurbsCurve.d.ts +50 -2
  14. package/dist/NurbsCurve.d.ts.map +1 -1
  15. package/dist/NurbsCurve.js +76 -2
  16. package/dist/NurbsCurve.js.map +1 -1
  17. package/dist/NurbsSurface.d.ts +9 -1
  18. package/dist/NurbsSurface.d.ts.map +1 -1
  19. package/dist/NurbsSurface.js +12 -3
  20. package/dist/NurbsSurface.js.map +1 -1
  21. package/dist/PolyCurve.d.ts +21 -3
  22. package/dist/PolyCurve.d.ts.map +1 -1
  23. package/dist/PolyCurve.js +82 -38
  24. package/dist/PolyCurve.js.map +1 -1
  25. package/dist/Polygon.d.ts +13 -2
  26. package/dist/Polygon.d.ts.map +1 -1
  27. package/dist/Polygon.js +21 -3
  28. package/dist/Polygon.js.map +1 -1
  29. package/dist/Polyline.d.ts +19 -2
  30. package/dist/Polyline.d.ts.map +1 -1
  31. package/dist/Polyline.js +38 -6
  32. package/dist/Polyline.js.map +1 -1
  33. package/dist/Surface.d.ts +17 -0
  34. package/dist/Surface.d.ts.map +1 -0
  35. package/dist/Surface.js +2 -0
  36. package/dist/Surface.js.map +1 -0
  37. package/dist/index.d.ts +4 -2
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/types.d.ts +13 -0
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/wasm-base64.d.ts +1 -1
  44. package/dist/wasm-base64.d.ts.map +1 -1
  45. package/dist/wasm-base64.js +1 -1
  46. package/dist/wasm-base64.js.map +1 -1
  47. package/dist/wasm-bindings.d.ts +132 -2
  48. package/dist/wasm-bindings.d.ts.map +1 -1
  49. package/dist/wasm-bindings.js +207 -2
  50. package/dist/wasm-bindings.js.map +1 -1
  51. package/package.json +1 -1
  52. package/src/Line.ts +38 -20
  53. package/src/Mesh.ts +909 -464
  54. package/src/MeshSurface.ts +72 -0
  55. package/src/NurbsCurve.ts +103 -3
  56. package/src/NurbsSurface.ts +28 -13
  57. package/src/PolyCurve.ts +157 -85
  58. package/src/Polygon.ts +34 -4
  59. package/src/Polyline.ts +74 -24
  60. package/src/Surface.ts +18 -0
  61. package/src/index.ts +5 -0
  62. package/src/types.ts +15 -0
  63. package/src/wasm-base64.ts +1 -1
  64. package/src/wasm-bindings.d.ts +101 -2
  65. package/src/wasm-bindings.js +218 -2
@@ -0,0 +1,72 @@
1
+ import { ensureInit } from "./engine.js";
2
+ import { decodePointResult } from "./BufferCodec.js";
3
+ import type { Mesh, MeshPlanarFace } from "./Mesh.js";
4
+ import { Point } from "./Point.js";
5
+ import type { Surface } from "./Surface.js";
6
+ import { Vec3 } from "./Vec3.js";
7
+ import * as wasm from "./wasm-bindings.js";
8
+
9
+ /**
10
+ * Logical planar surface extracted from a coplanar connected face group on a mesh.
11
+ * Mirrors the `NurbsSurface` query API for evaluation and normals.
12
+ */
13
+ export class MeshSurface implements Surface {
14
+ constructor(
15
+ public readonly mesh: Mesh,
16
+ public readonly face: MeshPlanarFace,
17
+ ) {}
18
+
19
+ get index(): number {
20
+ return this.face.index;
21
+ }
22
+
23
+ get seedTriangleIndex(): number {
24
+ return this.face.seedTriangleIndex;
25
+ }
26
+
27
+ get triangleIndices(): number[] {
28
+ return this.face.triangleIndices;
29
+ }
30
+
31
+ get triangleCount(): number {
32
+ return this.face.triangleCount;
33
+ }
34
+
35
+ get centroid(): Point {
36
+ return this.face.centroid;
37
+ }
38
+
39
+ get faceNormal(): Vec3 {
40
+ return this.face.normal;
41
+ }
42
+
43
+ get area(): number {
44
+ return this.face.area;
45
+ }
46
+
47
+ /**
48
+ * Evaluate a point on this planar mesh surface.
49
+ * Parameters map across the face group's local 2D bounds.
50
+ * Returns `Point(NaN, NaN, NaN)` when the query falls outside the actual face region.
51
+ */
52
+ evaluate(u: number, v: number): Point {
53
+ ensureInit();
54
+ return decodePointResult(
55
+ wasm.mesh_planar_face_evaluate(
56
+ this.mesh.vertexCount,
57
+ this.mesh.rawBuffer,
58
+ this.seedTriangleIndex,
59
+ u,
60
+ v,
61
+ ),
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Unit normal of this planar mesh surface.
67
+ * The result is constant across the entire face group.
68
+ */
69
+ normal(_u: number, _v: number): Vec3 {
70
+ return this.faceNormal;
71
+ }
72
+ }
package/src/NurbsCurve.ts CHANGED
@@ -4,9 +4,10 @@ import { Vec3 } from "./Vec3.js";
4
4
  import { Plane } from "./Plane.js";
5
5
  import { Mesh } from "./Mesh.js";
6
6
  import { Polyline } from "./Polyline.js";
7
+ import { PolyCurve } from "./PolyCurve.js";
7
8
  import type { Line } from "./Line.js";
8
9
  import type { Arc } from "./Arc.js";
9
- import type { RotationAxis } from "./types.js";
10
+ import type { CurveOffsetOptions, RotationAxis } from "./types.js";
10
11
  import * as wasm from "./wasm-bindings.js";
11
12
 
12
13
  /**
@@ -160,12 +161,81 @@ export class NurbsCurve {
160
161
  * @param distance - Offset distance
161
162
  * @param normal - Reference normal for determining offset direction
162
163
  * @param samples - Number of sample points (default 64)
164
+ * @param arcSamples - Number of samples per exact arc segment in the returned polyline(s)
165
+ * @param options - Offset options such as join style
163
166
  * @returns Offset polyline approximation
164
167
  */
165
- offset(distance: number, normal?: Vec3, samples = 64): Polyline {
168
+ offset(
169
+ distance: number,
170
+ normal?: Vec3,
171
+ samples = 64,
172
+ arcSamples = 32,
173
+ options: CurveOffsetOptions = {},
174
+ ): Polyline {
175
+ const results = this.offsetAll(distance, normal, samples, arcSamples, options);
176
+ if (results.length === 1) return results[0];
177
+ if (results.length === 0) {
178
+ throw new Error("NurbsCurve.offset() collapsed and produced no valid result polylines");
179
+ }
180
+ throw new Error(
181
+ `NurbsCurve.offset() produced ${results.length} result polylines. Use NurbsCurve.offsetAll() instead.`,
182
+ );
183
+ }
184
+
185
+ /**
186
+ * Offset this curve by a distance and return every sampled result polyline.
187
+ * Note: NURBS offset is approximated by sampling and offsetting as polyline.
188
+ * @param distance - Offset distance
189
+ * @param normal - Reference normal for determining offset direction
190
+ * @param samples - Number of sample points used to approximate the source curve
191
+ * @param arcSamples - Number of samples per exact arc segment in the returned polylines
192
+ * @param options - Offset options such as join style
193
+ * @returns Every valid offset polyline approximation
194
+ */
195
+ offsetAll(
196
+ distance: number,
197
+ normal?: Vec3,
198
+ samples = 64,
199
+ arcSamples = 32,
200
+ options: CurveOffsetOptions = {},
201
+ ): Polyline[] {
166
202
  const pts = this.sample(samples);
167
203
  const pl = new Polyline(pts);
168
- return pl.offset(distance, normal);
204
+ return pl.offsetAll(distance, normal, arcSamples, options);
205
+ }
206
+
207
+ /**
208
+ * Thicken this curve into a closed PolyCurve.
209
+ * Note: this is approximated by sampling the NURBS curve into a polyline first.
210
+ *
211
+ * When `bothSides` is false, the sampled source curve forms one side of the
212
+ * result. When true, the distance is applied on each side of the sampled curve.
213
+ */
214
+ thicken(
215
+ distance: number,
216
+ bothSides = false,
217
+ normal?: Vec3,
218
+ samples = 64,
219
+ options: CurveOffsetOptions = {},
220
+ ): PolyCurve {
221
+ const pts = this.sample(samples);
222
+ return new Polyline(pts).thicken(distance, bothSides, normal, options);
223
+ }
224
+
225
+ /**
226
+ * Extrude this curve along a direction vector.
227
+ * Note: this is approximated by sampling the NURBS curve into a polyline
228
+ * first. Open curves produce an open sheet; closed curves an uncapped tube
229
+ * unless `caps` is true (see extrudeAsSolid for a boolean-ready solid).
230
+ * @param direction - Extrusion direction and magnitude
231
+ * @param segments - Number of segments along the extrusion (default 1)
232
+ * @param caps - Whether to cap the ends of a closed profile (default false)
233
+ * @param samples - Number of curve samples used for tessellation (default 64)
234
+ * @returns Mesh representing the extruded surface
235
+ */
236
+ extrude(direction: Vec3, segments = 1, caps = false, samples = 64): Mesh {
237
+ const pts = this.sample(samples);
238
+ return new Polyline(pts).extrude(direction, segments, caps);
169
239
  }
170
240
 
171
241
  /**
@@ -178,6 +248,36 @@ export class NurbsCurve {
178
248
  return Mesh.extrudeCurveAsSolid(this, direction, segments);
179
249
  }
180
250
 
251
+ /**
252
+ * Build a NURBS curve from raw control points with kernel-managed knots and
253
+ * uniform weights.
254
+ *
255
+ * Open curves use a clamped uniform knot vector (curve interpolates the first
256
+ * and last control points). Closed curves are periodic (C^{degree-1}
257
+ * continuous loops); the first control point must NOT be repeated at the end.
258
+ *
259
+ * @param degree - Requested degree (clamped by the kernel to the valid range)
260
+ * @param controlPoints - Control points (min 2 open / 3 closed)
261
+ * @param closed - Build a periodic closed loop (default false)
262
+ * @returns New NurbsCurve instance
263
+ */
264
+ static fromPoints(degree: number, controlPoints: Point[], closed = false): NurbsCurve {
265
+ ensureInit();
266
+ const coords = new Float64Array(controlPoints.length * 3);
267
+ for (let i = 0; i < controlPoints.length; i++) {
268
+ coords[i * 3] = controlPoints[i].x;
269
+ coords[i * 3 + 1] = controlPoints[i].y;
270
+ coords[i * 3 + 2] = controlPoints[i].z;
271
+ }
272
+ const buf = wasm.build_nurbs_curve(degree, coords, closed);
273
+ if (buf.length < 2) {
274
+ throw new Error(
275
+ `NurbsCurve.fromPoints failed (degree ${degree}, ${controlPoints.length} points, closed=${closed})`,
276
+ );
277
+ }
278
+ return NurbsCurve.fromData(buf);
279
+ }
280
+
181
281
  /**
182
282
  * Decode a NurbsCurve from WASM flat buffer.
183
283
  * @param data - Buffer in format [degree, num_cp, xyz..., w..., k...]
@@ -5,13 +5,18 @@ import { Plane } from "./Plane.js";
5
5
  import { Mesh } from "./Mesh.js";
6
6
  import { Polyline } from "./Polyline.js";
7
7
  import { Polygon } from "./Polygon.js";
8
- import { Line } from "./Line.js";
9
- import { Arc } from "./Arc.js";
10
- import { PolyCurve } from "./PolyCurve.js";
11
- import { NurbsCurve } from "./NurbsCurve.js";
12
- import type { LoftableCurve, RotationAxis } from "./types.js";
13
- import { parsePolylineBuffer as parsePolylineBuf } from "./BufferCodec.js";
14
- import * as wasm from "./wasm-bindings.js";
8
+ import { Line } from "./Line.js";
9
+ import { Arc } from "./Arc.js";
10
+ import { PolyCurve } from "./PolyCurve.js";
11
+ import { NurbsCurve } from "./NurbsCurve.js";
12
+ import type { Surface } from "./Surface.js";
13
+ import type { LoftableCurve, RotationAxis } from "./types.js";
14
+ import {
15
+ decodePointResult,
16
+ decodeVec3Result,
17
+ parsePolylineBuffer as parsePolylineBuf,
18
+ } from "./BufferCodec.js";
19
+ import * as wasm from "./wasm-bindings.js";
15
20
 
16
21
  /**
17
22
  * Non-Uniform Rational B-Spline (NURBS) surface backed by WASM.
@@ -19,7 +24,7 @@ import * as wasm from "./wasm-bindings.js";
19
24
  *
20
25
  * WASM data format: [degree_u, degree_v, num_u, num_v, ...cp(flat)..., ...weights..., ...knots_u..., ...knots_v...]
21
26
  */
22
- export class NurbsSurface {
27
+ export class NurbsSurface implements Surface {
23
28
  private _data: Float64Array;
24
29
 
25
30
  /**
@@ -82,11 +87,21 @@ export class NurbsSurface {
82
87
  * @param v - Parameter in V direction [0, 1]
83
88
  * @returns Point on surface at (u, v)
84
89
  */
85
- evaluate(u: number, v: number): Point {
86
- ensureInit();
87
- const r = wasm.nurbs_surface_evaluate(this._data, u, v);
88
- return new Point(r[0], r[1], r[2]);
89
- }
90
+ evaluate(u: number, v: number): Point {
91
+ ensureInit();
92
+ return decodePointResult(wasm.nurbs_surface_evaluate(this._data, u, v));
93
+ }
94
+
95
+ /**
96
+ * Evaluate the unit normal on the surface at parameters (u, v).
97
+ * @param u - Parameter in U direction [0, 1]
98
+ * @param v - Parameter in V direction [0, 1]
99
+ * @returns Unit surface normal at (u, v)
100
+ */
101
+ normal(u: number, v: number): Vec3 {
102
+ ensureInit();
103
+ return decodeVec3Result(wasm.nurbs_surface_normal(this._data, u, v));
104
+ }
90
105
 
91
106
  /**
92
107
  * Tessellate this surface into a triangle mesh.
package/src/PolyCurve.ts CHANGED
@@ -6,15 +6,31 @@ import { Vec3 } from "./Vec3.js";
6
6
  import { Mesh } from "./Mesh.js";
7
7
  import { NurbsCurve } from "./NurbsCurve.js";
8
8
  import type { Plane } from "./Plane.js";
9
- import type { CurveSegment, RotationAxis } from "./types.js";
9
+ import type {
10
+ CurveOffsetJoinStyle,
11
+ CurveOffsetOptions,
12
+ CurveSegment,
13
+ RotationAxis,
14
+ } from "./types.js";
10
15
  import { SegmentTypeCode } from "./types.js";
11
16
  import { pointsToCoords } from "./BufferCodec.js";
12
17
  import * as wasm from "./wasm-bindings.js";
13
-
14
- /**
15
- * Composite curve made of sequential Line and Arc segments.
16
- */
17
- export class PolyCurve {
18
+
19
+ function offsetJoinStyleCode(style: CurveOffsetJoinStyle | undefined): number {
20
+ switch (style ?? "miter") {
21
+ case "round":
22
+ return 1;
23
+ case "bevel":
24
+ return 2;
25
+ default:
26
+ return 0;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Composite curve made of sequential Line and Arc segments.
32
+ */
33
+ export class PolyCurve {
18
34
  public readonly segments: CurveSegment[];
19
35
 
20
36
  constructor(segments: CurveSegment[]) {
@@ -199,10 +215,10 @@ export class PolyCurve {
199
215
  }
200
216
 
201
217
  /** Decode a PolyCurve from WASM segment buffer [count, type, ...data, ...] */
202
- static fromSegmentData(buf: Float64Array | number[]): PolyCurve {
203
- const count = buf[0];
204
- const segs: CurveSegment[] = [];
205
- let idx = 1;
218
+ static fromSegmentData(buf: Float64Array | number[]): PolyCurve {
219
+ const count = buf[0];
220
+ const segs: CurveSegment[] = [];
221
+ let idx = 1;
206
222
  for (let i = 0; i < count; i++) {
207
223
  const type = buf[idx++];
208
224
  if (type === SegmentTypeCode.Line) {
@@ -221,9 +237,32 @@ export class PolyCurve {
221
237
  segs.push(new Arc(center, radius, startAngle, endAngle, normal));
222
238
  idx += 9;
223
239
  }
224
- }
225
- return new PolyCurve(segs);
226
- }
240
+ }
241
+ return new PolyCurve(segs);
242
+ }
243
+
244
+ /** Decode multiple PolyCurves from WASM buffer [curveCount, curveLen, curveData..., ...] */
245
+ static fromSegmentDataSet(buf: Float64Array | number[]): PolyCurve[] {
246
+ const data = buf instanceof Float64Array ? buf : new Float64Array(buf);
247
+ const curveCount = Math.max(0, Math.floor(data[0] ?? 0));
248
+ const curves: PolyCurve[] = [];
249
+ let idx = 1;
250
+
251
+ for (let i = 0; i < curveCount; i++) {
252
+ const curveLen = Math.max(0, Math.floor(data[idx++] ?? -1));
253
+ if (curveLen <= 0 || idx + curveLen > data.length) {
254
+ throw new Error("PolyCurve.fromSegmentDataSet() received invalid curve set buffer");
255
+ }
256
+ curves.push(PolyCurve.fromSegmentData(data.slice(idx, idx + curveLen)));
257
+ idx += curveLen;
258
+ }
259
+
260
+ if (idx !== data.length) {
261
+ throw new Error("PolyCurve.fromSegmentDataSet() received trailing buffer data");
262
+ }
263
+
264
+ return curves;
265
+ }
227
266
 
228
267
  /**
229
268
  * Chamfer corners of this PolyCurve by cutting each corner with a straight segment.
@@ -263,76 +302,109 @@ export class PolyCurve {
263
302
  return PolyCurve.fromSegmentData(buf);
264
303
  }
265
304
 
266
- /**
267
- * Offset this PolyCurve by a distance in the given plane.
268
- * Extracts vertex points, offsets as a polyline, returns a new PolyCurve of line segments.
269
- */
270
- offset(distance: number, normal?: Vec3): PolyCurve {
271
- ensureInit();
272
- if (this.segments.length < 1) {
273
- throw new Error("PolyCurve.offset() requires at least 1 segment");
274
- }
275
-
276
- const pts: Point[] = [];
277
- for (const seg of this.segments) {
278
- if (seg instanceof Line) {
279
- if (pts.length === 0) pts.push(seg.start);
280
- pts.push(seg.end);
281
- } else {
282
- if (pts.length === 0) pts.push(seg.pointAt(0));
283
- pts.push(seg.pointAt(1));
284
- }
285
- }
286
- if (pts.length < 2) {
287
- throw new Error("PolyCurve.offset() requires at least 2 vertices");
288
- }
289
-
290
- const coords = new Float64Array(pts.length * 3);
291
- for (let i = 0; i < pts.length; i++) {
292
- coords[i * 3] = pts[i].x;
293
- coords[i * 3 + 1] = pts[i].y;
294
- coords[i * 3 + 2] = pts[i].z;
295
- }
296
-
297
- const nx = normal?.x ?? 0, ny = normal?.y ?? 0, nz = normal?.z ?? 0;
298
- const result = wasm.offset_polyline_curve(coords, distance, nx, ny, nz);
299
- if (result.length < 6) {
300
- throw new Error("PolyCurve.offset() failed - WASM returned insufficient data");
301
- }
302
-
303
- // Convert flat coords back to line segments
304
- const segs: CurveSegment[] = [];
305
- for (let i = 0; i < result.length - 3; i += 3) {
306
- const s = new Point(result[i], result[i + 1], result[i + 2]);
307
- const e = new Point(result[i + 3], result[i + 4], result[i + 5]);
308
- segs.push(new Line(s, e));
309
- }
310
- return new PolyCurve(segs);
311
- }
312
-
313
- /**
314
- * Convert to an exact NURBS curve representation via WASM.
315
- * Lines become degree-1 NURBS, Arcs become degree-2 rational NURBS.
316
- * All segments are degree-elevated to a common degree and joined.
305
+ /**
306
+ * Offset this PolyCurve by a distance in the given plane.
307
+ * Preserves exact line/arc segments where possible and applies the requested outside-corner join style.
308
+ * Defaults to `miter`.
309
+ */
310
+ offset(distance: number, normal?: Vec3, options: CurveOffsetOptions = {}): PolyCurve {
311
+ const results = this.offsetAll(distance, normal, options);
312
+ if (results.length === 1) return results[0];
313
+ if (results.length === 0) {
314
+ throw new Error("PolyCurve.offset() collapsed and produced no valid result curves");
315
+ }
316
+ throw new Error(
317
+ `PolyCurve.offset() produced ${results.length} result curves. Use PolyCurve.offsetAll() instead.`,
318
+ );
319
+ }
320
+
321
+ /**
322
+ * Offset this PolyCurve by a distance in the given plane and return every exact result curve.
323
+ * Defaults to `miter`.
324
+ */
325
+ offsetAll(distance: number, normal?: Vec3, options: CurveOffsetOptions = {}): PolyCurve[] {
326
+ ensureInit();
327
+ if (this.segments.length < 1) {
328
+ throw new Error("PolyCurve.offsetAll() requires at least 1 segment");
329
+ }
330
+
331
+ const nx = normal?.x ?? 0, ny = normal?.y ?? 0, nz = normal?.z ?? 0;
332
+ const joinStyleCode = offsetJoinStyleCode(options.joinStyle);
333
+ const result = wasm.offset_polycurve_all(this.toSegmentData(), distance, nx, ny, nz, joinStyleCode);
334
+ if (result.length < 1) {
335
+ throw new Error("PolyCurve.offsetAll() failed - WASM returned empty result");
336
+ }
337
+ return PolyCurve.fromSegmentDataSet(result);
338
+ }
339
+
340
+ /**
341
+ * Thicken this open PolyCurve into a single closed loop.
342
+ * When `bothSides` is false, the source curve forms one side and the offset
343
+ * curve forms the other. When true, the distance is applied on each side of
344
+ * the source curve, doubling the overall width.
345
+ *
346
+ * For line-like inputs where the plane is ambiguous, provide `normal`.
347
+ */
348
+ thicken(
349
+ distance: number,
350
+ bothSides = false,
351
+ normal?: Vec3,
352
+ options: CurveOffsetOptions = {},
353
+ ): PolyCurve {
354
+ ensureInit();
355
+ if (this.segments.length < 1) {
356
+ throw new Error("PolyCurve.thicken() requires at least 1 segment");
357
+ }
358
+ if (!Number.isFinite(distance) || distance <= 0) {
359
+ throw new Error("PolyCurve.thicken() requires a positive distance");
360
+ }
361
+ if (this.isClosed()) {
362
+ throw new Error("PolyCurve.thicken() requires an open curve");
363
+ }
364
+
365
+ const nx = normal?.x ?? 0, ny = normal?.y ?? 0, nz = normal?.z ?? 0;
366
+ const joinStyleCode = offsetJoinStyleCode(options.joinStyle);
367
+ const result = wasm.thicken_polycurve(
368
+ this.toSegmentData(),
369
+ distance,
370
+ bothSides,
371
+ nx,
372
+ ny,
373
+ nz,
374
+ joinStyleCode,
375
+ );
376
+ if (result.length < 1) {
377
+ throw new Error("PolyCurve.thicken() failed - WASM returned empty result");
378
+ }
379
+ return PolyCurve.fromSegmentData(result);
380
+ }
381
+
382
+ /**
383
+ * Convert to an exact NURBS curve representation via WASM.
384
+ * Lines become degree-1 NURBS, Arcs become degree-2 rational NURBS.
385
+ * All segments are degree-elevated to a common degree and joined.
317
386
  */
318
- toNurbs(): NurbsCurve {
319
- ensureInit();
320
- const segData: number[] = [this.segments.length];
321
- for (const seg of this.segments) {
322
- if (seg instanceof Line) {
323
- segData.push(SegmentTypeCode.Line);
324
- segData.push(seg.start.x, seg.start.y, seg.start.z);
325
- segData.push(seg.end.x, seg.end.y, seg.end.z);
326
- } else {
327
- // Arc
328
- segData.push(SegmentTypeCode.Arc);
329
- segData.push(seg.center.x, seg.center.y, seg.center.z);
330
- segData.push(seg.normal.x, seg.normal.y, seg.normal.z);
331
- segData.push(seg.radius, seg.startAngle, seg.endAngle);
332
- }
333
- }
334
- const buf = wasm.polycurve_to_nurbs(new Float64Array(segData));
335
- if (buf.length < 2) throw new Error("Failed to convert PolyCurve to NURBS");
336
- return NurbsCurve.fromData(buf);
337
- }
338
- }
387
+ toNurbs(): NurbsCurve {
388
+ ensureInit();
389
+ const buf = wasm.polycurve_to_nurbs(this.toSegmentData());
390
+ if (buf.length < 2) throw new Error("Failed to convert PolyCurve to NURBS");
391
+ return NurbsCurve.fromData(buf);
392
+ }
393
+
394
+ private toSegmentData(): Float64Array {
395
+ const segData: number[] = [this.segments.length];
396
+ for (const seg of this.segments) {
397
+ if (seg instanceof Line) {
398
+ segData.push(SegmentTypeCode.Line);
399
+ segData.push(seg.start.x, seg.start.y, seg.start.z);
400
+ segData.push(seg.end.x, seg.end.y, seg.end.z);
401
+ } else {
402
+ segData.push(SegmentTypeCode.Arc);
403
+ segData.push(seg.center.x, seg.center.y, seg.center.z);
404
+ segData.push(seg.normal.x, seg.normal.y, seg.normal.z);
405
+ segData.push(seg.radius, seg.startAngle, seg.endAngle);
406
+ }
407
+ }
408
+ return new Float64Array(segData);
409
+ }
410
+ }
package/src/Polygon.ts CHANGED
@@ -2,7 +2,7 @@ import { Point } from "./Point.js";
2
2
  import { Vec3 } from "./Vec3.js";
3
3
  import { Polyline } from "./Polyline.js";
4
4
  import type { Plane } from "./Plane.js";
5
- import type { RotationAxis } from "./types.js";
5
+ import type { CurveOffsetOptions, RotationAxis } from "./types.js";
6
6
 
7
7
  /**
8
8
  * A closed polygon defined by vertices.
@@ -55,11 +55,41 @@ export class Polygon extends Polyline {
55
55
  * Offset this polygon perpendicular to its edges.
56
56
  * @param distance - Offset distance
57
57
  * @param normal - Reference normal for determining offset direction
58
+ * @param arcSamples - Number of samples per arc join in the returned polygon(s)
59
+ * @param options - Offset options such as join style
58
60
  * @returns New offset polygon
59
61
  */
60
- override offset(distance: number, normal?: Vec3): Polygon {
61
- const pl = super.offset(distance, normal);
62
- return new Polygon(pl.points);
62
+ override offset(
63
+ distance: number,
64
+ normal?: Vec3,
65
+ arcSamples = 32,
66
+ options: CurveOffsetOptions = {},
67
+ ): Polygon {
68
+ const results = this.offsetAll(distance, normal, arcSamples, options);
69
+ if (results.length === 1) return results[0];
70
+ if (results.length === 0) {
71
+ throw new Error("Polygon.offset() collapsed and produced no valid result polygons");
72
+ }
73
+ throw new Error(
74
+ `Polygon.offset() produced ${results.length} result polygons. Use Polygon.offsetAll() instead.`,
75
+ );
76
+ }
77
+
78
+ /**
79
+ * Offset this polygon and return every sampled result polygon.
80
+ * @param distance - Offset distance
81
+ * @param normal - Reference normal for determining offset direction
82
+ * @param arcSamples - Number of samples per exact arc segment in the returned polygons
83
+ * @param options - Offset options such as join style
84
+ * @returns Every valid offset polygon
85
+ */
86
+ offsetAll(
87
+ distance: number,
88
+ normal?: Vec3,
89
+ arcSamples = 32,
90
+ options: CurveOffsetOptions = {},
91
+ ): Polygon[] {
92
+ return super.offsetAll(distance, normal, arcSamples, options).map(polyline => new Polygon(polyline.points));
63
93
  }
64
94
 
65
95
  /**