okgeometry-api 0.5.0 → 0.5.2
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/Mesh.d.ts +0 -32
- package/dist/Mesh.d.ts.map +1 -1
- package/dist/Mesh.js +8 -91
- package/dist/Mesh.js.map +1 -1
- package/dist/engine.d.ts +0 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +0 -37
- package/dist/engine.js.map +1 -1
- package/dist/mesh-boolean.protocol.d.ts +2 -2
- package/dist/mesh-boolean.protocol.d.ts.map +1 -1
- 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/package.json +46 -45
- package/src/Arc.ts +117 -0
- package/src/BufferCodec.ts +181 -0
- package/src/Circle.ts +153 -0
- package/src/Geometry.ts +39 -0
- package/src/Line.ts +144 -0
- package/src/Mesh.ts +1476 -0
- package/src/NurbsCurve.ts +240 -0
- package/src/NurbsSurface.ts +267 -0
- package/src/Plane.ts +132 -0
- package/src/Point.ts +145 -0
- package/src/PolyCurve.ts +306 -0
- package/src/Polygon.ts +75 -0
- package/src/Polyline.ts +153 -0
- package/src/Ray.ts +90 -0
- package/src/Vec3.ts +159 -0
- package/src/engine.ts +92 -0
- package/src/index.ts +45 -0
- package/src/mesh-boolean.pool.ts +481 -0
- package/src/mesh-boolean.protocol.ts +122 -0
- package/src/mesh-boolean.worker.ts +160 -0
- package/src/types.ts +84 -0
- package/src/wasm-base64.ts +2 -0
- package/wasm/okgeometrycore.d.ts +19 -20
- package/wasm/okgeometrycore.js +62 -26
- package/wasm/okgeometrycore_bg.js +47 -0
- package/wasm/okgeometrycore_bg.wasm +0 -0
- package/wasm/okgeometrycore_bg.wasm.d.ts +2 -2
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { ensureInit } from "./engine.js";
|
|
2
|
+
import { Point } from "./Point.js";
|
|
3
|
+
import { Vec3 } from "./Vec3.js";
|
|
4
|
+
import { Plane } from "./Plane.js";
|
|
5
|
+
import { Polyline } from "./Polyline.js";
|
|
6
|
+
import type { Line } from "./Line.js";
|
|
7
|
+
import type { Arc } from "./Arc.js";
|
|
8
|
+
import type { RotationAxis } from "./types.js";
|
|
9
|
+
import { parseIntersectionPoints } from "./BufferCodec.js";
|
|
10
|
+
import * as wasm from "../wasm/okgeometrycore_bg.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Non-Uniform Rational B-Spline (NURBS) curve backed by WASM.
|
|
14
|
+
* Provides exact representation for complex curves including conics.
|
|
15
|
+
*
|
|
16
|
+
* WASM data format: [degree, num_pts, x0,y0,z0, ..., w0,w1,..., k0,k1,...]
|
|
17
|
+
*/
|
|
18
|
+
export class NurbsCurve {
|
|
19
|
+
private _data: Float64Array;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a new NURBS curve.
|
|
23
|
+
* @param degree - Polynomial degree (1 = linear, 2 = quadratic, 3 = cubic)
|
|
24
|
+
* @param controlPoints - Control points defining the curve shape
|
|
25
|
+
* @param weights - Rational weights for each control point (use 1 for non-rational)
|
|
26
|
+
* @param knots - Knot vector (length = num_points + degree + 1)
|
|
27
|
+
*/
|
|
28
|
+
constructor(
|
|
29
|
+
public readonly degree: number,
|
|
30
|
+
public readonly controlPoints: Point[],
|
|
31
|
+
public readonly weights: number[],
|
|
32
|
+
public readonly knots: number[]
|
|
33
|
+
) {
|
|
34
|
+
this._data = NurbsCurve.encode(degree, controlPoints, weights, knots);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Evaluate a point on the curve at normalized parameter t.
|
|
39
|
+
* @param t - Parameter in [0, 1] (0 = start, 1 = end)
|
|
40
|
+
* @returns Point on curve at parameter t
|
|
41
|
+
*/
|
|
42
|
+
pointAt(t: number): Point {
|
|
43
|
+
ensureInit();
|
|
44
|
+
const r = wasm.evaluate_nurbs_curve_at(this._data, t);
|
|
45
|
+
return new Point(r[0], r[1], r[2]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Sample the curve into evenly-spaced points.
|
|
50
|
+
* @param n - Number of points to generate
|
|
51
|
+
* @returns Array of n points along the curve
|
|
52
|
+
*/
|
|
53
|
+
sample(n: number): Point[] {
|
|
54
|
+
ensureInit();
|
|
55
|
+
const buf = wasm.sample_nurbs_curve(this._data, n);
|
|
56
|
+
const pts: Point[] = [];
|
|
57
|
+
for (let i = 0; i < buf.length; i += 3) {
|
|
58
|
+
pts.push(new Point(buf[i], buf[i + 1], buf[i + 2]));
|
|
59
|
+
}
|
|
60
|
+
return pts;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Find intersection points with a plane.
|
|
65
|
+
* @param plane - Cutting plane
|
|
66
|
+
* @returns Array of intersection points
|
|
67
|
+
*/
|
|
68
|
+
intersectPlane(plane: Plane): Point[] {
|
|
69
|
+
ensureInit();
|
|
70
|
+
const buf = wasm.nurbs_curve_plane_intersect(
|
|
71
|
+
this._data,
|
|
72
|
+
plane.normal.x, plane.normal.y, plane.normal.z,
|
|
73
|
+
plane.d
|
|
74
|
+
);
|
|
75
|
+
if (buf.length === 0) return [];
|
|
76
|
+
const numPts = buf[0];
|
|
77
|
+
const pts: Point[] = [];
|
|
78
|
+
for (let i = 0; i < numPts; i++) {
|
|
79
|
+
const off = 1 + i * 3;
|
|
80
|
+
pts.push(new Point(buf[off], buf[off + 1], buf[off + 2]));
|
|
81
|
+
}
|
|
82
|
+
return pts;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Find intersection points with another NURBS curve.
|
|
87
|
+
* @param other - Other curve to intersect with
|
|
88
|
+
* @returns Array of intersection points
|
|
89
|
+
*/
|
|
90
|
+
intersectCurve(other: NurbsCurve): Point[] {
|
|
91
|
+
ensureInit();
|
|
92
|
+
const buf = wasm.nurbs_curve_curve_intersect(this._data, other._data);
|
|
93
|
+
if (buf.length === 0) return [];
|
|
94
|
+
const numPts = buf[0];
|
|
95
|
+
const pts: Point[] = [];
|
|
96
|
+
for (let i = 0; i < numPts; i++) {
|
|
97
|
+
const off = 1 + i * 3;
|
|
98
|
+
pts.push(new Point(buf[off], buf[off + 1], buf[off + 2]));
|
|
99
|
+
}
|
|
100
|
+
return pts;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Translate this curve by an offset vector.
|
|
105
|
+
* @param offset - Translation vector
|
|
106
|
+
* @returns New translated curve
|
|
107
|
+
*/
|
|
108
|
+
translate(offset: Vec3): NurbsCurve {
|
|
109
|
+
return new NurbsCurve(
|
|
110
|
+
this.degree,
|
|
111
|
+
this.controlPoints.map(p => p.add(offset)),
|
|
112
|
+
this.weights,
|
|
113
|
+
this.knots
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Rotate this curve around an axis.
|
|
119
|
+
* @param axis - Rotation axis (Vec3 through origin, or Line for arbitrary axis)
|
|
120
|
+
* @param angle - Rotation angle in radians
|
|
121
|
+
* @returns New rotated curve
|
|
122
|
+
*/
|
|
123
|
+
rotate(axis: RotationAxis, angle: number): NurbsCurve {
|
|
124
|
+
return new NurbsCurve(
|
|
125
|
+
this.degree,
|
|
126
|
+
this.controlPoints.map(p => p.rotate(axis, angle)),
|
|
127
|
+
this.weights,
|
|
128
|
+
this.knots
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Project this curve onto a plane.
|
|
134
|
+
* @param plane - Target plane
|
|
135
|
+
* @param direction - Optional projection direction (default: perpendicular to plane)
|
|
136
|
+
* @returns New projected curve
|
|
137
|
+
*/
|
|
138
|
+
projectOntoPlane(plane: Plane, direction?: Vec3): NurbsCurve {
|
|
139
|
+
const proj = (p: Point) => direction ? plane.projectPointAlongDirection(p, direction) : plane.projectPoint(p);
|
|
140
|
+
return new NurbsCurve(
|
|
141
|
+
this.degree,
|
|
142
|
+
this.controlPoints.map(proj),
|
|
143
|
+
this.weights,
|
|
144
|
+
this.knots
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Offset this curve by a distance.
|
|
150
|
+
* Note: NURBS offset is approximated by sampling and offsetting as polyline.
|
|
151
|
+
* @param distance - Offset distance
|
|
152
|
+
* @param normal - Reference normal for determining offset direction
|
|
153
|
+
* @param samples - Number of sample points (default 64)
|
|
154
|
+
* @returns Offset polyline approximation
|
|
155
|
+
*/
|
|
156
|
+
offset(distance: number, normal?: Vec3, samples = 64): Polyline {
|
|
157
|
+
const pts = this.sample(samples);
|
|
158
|
+
const pl = new Polyline(pts);
|
|
159
|
+
return pl.offset(distance, normal);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Decode a NurbsCurve from WASM flat buffer.
|
|
164
|
+
* @param data - Buffer in format [degree, num_cp, xyz..., w..., k...]
|
|
165
|
+
* @returns New NurbsCurve instance
|
|
166
|
+
*/
|
|
167
|
+
static fromData(data: Float64Array | number[]): NurbsCurve {
|
|
168
|
+
const d = data instanceof Float64Array ? data : new Float64Array(data);
|
|
169
|
+
const degree = d[0];
|
|
170
|
+
const n = d[1];
|
|
171
|
+
let idx = 2;
|
|
172
|
+
const pts: Point[] = [];
|
|
173
|
+
for (let i = 0; i < n; i++) {
|
|
174
|
+
pts.push(new Point(d[idx], d[idx + 1], d[idx + 2]));
|
|
175
|
+
idx += 3;
|
|
176
|
+
}
|
|
177
|
+
const weights = Array.from(d.slice(idx, idx + n)); idx += n;
|
|
178
|
+
const numKnots = n + degree + 1;
|
|
179
|
+
const knots = Array.from(d.slice(idx, idx + numKnots));
|
|
180
|
+
return new NurbsCurve(degree, pts, weights, knots);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Create exact degree-1 NURBS from a Line.
|
|
185
|
+
* @param line - Source line
|
|
186
|
+
* @returns Degree-1 NURBS representing the line exactly
|
|
187
|
+
*/
|
|
188
|
+
static fromLine(line: Line): NurbsCurve {
|
|
189
|
+
return new NurbsCurve(1, [line.start, line.end], [1, 1], [0, 0, 1, 1]);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Create exact degree-2 rational NURBS from an Arc.
|
|
194
|
+
* Uses WASM circle_arc for exact conic representation.
|
|
195
|
+
* @param arc - Source arc
|
|
196
|
+
* @returns Degree-2 rational NURBS representing the arc exactly
|
|
197
|
+
*/
|
|
198
|
+
static fromArc(arc: Arc): NurbsCurve {
|
|
199
|
+
ensureInit();
|
|
200
|
+
const buf = wasm.polycurve_to_nurbs(new Float64Array([
|
|
201
|
+
1, // 1 segment
|
|
202
|
+
1, // type = Arc
|
|
203
|
+
arc.center.x, arc.center.y, arc.center.z,
|
|
204
|
+
arc.normal.x, arc.normal.y, arc.normal.z,
|
|
205
|
+
arc.radius, arc.startAngle, arc.endAngle
|
|
206
|
+
]));
|
|
207
|
+
if (buf.length < 2) throw new Error("Failed to convert Arc to NURBS");
|
|
208
|
+
return NurbsCurve.fromData(buf);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get internal WASM data buffer.
|
|
213
|
+
* For advanced use cases requiring direct WASM interop.
|
|
214
|
+
*/
|
|
215
|
+
get data(): Float64Array {
|
|
216
|
+
return this._data;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Encode to WASM format */
|
|
220
|
+
private static encode(
|
|
221
|
+
degree: number,
|
|
222
|
+
controlPoints: Point[],
|
|
223
|
+
weights: number[],
|
|
224
|
+
knots: number[]
|
|
225
|
+
): Float64Array {
|
|
226
|
+
const n = controlPoints.length;
|
|
227
|
+
const data = new Float64Array(2 + n * 3 + weights.length + knots.length);
|
|
228
|
+
data[0] = degree;
|
|
229
|
+
data[1] = n;
|
|
230
|
+
let idx = 2;
|
|
231
|
+
for (const p of controlPoints) {
|
|
232
|
+
data[idx++] = p.x;
|
|
233
|
+
data[idx++] = p.y;
|
|
234
|
+
data[idx++] = p.z;
|
|
235
|
+
}
|
|
236
|
+
for (const w of weights) data[idx++] = w;
|
|
237
|
+
for (const k of knots) data[idx++] = k;
|
|
238
|
+
return data;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { ensureInit } from "./engine.js";
|
|
2
|
+
import { Point } from "./Point.js";
|
|
3
|
+
import { Vec3 } from "./Vec3.js";
|
|
4
|
+
import { Plane } from "./Plane.js";
|
|
5
|
+
import { Mesh } from "./Mesh.js";
|
|
6
|
+
import { Polyline } from "./Polyline.js";
|
|
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/okgeometrycore_bg.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Non-Uniform Rational B-Spline (NURBS) surface backed by WASM.
|
|
18
|
+
* Tensor-product surface defined by a 2D grid of control points.
|
|
19
|
+
*
|
|
20
|
+
* WASM data format: [degree_u, degree_v, num_u, num_v, ...cp(flat)..., ...weights..., ...knots_u..., ...knots_v...]
|
|
21
|
+
*/
|
|
22
|
+
export class NurbsSurface {
|
|
23
|
+
private _data: Float64Array;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a new NURBS surface.
|
|
27
|
+
* @param degreeU - Polynomial degree in U direction
|
|
28
|
+
* @param degreeV - Polynomial degree in V direction
|
|
29
|
+
* @param controlPoints - 2D grid of control points [u][v]
|
|
30
|
+
* @param weights - 2D grid of weights corresponding to control points
|
|
31
|
+
* @param knotsU - Knot vector in U direction
|
|
32
|
+
* @param knotsV - Knot vector in V direction
|
|
33
|
+
*/
|
|
34
|
+
constructor(
|
|
35
|
+
public readonly degreeU: number,
|
|
36
|
+
public readonly degreeV: number,
|
|
37
|
+
public readonly controlPoints: Point[][],
|
|
38
|
+
public readonly weights: number[][],
|
|
39
|
+
public readonly knotsU: number[],
|
|
40
|
+
public readonly knotsV: number[]
|
|
41
|
+
) {
|
|
42
|
+
this._data = NurbsSurface.encode(degreeU, degreeV, controlPoints, weights, knotsU, knotsV);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Rotate this surface around an axis.
|
|
47
|
+
* @param axis - Rotation axis (Vec3 through origin, or Line for arbitrary axis)
|
|
48
|
+
* @param angle - Rotation angle in radians
|
|
49
|
+
* @returns New rotated surface
|
|
50
|
+
*/
|
|
51
|
+
rotate(axis: RotationAxis, angle: number): NurbsSurface {
|
|
52
|
+
return new NurbsSurface(
|
|
53
|
+
this.degreeU,
|
|
54
|
+
this.degreeV,
|
|
55
|
+
this.controlPoints.map(row => row.map(p => p.rotate(axis, angle))),
|
|
56
|
+
this.weights,
|
|
57
|
+
this.knotsU,
|
|
58
|
+
this.knotsV
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Translate this surface by an offset vector.
|
|
64
|
+
* @param offset - Translation vector
|
|
65
|
+
* @returns New translated surface
|
|
66
|
+
*/
|
|
67
|
+
translate(offset: Vec3): NurbsSurface {
|
|
68
|
+
const newCps = this.controlPoints.map(row => row.map(p => p.add(offset)));
|
|
69
|
+
return new NurbsSurface(
|
|
70
|
+
this.degreeU,
|
|
71
|
+
this.degreeV,
|
|
72
|
+
newCps,
|
|
73
|
+
this.weights,
|
|
74
|
+
this.knotsU,
|
|
75
|
+
this.knotsV
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Evaluate a point on the surface at parameters (u, v).
|
|
81
|
+
* @param u - Parameter in U direction [0, 1]
|
|
82
|
+
* @param v - Parameter in V direction [0, 1]
|
|
83
|
+
* @returns Point on surface at (u, v)
|
|
84
|
+
*/
|
|
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
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Tessellate this surface into a triangle mesh.
|
|
93
|
+
* @param uSegs - Number of segments in U direction
|
|
94
|
+
* @param vSegs - Number of segments in V direction
|
|
95
|
+
* @returns Mesh approximating the surface
|
|
96
|
+
*/
|
|
97
|
+
tessellate(uSegs: number, vSegs: number): Mesh {
|
|
98
|
+
ensureInit();
|
|
99
|
+
const full = new Float64Array(this._data.length + 2);
|
|
100
|
+
full.set(this._data);
|
|
101
|
+
full[this._data.length] = uSegs;
|
|
102
|
+
full[this._data.length + 1] = vSegs;
|
|
103
|
+
return Mesh.fromBuffer(wasm.tessellate_nurbs_surface(full));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Find intersection curves with a plane.
|
|
108
|
+
* @param plane - Cutting plane
|
|
109
|
+
* @param tess - Tessellation resolution for intersection detection (default 20)
|
|
110
|
+
* @returns Array of polylines representing intersection curves
|
|
111
|
+
*/
|
|
112
|
+
intersectPlane(plane: Plane, tess = 20): Polyline[] {
|
|
113
|
+
ensureInit();
|
|
114
|
+
const buf = wasm.nurbs_surface_plane_intersect(
|
|
115
|
+
this._data,
|
|
116
|
+
plane.normal.x, plane.normal.y, plane.normal.z,
|
|
117
|
+
plane.d,
|
|
118
|
+
tess
|
|
119
|
+
);
|
|
120
|
+
return parsePolylineBuf(buf).map(pts => new Polyline(pts));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Find intersection curves with another NURBS surface.
|
|
125
|
+
* @param other - Other surface to intersect with
|
|
126
|
+
* @param tess - Tessellation resolution for intersection detection (default 20)
|
|
127
|
+
* @returns Array of polylines representing intersection curves
|
|
128
|
+
*/
|
|
129
|
+
intersectSurface(other: NurbsSurface, tess = 20): Polyline[] {
|
|
130
|
+
ensureInit();
|
|
131
|
+
const buf = wasm.nurbs_surface_surface_intersect(this._data, other._data, tess);
|
|
132
|
+
return parsePolylineBuf(buf).map(pts => new Polyline(pts));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Convert any supported curve type to a NurbsCurve.
|
|
137
|
+
* @param c - Curve to convert
|
|
138
|
+
* @returns NurbsCurve representation
|
|
139
|
+
*/
|
|
140
|
+
private static toNurbs(c: LoftableCurve): NurbsCurve {
|
|
141
|
+
if (c instanceof NurbsCurve) return c;
|
|
142
|
+
if (c instanceof PolyCurve) return c.toNurbs();
|
|
143
|
+
if (c instanceof Polygon || c instanceof Polyline) return PolyCurve.byPoints((c as Polyline).points).toNurbs();
|
|
144
|
+
if (c instanceof Arc) return NurbsCurve.fromArc(c);
|
|
145
|
+
if (c instanceof Line) return NurbsCurve.fromLine(c);
|
|
146
|
+
throw new Error(`Unsupported curve type for loft`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Loft a NURBS surface through multiple curves.
|
|
151
|
+
* Accepts NurbsCurve, PolyCurve, Polyline, Polygon, Arc, or Line.
|
|
152
|
+
* Automatically converts to NURBS and unifies knot vectors.
|
|
153
|
+
*
|
|
154
|
+
* @param curves - Array of curves to loft through (minimum 2)
|
|
155
|
+
* @param degreeV - Polynomial degree in the loft direction (default 3)
|
|
156
|
+
* @returns New NurbsSurface passing through all input curves
|
|
157
|
+
*/
|
|
158
|
+
static loftCurves(curves: LoftableCurve[], degreeV = 3): NurbsSurface {
|
|
159
|
+
ensureInit();
|
|
160
|
+
|
|
161
|
+
// Step 0: Convert all curves to NurbsCurve
|
|
162
|
+
const nurbsCurves = curves.map(c => NurbsSurface.toNurbs(c));
|
|
163
|
+
|
|
164
|
+
// Step 1: Make curves compatible (unified knot vectors, same CP count)
|
|
165
|
+
let loftCurves = nurbsCurves;
|
|
166
|
+
if (nurbsCurves.length >= 2) {
|
|
167
|
+
const cpCounts = nurbsCurves.map(c => c.controlPoints.length);
|
|
168
|
+
const allSame = cpCounts.every(n => n === cpCounts[0]);
|
|
169
|
+
if (!allSame) {
|
|
170
|
+
const compatParts: number[] = [nurbsCurves.length];
|
|
171
|
+
for (const c of nurbsCurves) {
|
|
172
|
+
compatParts.push(c.degree, c.controlPoints.length);
|
|
173
|
+
for (const p of c.controlPoints) { compatParts.push(p.x, p.y, p.z); }
|
|
174
|
+
for (const w of c.weights) { compatParts.push(w); }
|
|
175
|
+
for (const k of c.knots) { compatParts.push(k); }
|
|
176
|
+
}
|
|
177
|
+
const compatBuf = wasm.make_nurbs_curves_compatible(new Float64Array(compatParts));
|
|
178
|
+
if (compatBuf.length > 0) {
|
|
179
|
+
const count = compatBuf[0];
|
|
180
|
+
const decoded: NurbsCurve[] = [];
|
|
181
|
+
let off = 1;
|
|
182
|
+
for (let i = 0; i < count; i++) {
|
|
183
|
+
const deg = compatBuf[off];
|
|
184
|
+
const n = compatBuf[off + 1];
|
|
185
|
+
const curveLen = 2 + n * 3 + n + (n + deg + 1);
|
|
186
|
+
decoded.push(NurbsCurve.fromData(compatBuf.slice(off, off + curveLen)));
|
|
187
|
+
off += curveLen;
|
|
188
|
+
}
|
|
189
|
+
loftCurves = decoded;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Step 2: Encode for loft_nurbs_surface
|
|
195
|
+
const parts: number[] = [loftCurves.length, degreeV];
|
|
196
|
+
for (const c of loftCurves) {
|
|
197
|
+
const pts = c.controlPoints;
|
|
198
|
+
parts.push(c.degree, pts.length);
|
|
199
|
+
for (const p of pts) { parts.push(p.x, p.y, p.z); }
|
|
200
|
+
for (const w of c.weights) { parts.push(w); }
|
|
201
|
+
for (const k of c.knots) { parts.push(k); }
|
|
202
|
+
}
|
|
203
|
+
const buf = wasm.loft_nurbs_surface(new Float64Array(parts));
|
|
204
|
+
if (buf.length < 4) throw new Error("NurbsSurface loft failed");
|
|
205
|
+
const degU = buf[0], degV2 = buf[1], numU = buf[2], numV = buf[3];
|
|
206
|
+
const totalPts = numU * numV;
|
|
207
|
+
const expectedLen = 4 + totalPts * 3 + totalPts + (degU + numU + 1) + (degV2 + numV + 1);
|
|
208
|
+
if (buf.length < expectedLen) throw new Error("NurbsSurface loft: buffer too short");
|
|
209
|
+
let idx = 4;
|
|
210
|
+
const cp: Point[][] = [];
|
|
211
|
+
for (let u = 0; u < numU; u++) {
|
|
212
|
+
const row: Point[] = [];
|
|
213
|
+
for (let v = 0; v < numV; v++) {
|
|
214
|
+
row.push(new Point(buf[idx], buf[idx + 1], buf[idx + 2]));
|
|
215
|
+
idx += 3;
|
|
216
|
+
}
|
|
217
|
+
cp.push(row);
|
|
218
|
+
}
|
|
219
|
+
const weights: number[][] = [];
|
|
220
|
+
for (let u = 0; u < numU; u++) {
|
|
221
|
+
const row: number[] = [];
|
|
222
|
+
for (let v = 0; v < numV; v++) { row.push(buf[idx++]); }
|
|
223
|
+
weights.push(row);
|
|
224
|
+
}
|
|
225
|
+
const knotsULen = degU + numU + 1;
|
|
226
|
+
const knotsVLen = degV2 + numV + 1;
|
|
227
|
+
const knotsU = Array.from(buf.slice(idx, idx + knotsULen)); idx += knotsULen;
|
|
228
|
+
const knotsV = Array.from(buf.slice(idx, idx + knotsVLen));
|
|
229
|
+
return new NurbsSurface(degU, degV2, cp, weights, knotsU, knotsV);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Encode to WASM format */
|
|
233
|
+
private static encode(
|
|
234
|
+
degU: number,
|
|
235
|
+
degV: number,
|
|
236
|
+
controlPoints: Point[][],
|
|
237
|
+
weights: number[][],
|
|
238
|
+
knotsU: number[],
|
|
239
|
+
knotsV: number[]
|
|
240
|
+
): Float64Array {
|
|
241
|
+
const numU = controlPoints.length;
|
|
242
|
+
const numV = controlPoints[0]?.length ?? 0;
|
|
243
|
+
const totalPts = numU * numV;
|
|
244
|
+
const data = new Float64Array(4 + totalPts * 3 + totalPts + knotsU.length + knotsV.length);
|
|
245
|
+
data[0] = degU;
|
|
246
|
+
data[1] = degV;
|
|
247
|
+
data[2] = numU;
|
|
248
|
+
data[3] = numV;
|
|
249
|
+
let idx = 4;
|
|
250
|
+
for (let u = 0; u < numU; u++) {
|
|
251
|
+
for (let v = 0; v < numV; v++) {
|
|
252
|
+
const p = controlPoints[u][v];
|
|
253
|
+
data[idx++] = p.x;
|
|
254
|
+
data[idx++] = p.y;
|
|
255
|
+
data[idx++] = p.z;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
for (let u = 0; u < numU; u++) {
|
|
259
|
+
for (let v = 0; v < numV; v++) {
|
|
260
|
+
data[idx++] = weights[u][v];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
for (const k of knotsU) data[idx++] = k;
|
|
264
|
+
for (const k of knotsV) data[idx++] = k;
|
|
265
|
+
return data;
|
|
266
|
+
}
|
|
267
|
+
}
|
package/src/Plane.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Point } from "./Point.js";
|
|
2
|
+
import { Vec3 } from "./Vec3.js";
|
|
3
|
+
|
|
4
|
+
export class Plane {
|
|
5
|
+
public readonly origin: Point;
|
|
6
|
+
public readonly normal: Vec3;
|
|
7
|
+
/** Optional X-axis direction in the plane (for consistent orientation) */
|
|
8
|
+
public readonly xAxis?: Vec3;
|
|
9
|
+
|
|
10
|
+
constructor(origin: Point, normal: Vec3, xAxis?: Vec3) {
|
|
11
|
+
this.origin = origin;
|
|
12
|
+
this.normal = normal.normalize();
|
|
13
|
+
// Store xAxis if provided (should be perpendicular to normal)
|
|
14
|
+
if (xAxis) {
|
|
15
|
+
// Project xAxis onto plane and normalize
|
|
16
|
+
const dot = xAxis.dot(this.normal);
|
|
17
|
+
const projected = new Vec3(
|
|
18
|
+
xAxis.x - this.normal.x * dot,
|
|
19
|
+
xAxis.y - this.normal.y * dot,
|
|
20
|
+
xAxis.z - this.normal.z * dot
|
|
21
|
+
);
|
|
22
|
+
const len = Math.sqrt(projected.x ** 2 + projected.y ** 2 + projected.z ** 2);
|
|
23
|
+
if (len > 1e-10) {
|
|
24
|
+
this.xAxis = new Vec3(projected.x / len, projected.y / len, projected.z / len);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Get the X-axis direction in the plane.
|
|
30
|
+
* If not explicitly set, computes one using a heuristic. */
|
|
31
|
+
getXAxis(): Vec3 {
|
|
32
|
+
if (this.xAxis) return this.xAxis;
|
|
33
|
+
// Heuristic: cross normal with world X or Y
|
|
34
|
+
const n = this.normal;
|
|
35
|
+
let ref: Vec3;
|
|
36
|
+
if (Math.abs(n.x) > 0.9) {
|
|
37
|
+
ref = Vec3.Y;
|
|
38
|
+
} else {
|
|
39
|
+
ref = Vec3.X;
|
|
40
|
+
}
|
|
41
|
+
const cross = new Vec3(
|
|
42
|
+
n.y * ref.z - n.z * ref.y,
|
|
43
|
+
n.z * ref.x - n.x * ref.z,
|
|
44
|
+
n.x * ref.y - n.y * ref.x
|
|
45
|
+
);
|
|
46
|
+
const len = Math.sqrt(cross.x ** 2 + cross.y ** 2 + cross.z ** 2);
|
|
47
|
+
return new Vec3(cross.x / len, cross.y / len, cross.z / len);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Signed distance from point to plane (positive = same side as normal) */
|
|
51
|
+
distanceTo(point: Point): number {
|
|
52
|
+
return (
|
|
53
|
+
this.normal.x * (point.x - this.origin.x) +
|
|
54
|
+
this.normal.y * (point.y - this.origin.y) +
|
|
55
|
+
this.normal.z * (point.z - this.origin.z)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Project a point onto this plane (orthogonal to plane normal) */
|
|
60
|
+
projectPoint(point: Point): Point {
|
|
61
|
+
const d = this.distanceTo(point);
|
|
62
|
+
return new Point(
|
|
63
|
+
point.x - this.normal.x * d,
|
|
64
|
+
point.y - this.normal.y * d,
|
|
65
|
+
point.z - this.normal.z * d
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Project a point onto this plane along a given direction (ray-plane intersection).
|
|
70
|
+
* Falls back to orthogonal projection if direction is parallel to the plane. */
|
|
71
|
+
projectPointAlongDirection(point: Point, direction: Vec3): Point {
|
|
72
|
+
const denom = this.normal.dot(direction);
|
|
73
|
+
if (Math.abs(denom) < 1e-15) {
|
|
74
|
+
// Direction is parallel to the plane — fall back to orthogonal
|
|
75
|
+
return this.projectPoint(point);
|
|
76
|
+
}
|
|
77
|
+
const t = -(
|
|
78
|
+
this.normal.x * (point.x - this.origin.x) +
|
|
79
|
+
this.normal.y * (point.y - this.origin.y) +
|
|
80
|
+
this.normal.z * (point.z - this.origin.z)
|
|
81
|
+
) / denom;
|
|
82
|
+
return new Point(
|
|
83
|
+
point.x + direction.x * t,
|
|
84
|
+
point.y + direction.y * t,
|
|
85
|
+
point.z + direction.z * t
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Plane equation constant d where n·p = d */
|
|
90
|
+
get d(): number {
|
|
91
|
+
return this.normal.dot(this.origin.toVec3());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Bisector plane of two planes. Normal bisects the dihedral angle.
|
|
95
|
+
* Origin is taken from planeA. */
|
|
96
|
+
static bisector(a: Plane, b: Plane): Plane {
|
|
97
|
+
const dot = a.normal.dot(b.normal);
|
|
98
|
+
const nb = dot < 0 ? b.normal.negate() : b.normal;
|
|
99
|
+
const bisectNormal = a.normal.add(nb).normalize();
|
|
100
|
+
return new Plane(a.origin, bisectNormal);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Compute the intersection point of three planes.
|
|
105
|
+
* Returns null if the system is degenerate (planes parallel or co-planar).
|
|
106
|
+
*
|
|
107
|
+
* Each plane is n·x = d. The solution uses Cramer's rule:
|
|
108
|
+
* x = (d1(n2×n3) + d2(n3×n1) + d3(n1×n2)) / (n1·(n2×n3))
|
|
109
|
+
*/
|
|
110
|
+
static intersect3(p1: Plane, p2: Plane, p3: Plane): Point | null {
|
|
111
|
+
const n2xn3 = p2.normal.cross(p3.normal);
|
|
112
|
+
const denom = p1.normal.dot(n2xn3);
|
|
113
|
+
|
|
114
|
+
if (Math.abs(denom) < 1e-10) return null;
|
|
115
|
+
|
|
116
|
+
const n3xn1 = p3.normal.cross(p1.normal);
|
|
117
|
+
const n1xn2 = p1.normal.cross(p2.normal);
|
|
118
|
+
|
|
119
|
+
const d1 = p1.d, d2 = p2.d, d3 = p3.d;
|
|
120
|
+
const inv = 1.0 / denom;
|
|
121
|
+
|
|
122
|
+
return new Point(
|
|
123
|
+
(d1 * n2xn3.x + d2 * n3xn1.x + d3 * n1xn2.x) * inv,
|
|
124
|
+
(d1 * n2xn3.y + d2 * n3xn1.y + d3 * n1xn2.y) * inv,
|
|
125
|
+
(d1 * n2xn3.z + d2 * n3xn1.z + d3 * n1xn2.z) * inv,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static readonly XY = new Plane(Point.ORIGIN, Vec3.Z);
|
|
130
|
+
static readonly XZ = new Plane(Point.ORIGIN, Vec3.Y);
|
|
131
|
+
static readonly YZ = new Plane(Point.ORIGIN, Vec3.X);
|
|
132
|
+
}
|