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.
- package/dist/Arc.js +1 -1
- package/dist/Arc.js.map +1 -1
- package/dist/Brep.js +1 -1
- package/dist/Brep.js.map +1 -1
- package/dist/Circle.js +1 -1
- package/dist/Circle.js.map +1 -1
- package/dist/Line.js +1 -1
- package/dist/Line.js.map +1 -1
- package/dist/Mesh.d.ts +41 -0
- package/dist/Mesh.d.ts.map +1 -1
- package/dist/Mesh.js +235 -21
- package/dist/Mesh.js.map +1 -1
- package/dist/MeshSurface.js +1 -1
- package/dist/MeshSurface.js.map +1 -1
- package/dist/NurbsCurve.js +1 -1
- package/dist/NurbsCurve.js.map +1 -1
- package/dist/NurbsSurface.js +1 -1
- package/dist/NurbsSurface.js.map +1 -1
- package/dist/PolyCurve.js +1 -1
- package/dist/PolyCurve.js.map +1 -1
- package/dist/Polyline.js +1 -1
- package/dist/Polyline.js.map +1 -1
- package/dist/Ray.js +1 -1
- package/dist/Ray.js.map +1 -1
- package/dist/SculptSession.js +1 -1
- package/dist/SculptSession.js.map +1 -1
- package/dist/engine-threaded.d.ts +2 -0
- package/dist/engine-threaded.d.ts.map +1 -0
- package/dist/engine-threaded.js +33 -0
- package/dist/engine-threaded.js.map +1 -0
- package/dist/engine.d.ts +21 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +42 -0
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mesh-boolean.pool.d.ts +23 -1
- package/dist/mesh-boolean.pool.d.ts.map +1 -1
- package/dist/mesh-boolean.pool.js +97 -12
- package/dist/mesh-boolean.pool.js.map +1 -1
- package/dist/mesh-boolean.protocol.d.ts +21 -1
- package/dist/mesh-boolean.protocol.d.ts.map +1 -1
- package/dist/mesh-boolean.protocol.js.map +1 -1
- package/dist/mesh-boolean.worker.d.ts.map +1 -1
- package/dist/mesh-boolean.worker.js +94 -1
- package/dist/mesh-boolean.worker.js.map +1 -1
- package/dist/wasm-active.d.ts +20 -0
- package/dist/wasm-active.d.ts.map +1 -0
- package/dist/wasm-active.js +26 -0
- package/dist/wasm-active.js.map +1 -0
- package/dist/wasm-base64-threaded.d.ts +2 -0
- package/dist/wasm-base64-threaded.d.ts.map +1 -0
- package/dist/wasm-base64-threaded.js +3 -0
- package/dist/wasm-base64-threaded.js.map +1 -0
- package/dist/wasm-base64.d.ts +1 -1
- package/dist/wasm-base64.d.ts.map +1 -1
- package/dist/wasm-base64.js +1 -1
- package/dist/wasm-base64.js.map +1 -1
- package/dist/wasm-bindings-threaded.d.ts +2664 -0
- package/dist/wasm-bindings-threaded.d.ts.map +1 -0
- package/dist/wasm-bindings-threaded.js +5008 -0
- package/dist/wasm-bindings-threaded.js.map +1 -0
- package/dist/wasm-bindings.d.ts +130 -0
- package/dist/wasm-bindings.d.ts.map +1 -1
- package/dist/wasm-bindings.js +208 -0
- package/dist/wasm-bindings.js.map +1 -1
- package/dist/workerHelpers-threaded.d.ts +2 -0
- package/dist/workerHelpers-threaded.d.ts.map +1 -0
- package/dist/workerHelpers-threaded.js +100 -0
- package/dist/workerHelpers-threaded.js.map +1 -0
- package/package.json +8 -3
- package/src/Arc.ts +117 -117
- package/src/Brep.ts +1 -1
- package/src/Circle.ts +166 -166
- package/src/Line.ts +171 -171
- package/src/Mesh.ts +300 -45
- package/src/MeshSurface.ts +1 -1
- package/src/NurbsCurve.ts +655 -655
- package/src/NurbsSurface.ts +721 -721
- package/src/PolyCurve.ts +203 -203
- package/src/Polyline.ts +316 -316
- package/src/Ray.ts +90 -90
- package/src/SculptSession.ts +377 -377
- package/src/engine-threaded.ts +39 -0
- package/src/engine.ts +46 -0
- package/src/index.ts +1 -1
- package/src/mesh-boolean.pool.ts +208 -86
- package/src/mesh-boolean.protocol.ts +44 -1
- package/src/mesh-boolean.worker.ts +170 -56
- package/src/wasm-active.ts +31 -0
- package/src/wasm-base64-threaded.ts +2 -0
- package/src/wasm-base64.ts +1 -1
- package/src/wasm-bindings-threaded.d.ts +1989 -0
- package/src/wasm-bindings-threaded.js +5235 -0
- package/src/wasm-bindings.d.ts +104 -0
- package/src/wasm-bindings.js +225 -0
- 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
|
|
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
|
+
}
|