okgeometry-api 1.1.6 → 1.1.7

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 (52) hide show
  1. package/dist/Arc.js +1 -1
  2. package/dist/Arc.js.map +1 -1
  3. package/dist/Circle.js +1 -1
  4. package/dist/Circle.js.map +1 -1
  5. package/dist/Line.js +1 -1
  6. package/dist/Line.js.map +1 -1
  7. package/dist/Mesh.d.ts +101 -4
  8. package/dist/Mesh.d.ts.map +1 -1
  9. package/dist/Mesh.js +103 -5
  10. package/dist/Mesh.js.map +1 -1
  11. package/dist/NurbsCurve.js +1 -1
  12. package/dist/NurbsCurve.js.map +1 -1
  13. package/dist/NurbsSurface.d.ts.map +1 -1
  14. package/dist/NurbsSurface.js +10 -7
  15. package/dist/NurbsSurface.js.map +1 -1
  16. package/dist/PolyCurve.js +1 -1
  17. package/dist/PolyCurve.js.map +1 -1
  18. package/dist/Polyline.js +1 -1
  19. package/dist/Polyline.js.map +1 -1
  20. package/dist/Ray.js +1 -1
  21. package/dist/Ray.js.map +1 -1
  22. package/dist/engine.d.ts.map +1 -1
  23. package/dist/engine.js +1 -3
  24. package/dist/engine.js.map +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/wasm-base64.d.ts +1 -1
  29. package/dist/wasm-base64.d.ts.map +1 -1
  30. package/dist/wasm-base64.js +1 -1
  31. package/dist/wasm-base64.js.map +1 -1
  32. package/package.json +7 -6
  33. package/src/Arc.ts +117 -117
  34. package/src/Circle.ts +153 -153
  35. package/src/Line.ts +144 -144
  36. package/src/Mesh.ts +671 -452
  37. package/src/NurbsCurve.ts +240 -240
  38. package/src/NurbsSurface.ts +249 -245
  39. package/src/PolyCurve.ts +306 -306
  40. package/src/Polyline.ts +153 -153
  41. package/src/Ray.ts +90 -90
  42. package/src/engine.ts +9 -11
  43. package/src/index.ts +6 -0
  44. package/src/wasm-base64.ts +1 -1
  45. package/wasm/README.md +0 -104
  46. package/wasm/okgeometrycore.d.ts +0 -754
  47. package/wasm/okgeometrycore.js +0 -2005
  48. package/wasm/okgeometrycore_bg.d.ts +0 -3
  49. package/wasm/okgeometrycore_bg.js +0 -1686
  50. package/wasm/okgeometrycore_bg.wasm +0 -0
  51. package/wasm/okgeometrycore_bg.wasm.d.ts +0 -100
  52. package/wasm/package.json +0 -19
package/src/PolyCurve.ts CHANGED
@@ -1,306 +1,306 @@
1
- import { ensureInit } from "./engine.js";
2
- import { Point } from "./Point.js";
3
- import { Line } from "./Line.js";
4
- import { Arc } from "./Arc.js";
5
- import { Vec3 } from "./Vec3.js";
6
- import { NurbsCurve } from "./NurbsCurve.js";
7
- import type { Plane } from "./Plane.js";
8
- import type { CurveSegment, RotationAxis } from "./types.js";
9
- import { SegmentTypeCode } from "./types.js";
10
- import { pointsToCoords, coordsToPoints } from "./BufferCodec.js";
11
- import * as wasm from "../wasm/okgeometrycore_bg.js";
12
-
13
- /**
14
- * Composite curve made of sequential Line and Arc segments.
15
- */
16
- export class PolyCurve {
17
- public readonly segments: CurveSegment[];
18
-
19
- constructor(segments: CurveSegment[]) {
20
- this.segments = segments;
21
- }
22
-
23
- /** Create a PolyCurve by connecting points with line segments */
24
- static byPoints(points: Point[]): PolyCurve {
25
- if (points.length < 2) return new PolyCurve([]);
26
- const segs: CurveSegment[] = [];
27
- for (let i = 0; i < points.length - 1; i++) {
28
- segs.push(new Line(points[i], points[i + 1]));
29
- }
30
- return new PolyCurve(segs);
31
- }
32
-
33
- get startPoint(): Point {
34
- if (this.segments.length === 0) return Point.ORIGIN;
35
- const s = this.segments[0];
36
- return s instanceof Line ? s.start : s.pointAt(0);
37
- }
38
-
39
- get endPoint(): Point {
40
- if (this.segments.length === 0) return Point.ORIGIN;
41
- const s = this.segments[this.segments.length - 1];
42
- return s instanceof Line ? s.end : s.pointAt(1);
43
- }
44
-
45
- /** Returns all constituent curves (Line and Arc segments) in order. */
46
- get curves(): CurveSegment[] {
47
- return this.segments;
48
- }
49
-
50
- /**
51
- * Sample all segments into a flat array of points for rendering.
52
- * Line segments produce 2 points; Arc segments are sampled with `arcSamples` points.
53
- * Consecutive duplicate junction points are removed.
54
- */
55
- sample(arcSamples = 32): Point[] {
56
- const pts: Point[] = [];
57
- for (const seg of this.segments) {
58
- let segPts: Point[];
59
- if (seg instanceof Line) {
60
- segPts = [seg.start, seg.end];
61
- } else {
62
- // Arc: sample into multiple points for smooth rendering
63
- segPts = seg.sample(arcSamples);
64
- }
65
- for (const p of segPts) {
66
- // Skip duplicate junction points
67
- if (pts.length > 0 && pts[pts.length - 1].equals(p)) continue;
68
- pts.push(p);
69
- }
70
- }
71
- return pts;
72
- }
73
-
74
- /** Evaluate a point on the curve at normalized parameter t (0 = start, 1 = end). */
75
- pointAt(t: number): Point {
76
- if (this.segments.length === 0) return Point.ORIGIN;
77
- if (t <= 0) return this.startPoint;
78
- if (t >= 1) return this.endPoint;
79
-
80
- const lengths = this.segments.map(s => s.length());
81
- const total = lengths.reduce((a, b) => a + b, 0);
82
- if (total === 0) return this.startPoint;
83
-
84
- let target = t * total;
85
- for (let i = 0; i < this.segments.length; i++) {
86
- if (target <= lengths[i] || i === this.segments.length - 1) {
87
- const localT = lengths[i] > 0 ? target / lengths[i] : 0;
88
- return this.segments[i].pointAt(Math.min(localT, 1));
89
- }
90
- target -= lengths[i];
91
- }
92
- return this.endPoint;
93
- }
94
-
95
- isClosed(eps = 1e-10): boolean {
96
- return this.startPoint.equals(this.endPoint, eps);
97
- }
98
-
99
- length(): number {
100
- return this.segments.reduce((sum, s) => sum + s.length(), 0);
101
- }
102
-
103
- rotate(axis: RotationAxis, angle: number): PolyCurve {
104
- return new PolyCurve(this.segments.map(s => s.rotate(axis, angle)));
105
- }
106
-
107
- translate(offset: Vec3): PolyCurve {
108
- return new PolyCurve(this.segments.map(s => s.translate(offset)));
109
- }
110
-
111
- projectOntoPlane(plane: Plane, direction?: Vec3, arcSamples = 32): PolyCurve {
112
- const proj = (p: Point) => direction ? plane.projectPointAlongDirection(p, direction) : plane.projectPoint(p);
113
- const segs: CurveSegment[] = [];
114
- for (const s of this.segments) {
115
- if (s instanceof Line) {
116
- segs.push(new Line(proj(s.start), proj(s.end)));
117
- } else {
118
- // Arc projects to elliptical arc — approximate with line segments
119
- const pts = s.sample(arcSamples);
120
- for (let i = 0; i < pts.length - 1; i++) {
121
- segs.push(new Line(proj(pts[i]), proj(pts[i + 1])));
122
- }
123
- }
124
- }
125
- return new PolyCurve(segs);
126
- }
127
-
128
- /**
129
- * Fillet corners of this PolyCurve with arcs of the given radius.
130
- * Extracts the vertex points, calls WASM fillet_corners, returns a new PolyCurve.
131
- */
132
- fillet(radius: number, normal?: Vec3): PolyCurve {
133
- ensureInit();
134
- if (this.segments.length < 2) {
135
- throw new Error("PolyCurve.fillet() requires at least 2 segments");
136
- }
137
-
138
- // Extract all vertex points from segments
139
- const pts: Point[] = [];
140
- for (const seg of this.segments) {
141
- if (seg instanceof Line) {
142
- if (pts.length === 0) pts.push(seg.start);
143
- pts.push(seg.end);
144
- } else {
145
- // Arc: use start and end points
146
- if (pts.length === 0) pts.push(seg.pointAt(0));
147
- pts.push(seg.pointAt(1));
148
- }
149
- }
150
- if (pts.length < 3) {
151
- throw new Error("PolyCurve.fillet() requires at least 3 vertices");
152
- }
153
-
154
- const coords = new Float64Array(pts.length * 3);
155
- for (let i = 0; i < pts.length; i++) {
156
- coords[i * 3] = pts[i].x;
157
- coords[i * 3 + 1] = pts[i].y;
158
- coords[i * 3 + 2] = pts[i].z;
159
- }
160
-
161
- const nx = normal?.x ?? 0, ny = normal?.y ?? 0, nz = normal?.z ?? 0;
162
- const buf = wasm.fillet_polycurve(coords, radius, nx, ny, nz);
163
- if (buf.length < 1) {
164
- throw new Error("PolyCurve.fillet() failed - WASM returned empty result");
165
- }
166
- return PolyCurve.fromSegmentData(buf);
167
- }
168
-
169
- /** Decode a PolyCurve from WASM segment buffer [count, type, ...data, ...] */
170
- static fromSegmentData(buf: Float64Array | number[]): PolyCurve {
171
- const count = buf[0];
172
- const segs: CurveSegment[] = [];
173
- let idx = 1;
174
- for (let i = 0; i < count; i++) {
175
- const type = buf[idx++];
176
- if (type === SegmentTypeCode.Line) {
177
- // Line: 6 floats
178
- const s = new Point(buf[idx], buf[idx + 1], buf[idx + 2]);
179
- const e = new Point(buf[idx + 3], buf[idx + 4], buf[idx + 5]);
180
- segs.push(new Line(s, e));
181
- idx += 6;
182
- } else if (type === SegmentTypeCode.Arc) {
183
- // Arc: 9 floats
184
- const center = new Point(buf[idx], buf[idx + 1], buf[idx + 2]);
185
- const normal = new Vec3(buf[idx + 3], buf[idx + 4], buf[idx + 5]);
186
- const radius = buf[idx + 6];
187
- const startAngle = buf[idx + 7];
188
- const endAngle = buf[idx + 8];
189
- segs.push(new Arc(center, radius, startAngle, endAngle, normal));
190
- idx += 9;
191
- }
192
- }
193
- return new PolyCurve(segs);
194
- }
195
-
196
- /**
197
- * Chamfer corners of this PolyCurve by cutting each corner with a straight segment.
198
- * Returns a new PolyCurve of line segments only.
199
- */
200
- chamfer(distance: number): PolyCurve {
201
- ensureInit();
202
- if (this.segments.length < 2) {
203
- throw new Error("PolyCurve.chamfer() requires at least 2 segments");
204
- }
205
-
206
- const pts: Point[] = [];
207
- for (const seg of this.segments) {
208
- if (seg instanceof Line) {
209
- if (pts.length === 0) pts.push(seg.start);
210
- pts.push(seg.end);
211
- } else {
212
- if (pts.length === 0) pts.push(seg.pointAt(0));
213
- pts.push(seg.pointAt(1));
214
- }
215
- }
216
- if (pts.length < 3) {
217
- throw new Error("PolyCurve.chamfer() requires at least 3 vertices");
218
- }
219
-
220
- const coords = new Float64Array(pts.length * 3);
221
- for (let i = 0; i < pts.length; i++) {
222
- coords[i * 3] = pts[i].x;
223
- coords[i * 3 + 1] = pts[i].y;
224
- coords[i * 3 + 2] = pts[i].z;
225
- }
226
-
227
- const buf = wasm.chamfer_polycurve(coords, distance);
228
- if (buf.length < 1) {
229
- throw new Error("PolyCurve.chamfer() failed - WASM returned empty result");
230
- }
231
- return PolyCurve.fromSegmentData(buf);
232
- }
233
-
234
- /**
235
- * Offset this PolyCurve by a distance in the given plane.
236
- * Extracts vertex points, offsets as a polyline, returns a new PolyCurve of line segments.
237
- */
238
- offset(distance: number, normal?: Vec3): PolyCurve {
239
- ensureInit();
240
- if (this.segments.length < 1) {
241
- throw new Error("PolyCurve.offset() requires at least 1 segment");
242
- }
243
-
244
- const pts: Point[] = [];
245
- for (const seg of this.segments) {
246
- if (seg instanceof Line) {
247
- if (pts.length === 0) pts.push(seg.start);
248
- pts.push(seg.end);
249
- } else {
250
- if (pts.length === 0) pts.push(seg.pointAt(0));
251
- pts.push(seg.pointAt(1));
252
- }
253
- }
254
- if (pts.length < 2) {
255
- throw new Error("PolyCurve.offset() requires at least 2 vertices");
256
- }
257
-
258
- const coords = new Float64Array(pts.length * 3);
259
- for (let i = 0; i < pts.length; i++) {
260
- coords[i * 3] = pts[i].x;
261
- coords[i * 3 + 1] = pts[i].y;
262
- coords[i * 3 + 2] = pts[i].z;
263
- }
264
-
265
- const nx = normal?.x ?? 0, ny = normal?.y ?? 0, nz = normal?.z ?? 0;
266
- const result = wasm.offset_polyline_curve(coords, distance, nx, ny, nz);
267
- if (result.length < 6) {
268
- throw new Error("PolyCurve.offset() failed - WASM returned insufficient data");
269
- }
270
-
271
- // Convert flat coords back to line segments
272
- const segs: CurveSegment[] = [];
273
- for (let i = 0; i < result.length - 3; i += 3) {
274
- const s = new Point(result[i], result[i + 1], result[i + 2]);
275
- const e = new Point(result[i + 3], result[i + 4], result[i + 5]);
276
- segs.push(new Line(s, e));
277
- }
278
- return new PolyCurve(segs);
279
- }
280
-
281
- /**
282
- * Convert to an exact NURBS curve representation via WASM.
283
- * Lines become degree-1 NURBS, Arcs become degree-2 rational NURBS.
284
- * All segments are degree-elevated to a common degree and joined.
285
- */
286
- toNurbs(): NurbsCurve {
287
- ensureInit();
288
- const segData: number[] = [this.segments.length];
289
- for (const seg of this.segments) {
290
- if (seg instanceof Line) {
291
- segData.push(SegmentTypeCode.Line);
292
- segData.push(seg.start.x, seg.start.y, seg.start.z);
293
- segData.push(seg.end.x, seg.end.y, seg.end.z);
294
- } else {
295
- // Arc
296
- segData.push(SegmentTypeCode.Arc);
297
- segData.push(seg.center.x, seg.center.y, seg.center.z);
298
- segData.push(seg.normal.x, seg.normal.y, seg.normal.z);
299
- segData.push(seg.radius, seg.startAngle, seg.endAngle);
300
- }
301
- }
302
- const buf = wasm.polycurve_to_nurbs(new Float64Array(segData));
303
- if (buf.length < 2) throw new Error("Failed to convert PolyCurve to NURBS");
304
- return NurbsCurve.fromData(buf);
305
- }
306
- }
1
+ import { ensureInit } from "./engine.js";
2
+ import { Point } from "./Point.js";
3
+ import { Line } from "./Line.js";
4
+ import { Arc } from "./Arc.js";
5
+ import { Vec3 } from "./Vec3.js";
6
+ import { NurbsCurve } from "./NurbsCurve.js";
7
+ import type { Plane } from "./Plane.js";
8
+ import type { CurveSegment, RotationAxis } from "./types.js";
9
+ import { SegmentTypeCode } from "./types.js";
10
+ import { pointsToCoords, coordsToPoints } from "./BufferCodec.js";
11
+ import * as wasm from "../wasm/okgeometrycore.js";
12
+
13
+ /**
14
+ * Composite curve made of sequential Line and Arc segments.
15
+ */
16
+ export class PolyCurve {
17
+ public readonly segments: CurveSegment[];
18
+
19
+ constructor(segments: CurveSegment[]) {
20
+ this.segments = segments;
21
+ }
22
+
23
+ /** Create a PolyCurve by connecting points with line segments */
24
+ static byPoints(points: Point[]): PolyCurve {
25
+ if (points.length < 2) return new PolyCurve([]);
26
+ const segs: CurveSegment[] = [];
27
+ for (let i = 0; i < points.length - 1; i++) {
28
+ segs.push(new Line(points[i], points[i + 1]));
29
+ }
30
+ return new PolyCurve(segs);
31
+ }
32
+
33
+ get startPoint(): Point {
34
+ if (this.segments.length === 0) return Point.ORIGIN;
35
+ const s = this.segments[0];
36
+ return s instanceof Line ? s.start : s.pointAt(0);
37
+ }
38
+
39
+ get endPoint(): Point {
40
+ if (this.segments.length === 0) return Point.ORIGIN;
41
+ const s = this.segments[this.segments.length - 1];
42
+ return s instanceof Line ? s.end : s.pointAt(1);
43
+ }
44
+
45
+ /** Returns all constituent curves (Line and Arc segments) in order. */
46
+ get curves(): CurveSegment[] {
47
+ return this.segments;
48
+ }
49
+
50
+ /**
51
+ * Sample all segments into a flat array of points for rendering.
52
+ * Line segments produce 2 points; Arc segments are sampled with `arcSamples` points.
53
+ * Consecutive duplicate junction points are removed.
54
+ */
55
+ sample(arcSamples = 32): Point[] {
56
+ const pts: Point[] = [];
57
+ for (const seg of this.segments) {
58
+ let segPts: Point[];
59
+ if (seg instanceof Line) {
60
+ segPts = [seg.start, seg.end];
61
+ } else {
62
+ // Arc: sample into multiple points for smooth rendering
63
+ segPts = seg.sample(arcSamples);
64
+ }
65
+ for (const p of segPts) {
66
+ // Skip duplicate junction points
67
+ if (pts.length > 0 && pts[pts.length - 1].equals(p)) continue;
68
+ pts.push(p);
69
+ }
70
+ }
71
+ return pts;
72
+ }
73
+
74
+ /** Evaluate a point on the curve at normalized parameter t (0 = start, 1 = end). */
75
+ pointAt(t: number): Point {
76
+ if (this.segments.length === 0) return Point.ORIGIN;
77
+ if (t <= 0) return this.startPoint;
78
+ if (t >= 1) return this.endPoint;
79
+
80
+ const lengths = this.segments.map(s => s.length());
81
+ const total = lengths.reduce((a, b) => a + b, 0);
82
+ if (total === 0) return this.startPoint;
83
+
84
+ let target = t * total;
85
+ for (let i = 0; i < this.segments.length; i++) {
86
+ if (target <= lengths[i] || i === this.segments.length - 1) {
87
+ const localT = lengths[i] > 0 ? target / lengths[i] : 0;
88
+ return this.segments[i].pointAt(Math.min(localT, 1));
89
+ }
90
+ target -= lengths[i];
91
+ }
92
+ return this.endPoint;
93
+ }
94
+
95
+ isClosed(eps = 1e-10): boolean {
96
+ return this.startPoint.equals(this.endPoint, eps);
97
+ }
98
+
99
+ length(): number {
100
+ return this.segments.reduce((sum, s) => sum + s.length(), 0);
101
+ }
102
+
103
+ rotate(axis: RotationAxis, angle: number): PolyCurve {
104
+ return new PolyCurve(this.segments.map(s => s.rotate(axis, angle)));
105
+ }
106
+
107
+ translate(offset: Vec3): PolyCurve {
108
+ return new PolyCurve(this.segments.map(s => s.translate(offset)));
109
+ }
110
+
111
+ projectOntoPlane(plane: Plane, direction?: Vec3, arcSamples = 32): PolyCurve {
112
+ const proj = (p: Point) => direction ? plane.projectPointAlongDirection(p, direction) : plane.projectPoint(p);
113
+ const segs: CurveSegment[] = [];
114
+ for (const s of this.segments) {
115
+ if (s instanceof Line) {
116
+ segs.push(new Line(proj(s.start), proj(s.end)));
117
+ } else {
118
+ // Arc projects to elliptical arc — approximate with line segments
119
+ const pts = s.sample(arcSamples);
120
+ for (let i = 0; i < pts.length - 1; i++) {
121
+ segs.push(new Line(proj(pts[i]), proj(pts[i + 1])));
122
+ }
123
+ }
124
+ }
125
+ return new PolyCurve(segs);
126
+ }
127
+
128
+ /**
129
+ * Fillet corners of this PolyCurve with arcs of the given radius.
130
+ * Extracts the vertex points, calls WASM fillet_corners, returns a new PolyCurve.
131
+ */
132
+ fillet(radius: number, normal?: Vec3): PolyCurve {
133
+ ensureInit();
134
+ if (this.segments.length < 2) {
135
+ throw new Error("PolyCurve.fillet() requires at least 2 segments");
136
+ }
137
+
138
+ // Extract all vertex points from segments
139
+ const pts: Point[] = [];
140
+ for (const seg of this.segments) {
141
+ if (seg instanceof Line) {
142
+ if (pts.length === 0) pts.push(seg.start);
143
+ pts.push(seg.end);
144
+ } else {
145
+ // Arc: use start and end points
146
+ if (pts.length === 0) pts.push(seg.pointAt(0));
147
+ pts.push(seg.pointAt(1));
148
+ }
149
+ }
150
+ if (pts.length < 3) {
151
+ throw new Error("PolyCurve.fillet() requires at least 3 vertices");
152
+ }
153
+
154
+ const coords = new Float64Array(pts.length * 3);
155
+ for (let i = 0; i < pts.length; i++) {
156
+ coords[i * 3] = pts[i].x;
157
+ coords[i * 3 + 1] = pts[i].y;
158
+ coords[i * 3 + 2] = pts[i].z;
159
+ }
160
+
161
+ const nx = normal?.x ?? 0, ny = normal?.y ?? 0, nz = normal?.z ?? 0;
162
+ const buf = wasm.fillet_polycurve(coords, radius, nx, ny, nz);
163
+ if (buf.length < 1) {
164
+ throw new Error("PolyCurve.fillet() failed - WASM returned empty result");
165
+ }
166
+ return PolyCurve.fromSegmentData(buf);
167
+ }
168
+
169
+ /** Decode a PolyCurve from WASM segment buffer [count, type, ...data, ...] */
170
+ static fromSegmentData(buf: Float64Array | number[]): PolyCurve {
171
+ const count = buf[0];
172
+ const segs: CurveSegment[] = [];
173
+ let idx = 1;
174
+ for (let i = 0; i < count; i++) {
175
+ const type = buf[idx++];
176
+ if (type === SegmentTypeCode.Line) {
177
+ // Line: 6 floats
178
+ const s = new Point(buf[idx], buf[idx + 1], buf[idx + 2]);
179
+ const e = new Point(buf[idx + 3], buf[idx + 4], buf[idx + 5]);
180
+ segs.push(new Line(s, e));
181
+ idx += 6;
182
+ } else if (type === SegmentTypeCode.Arc) {
183
+ // Arc: 9 floats
184
+ const center = new Point(buf[idx], buf[idx + 1], buf[idx + 2]);
185
+ const normal = new Vec3(buf[idx + 3], buf[idx + 4], buf[idx + 5]);
186
+ const radius = buf[idx + 6];
187
+ const startAngle = buf[idx + 7];
188
+ const endAngle = buf[idx + 8];
189
+ segs.push(new Arc(center, radius, startAngle, endAngle, normal));
190
+ idx += 9;
191
+ }
192
+ }
193
+ return new PolyCurve(segs);
194
+ }
195
+
196
+ /**
197
+ * Chamfer corners of this PolyCurve by cutting each corner with a straight segment.
198
+ * Returns a new PolyCurve of line segments only.
199
+ */
200
+ chamfer(distance: number): PolyCurve {
201
+ ensureInit();
202
+ if (this.segments.length < 2) {
203
+ throw new Error("PolyCurve.chamfer() requires at least 2 segments");
204
+ }
205
+
206
+ const pts: Point[] = [];
207
+ for (const seg of this.segments) {
208
+ if (seg instanceof Line) {
209
+ if (pts.length === 0) pts.push(seg.start);
210
+ pts.push(seg.end);
211
+ } else {
212
+ if (pts.length === 0) pts.push(seg.pointAt(0));
213
+ pts.push(seg.pointAt(1));
214
+ }
215
+ }
216
+ if (pts.length < 3) {
217
+ throw new Error("PolyCurve.chamfer() requires at least 3 vertices");
218
+ }
219
+
220
+ const coords = new Float64Array(pts.length * 3);
221
+ for (let i = 0; i < pts.length; i++) {
222
+ coords[i * 3] = pts[i].x;
223
+ coords[i * 3 + 1] = pts[i].y;
224
+ coords[i * 3 + 2] = pts[i].z;
225
+ }
226
+
227
+ const buf = wasm.chamfer_polycurve(coords, distance);
228
+ if (buf.length < 1) {
229
+ throw new Error("PolyCurve.chamfer() failed - WASM returned empty result");
230
+ }
231
+ return PolyCurve.fromSegmentData(buf);
232
+ }
233
+
234
+ /**
235
+ * Offset this PolyCurve by a distance in the given plane.
236
+ * Extracts vertex points, offsets as a polyline, returns a new PolyCurve of line segments.
237
+ */
238
+ offset(distance: number, normal?: Vec3): PolyCurve {
239
+ ensureInit();
240
+ if (this.segments.length < 1) {
241
+ throw new Error("PolyCurve.offset() requires at least 1 segment");
242
+ }
243
+
244
+ const pts: Point[] = [];
245
+ for (const seg of this.segments) {
246
+ if (seg instanceof Line) {
247
+ if (pts.length === 0) pts.push(seg.start);
248
+ pts.push(seg.end);
249
+ } else {
250
+ if (pts.length === 0) pts.push(seg.pointAt(0));
251
+ pts.push(seg.pointAt(1));
252
+ }
253
+ }
254
+ if (pts.length < 2) {
255
+ throw new Error("PolyCurve.offset() requires at least 2 vertices");
256
+ }
257
+
258
+ const coords = new Float64Array(pts.length * 3);
259
+ for (let i = 0; i < pts.length; i++) {
260
+ coords[i * 3] = pts[i].x;
261
+ coords[i * 3 + 1] = pts[i].y;
262
+ coords[i * 3 + 2] = pts[i].z;
263
+ }
264
+
265
+ const nx = normal?.x ?? 0, ny = normal?.y ?? 0, nz = normal?.z ?? 0;
266
+ const result = wasm.offset_polyline_curve(coords, distance, nx, ny, nz);
267
+ if (result.length < 6) {
268
+ throw new Error("PolyCurve.offset() failed - WASM returned insufficient data");
269
+ }
270
+
271
+ // Convert flat coords back to line segments
272
+ const segs: CurveSegment[] = [];
273
+ for (let i = 0; i < result.length - 3; i += 3) {
274
+ const s = new Point(result[i], result[i + 1], result[i + 2]);
275
+ const e = new Point(result[i + 3], result[i + 4], result[i + 5]);
276
+ segs.push(new Line(s, e));
277
+ }
278
+ return new PolyCurve(segs);
279
+ }
280
+
281
+ /**
282
+ * Convert to an exact NURBS curve representation via WASM.
283
+ * Lines become degree-1 NURBS, Arcs become degree-2 rational NURBS.
284
+ * All segments are degree-elevated to a common degree and joined.
285
+ */
286
+ toNurbs(): NurbsCurve {
287
+ ensureInit();
288
+ const segData: number[] = [this.segments.length];
289
+ for (const seg of this.segments) {
290
+ if (seg instanceof Line) {
291
+ segData.push(SegmentTypeCode.Line);
292
+ segData.push(seg.start.x, seg.start.y, seg.start.z);
293
+ segData.push(seg.end.x, seg.end.y, seg.end.z);
294
+ } else {
295
+ // Arc
296
+ segData.push(SegmentTypeCode.Arc);
297
+ segData.push(seg.center.x, seg.center.y, seg.center.z);
298
+ segData.push(seg.normal.x, seg.normal.y, seg.normal.z);
299
+ segData.push(seg.radius, seg.startAngle, seg.endAngle);
300
+ }
301
+ }
302
+ const buf = wasm.polycurve_to_nurbs(new Float64Array(segData));
303
+ if (buf.length < 2) throw new Error("Failed to convert PolyCurve to NURBS");
304
+ return NurbsCurve.fromData(buf);
305
+ }
306
+ }