okgeometry-api 1.22.0 → 1.24.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/package.json CHANGED
@@ -1,60 +1,60 @@
1
- {
2
- "name": "okgeometry-api",
3
- "version": "1.22.0",
4
- "description": "Geometry engine API for AEC applications - NURBS, meshes, booleans, intersections. Powered by Rust/WASM.",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
12
- }
13
- },
14
- "files": [
15
- "dist/",
16
- "src/"
17
- ],
18
- "scripts": {
19
- "build:wasm": "node -e \"require('fs').rmSync('wasm',{recursive:true,force:true})\" && cd .. && wasm-pack build --target web --out-dir okgeometry-api/wasm",
20
- "sync:wasm-bindings": "node scripts/sync-wasm-bindings.mjs",
21
- "inline-wasm": "tsx scripts/inline-wasm.ts",
22
- "build": "npm run build:wasm && npm run sync:wasm-bindings && npm run inline-wasm && tsc",
23
- "bench:boolean-heavy": "npm run build && tsx scripts/bench-boolean-heavy.ts",
24
- "bench:boolean-ab": "npm run build && tsx scripts/bench-boolean-ab.ts",
25
- "bench:boolean-batch": "npm run build && tsx scripts/bench-boolean-batch.ts",
26
- "bench:boolean-matrix": "tsx scripts/bench-boolean-matrix.ts",
27
- "smoke:brep-boolean": "tsx scripts/smoke-brep-boolean.ts",
28
- "prepublishOnly": "npm run build",
29
- "smoke:brep-parametric": "tsx scripts/smoke-brep-parametric.ts",
30
- "smoke:brep-from-mesh": "tsx scripts/smoke-brep-from-mesh.ts",
31
- "smoke:brep-faces": "tsx scripts/smoke-brep-faces.ts",
32
- "smoke:curve-intersections": "tsx scripts/smoke-curve-intersections.ts",
33
- "smoke:brep-intersections": "tsx scripts/smoke-brep-intersections.ts",
34
- "smoke:brep-sheet-boolean": "tsx scripts/smoke-brep-sheet-boolean.ts",
35
- "smoke:geometry-boolean": "tsx scripts/smoke-geometry-boolean.ts",
36
- "smoke:curve-region": "tsx scripts/smoke-curve-region.ts",
37
- "smoke:sculpt": "tsx scripts/smoke-sculpt.ts",
38
- "smoke:offset-chamfer": "tsx scripts/smoke-offset-chamfer.ts"
39
- },
40
- "keywords": [
41
- "geometry",
42
- "wasm",
43
- "nurbs",
44
- "mesh",
45
- "boolean",
46
- "cad",
47
- "aec",
48
- "3d"
49
- ],
50
- "author": "Mostafa El Ayoubi",
51
- "license": "Proprietary License",
52
- "repository": {
53
- "type": "git",
54
- "url": "https://www.orkestra.online"
55
- },
56
- "devDependencies": {
57
- "tsx": "^4.7.0",
58
- "typescript": "^5.4.0"
59
- }
60
- }
1
+ {
2
+ "name": "okgeometry-api",
3
+ "version": "1.24.0",
4
+ "description": "Geometry engine API for AEC applications - NURBS, meshes, booleans, intersections. Powered by Rust/WASM.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist/",
16
+ "src/"
17
+ ],
18
+ "scripts": {
19
+ "build:wasm": "node -e \"require('fs').rmSync('wasm',{recursive:true,force:true})\" && cd .. && wasm-pack build --target web --out-dir okgeometry-api/wasm",
20
+ "sync:wasm-bindings": "node scripts/sync-wasm-bindings.mjs",
21
+ "inline-wasm": "tsx scripts/inline-wasm.ts",
22
+ "build": "npm run build:wasm && npm run sync:wasm-bindings && npm run inline-wasm && tsc",
23
+ "bench:boolean-heavy": "npm run build && tsx scripts/bench-boolean-heavy.ts",
24
+ "bench:boolean-ab": "npm run build && tsx scripts/bench-boolean-ab.ts",
25
+ "bench:boolean-batch": "npm run build && tsx scripts/bench-boolean-batch.ts",
26
+ "bench:boolean-matrix": "tsx scripts/bench-boolean-matrix.ts",
27
+ "smoke:brep-boolean": "tsx scripts/smoke-brep-boolean.ts",
28
+ "prepublishOnly": "npm run build",
29
+ "smoke:brep-parametric": "tsx scripts/smoke-brep-parametric.ts",
30
+ "smoke:brep-from-mesh": "tsx scripts/smoke-brep-from-mesh.ts",
31
+ "smoke:brep-faces": "tsx scripts/smoke-brep-faces.ts",
32
+ "smoke:curve-intersections": "tsx scripts/smoke-curve-intersections.ts",
33
+ "smoke:brep-intersections": "tsx scripts/smoke-brep-intersections.ts",
34
+ "smoke:brep-sheet-boolean": "tsx scripts/smoke-brep-sheet-boolean.ts",
35
+ "smoke:geometry-boolean": "tsx scripts/smoke-geometry-boolean.ts",
36
+ "smoke:curve-region": "tsx scripts/smoke-curve-region.ts",
37
+ "smoke:sculpt": "tsx scripts/smoke-sculpt.ts",
38
+ "smoke:offset-chamfer": "tsx scripts/smoke-offset-chamfer.ts"
39
+ },
40
+ "keywords": [
41
+ "geometry",
42
+ "wasm",
43
+ "nurbs",
44
+ "mesh",
45
+ "boolean",
46
+ "cad",
47
+ "aec",
48
+ "3d"
49
+ ],
50
+ "author": "Mostafa El Ayoubi",
51
+ "license": "Proprietary License",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "https://www.orkestra.online"
55
+ },
56
+ "devDependencies": {
57
+ "tsx": "^4.7.0",
58
+ "typescript": "^5.4.0"
59
+ }
60
+ }
package/src/Brep.ts CHANGED
@@ -485,6 +485,30 @@ export class Brep {
485
485
  );
486
486
  }
487
487
 
488
+ /**
489
+ * Wireframe of the whole BREP in ONE WASM crossing: per face, `uCount`
490
+ * curves running along u plus `vCount` along v, at evenly spaced INTERIOR
491
+ * normalized parameters (i+1)/(count+1), each clipped to that face's
492
+ * trimmed region. The body JSON is parsed once for the entire sweep, so
493
+ * this scales to large face counts where per-face
494
+ * {@link faceIsoCurvesTrimmedNormalized} calls would re-parse the body
495
+ * repeatedly. Boundary parameters are excluded (they coincide with the
496
+ * BREP's edges); faces that fail to evaluate are skipped atomically.
497
+ */
498
+ wireframeCurves(uCount: number, vCount: number): NurbsCurve[] {
499
+ ensureInit();
500
+ const buf = wasm.brep_wireframe_curves(this.json, uCount, vCount);
501
+ // An empty buffer (not even a zero count header) means the call failed
502
+ // outright, e.g. unparseable JSON — surface the reason instead of
503
+ // pretending the wireframe is empty.
504
+ if (buf.length < 1) {
505
+ throw new Error(
506
+ `Brep.wireframeCurves() failed: ${wasm.geometry_last_error() || "unknown error"}`,
507
+ );
508
+ }
509
+ return Brep.parseCurveList(buf);
510
+ }
511
+
488
512
  /** Parse [count, (bufLen, curveData…)·count] into NurbsCurves. */
489
513
  private static parseCurveList(buf: Float64Array): NurbsCurve[] {
490
514
  if (buf.length < 1) return [];
package/src/Line.ts CHANGED
@@ -1,12 +1,12 @@
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 { PolyCurve } from "./PolyCurve.js";
6
- import { Polyline } from "./Polyline.js";
7
- import type { Plane } from "./Plane.js";
8
- import type { CurveOffsetOptions, RotationAxis } from "./types.js";
9
- import * as wasm from "./wasm-bindings.js";
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 { PolyCurve } from "./PolyCurve.js";
6
+ import { Polyline } from "./Polyline.js";
7
+ import type { Plane } from "./Plane.js";
8
+ import type { CurveOffsetOptions, RotationAxis } from "./types.js";
9
+ import * as wasm from "./wasm-bindings.js";
10
10
 
11
11
  /**
12
12
  * Immutable line segment defined by start and end points.
@@ -86,6 +86,15 @@ export class Line {
86
86
  return new Line(this.start.add(offset), this.end.add(offset));
87
87
  }
88
88
 
89
+ /**
90
+ * Apply a row-major 4x4 matrix to both endpoints.
91
+ * @param matrix - Row-major 4x4 matrix (16 entries)
92
+ * @returns New transformed line
93
+ */
94
+ applyMatrix(matrix: number[] | Float64Array): Line {
95
+ return new Line(this.start.applyMatrix(matrix), this.end.applyMatrix(matrix));
96
+ }
97
+
89
98
  /**
90
99
  * Rotate this line around an axis.
91
100
  * @param axis - Rotation axis (Vec3 through origin, or Line for arbitrary axis)
@@ -102,40 +111,40 @@ export class Line {
102
111
  * @param normal - Reference normal for determining offset direction (default Z axis)
103
112
  * @returns New offset line
104
113
  */
105
- offset(distance: number, normal: Vec3 = Vec3.Z): Line {
106
- ensureInit();
107
- const r = wasm.line_offset(
108
- this.start.x, this.start.y, this.start.z,
109
- this.end.x, this.end.y, this.end.z,
114
+ offset(distance: number, normal: Vec3 = Vec3.Z): Line {
115
+ ensureInit();
116
+ const r = wasm.line_offset(
117
+ this.start.x, this.start.y, this.start.z,
118
+ this.end.x, this.end.y, this.end.z,
110
119
  distance,
111
120
  normal.x, normal.y, normal.z,
112
121
  );
113
122
  return new Line(
114
123
  new Point(r[0], r[1], r[2]),
115
- new Point(r[3], r[4], r[5]),
116
- );
117
- }
118
-
119
- /**
120
- * Thicken this line into a closed PolyCurve suitable for extrusion.
121
- * When `bothSides` is false, the source line forms one side of the result.
122
- * When true, the thickness is added on each side of the line.
123
- *
124
- * If `normal` is omitted, the kernel will infer a stable fallback plane.
125
- */
126
- thicken(
127
- distance: number,
128
- bothSides = false,
129
- normal?: Vec3,
130
- options: CurveOffsetOptions = {},
131
- ): PolyCurve {
132
- return new Polyline([this.start, this.end]).thicken(distance, bothSides, normal, options);
133
- }
134
-
135
- /**
136
- * Project this line onto a plane.
137
- * @param plane - Target plane
138
- * @param direction - Optional projection direction (default: perpendicular to plane)
124
+ new Point(r[3], r[4], r[5]),
125
+ );
126
+ }
127
+
128
+ /**
129
+ * Thicken this line into a closed PolyCurve suitable for extrusion.
130
+ * When `bothSides` is false, the source line forms one side of the result.
131
+ * When true, the thickness is added on each side of the line.
132
+ *
133
+ * If `normal` is omitted, the kernel will infer a stable fallback plane.
134
+ */
135
+ thicken(
136
+ distance: number,
137
+ bothSides = false,
138
+ normal?: Vec3,
139
+ options: CurveOffsetOptions = {},
140
+ ): PolyCurve {
141
+ return new Polyline([this.start, this.end]).thicken(distance, bothSides, normal, options);
142
+ }
143
+
144
+ /**
145
+ * Project this line onto a plane.
146
+ * @param plane - Target plane
147
+ * @param direction - Optional projection direction (default: perpendicular to plane)
139
148
  * @returns New projected line
140
149
  */
141
150
  projectOntoPlane(plane: Plane, direction?: Vec3): Line {
package/src/NurbsCurve.ts CHANGED
@@ -363,6 +363,22 @@ export class NurbsCurve {
363
363
  );
364
364
  }
365
365
 
366
+ /**
367
+ * Apply a row-major 4x4 AFFINE matrix to this curve. NURBS are invariant
368
+ * under affine maps of their control points, so rigid transforms, scaling
369
+ * (uniform or not) and shear are EXACT — weights and knots are unchanged.
370
+ * @param matrix - Row-major 4x4 matrix (16 entries)
371
+ * @returns New transformed curve
372
+ */
373
+ applyMatrix(matrix: number[] | Float64Array): NurbsCurve {
374
+ return new NurbsCurve(
375
+ this.degree,
376
+ this.controlPoints.map(p => p.applyMatrix(matrix)),
377
+ this.weights,
378
+ this.knots
379
+ );
380
+ }
381
+
366
382
  /**
367
383
  * Rotate this curve around an axis.
368
384
  * @param axis - Rotation axis (Vec3 through origin, or Line for arbitrary axis)
@@ -81,6 +81,25 @@ export class NurbsSurface implements Surface {
81
81
  );
82
82
  }
83
83
 
84
+ /**
85
+ * Apply a row-major 4x4 AFFINE matrix to this surface. NURBS are invariant
86
+ * under affine maps of their control net, so rigid transforms, scaling
87
+ * (uniform or not) and shear are EXACT — weights and knots are unchanged.
88
+ * @param matrix - Row-major 4x4 matrix (16 entries)
89
+ * @returns New transformed surface
90
+ */
91
+ applyMatrix(matrix: number[] | Float64Array): NurbsSurface {
92
+ const newCps = this.controlPoints.map(row => row.map(p => p.applyMatrix(matrix)));
93
+ return new NurbsSurface(
94
+ this.degreeU,
95
+ this.degreeV,
96
+ newCps,
97
+ this.weights,
98
+ this.knotsU,
99
+ this.knotsV
100
+ );
101
+ }
102
+
84
103
  /**
85
104
  * Evaluate a point on the surface at parameters (u, v).
86
105
  * @param u - Parameter in U direction [0, 1]
package/src/Point.ts CHANGED
@@ -1,145 +1,161 @@
1
- import { Vec3 } from "./Vec3.js";
2
- import type { Plane } from "./Plane.js";
3
- import type { RotationAxis } from "./types.js";
4
-
5
- /**
6
- * Immutable 3D point for representing positions in space.
7
- * All operations return new Point instances.
8
- */
9
- export class Point {
10
- constructor(
11
- public readonly x: number,
12
- public readonly y: number,
13
- public readonly z: number
14
- ) {}
15
-
16
- /**
17
- * Compute the distance to another point.
18
- * @param other - Target point
19
- * @returns Euclidean distance between points
20
- */
21
- distanceTo(other: Point): number {
22
- const dx = this.x - other.x;
23
- const dy = this.y - other.y;
24
- const dz = this.z - other.z;
25
- return Math.sqrt(dx * dx + dy * dy + dz * dz);
26
- }
27
-
28
- /**
29
- * Convert this point to a vector (from origin).
30
- * @returns Vec3 with same coordinates
31
- */
32
- toVec3(): Vec3 {
33
- return new Vec3(this.x, this.y, this.z);
34
- }
35
-
36
- /**
37
- * Add a vector to this point.
38
- * @param v - Vector to add
39
- * @returns New point offset by vector
40
- */
41
- add(v: Vec3): Point {
42
- return new Point(this.x + v.x, this.y + v.y, this.z + v.z);
43
- }
44
-
45
- /**
46
- * Translate this point by an offset vector.
47
- * Alias for add().
48
- * @param offset - Translation vector
49
- * @returns New translated point
50
- */
51
- translate(offset: Vec3): Point {
52
- return this.add(offset);
53
- }
54
-
55
- /**
56
- * Rotate around an axis by angle in radians using Rodrigues' formula.
57
- * @param axis - Vec3 (rotation around origin) or Line (rotation around arbitrary axis)
58
- * @param angle - Rotation angle in radians
59
- * @returns Rotated point
60
- */
61
- rotate(axis: RotationAxis, angle: number): Point {
62
- if ('start' in axis && 'end' in axis) {
63
- const dir = new Vec3(axis.end.x - axis.start.x, axis.end.y - axis.start.y, axis.end.z - axis.start.z);
64
- const shifted = new Point(this.x - axis.start.x, this.y - axis.start.y, this.z - axis.start.z);
65
- const rotated = shifted.rotate(dir, angle);
66
- return new Point(rotated.x + axis.start.x, rotated.y + axis.start.y, rotated.z + axis.start.z);
67
- }
68
- const len = Math.sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z);
69
- if (len < 1e-15) return this.clone();
70
- const k = { x: axis.x / len, y: axis.y / len, z: axis.z / len };
71
- const cos = Math.cos(angle);
72
- const sin = Math.sin(angle);
73
- const dot = k.x * this.x + k.y * this.y + k.z * this.z;
74
- const cx = k.y * this.z - k.z * this.y;
75
- const cy = k.z * this.x - k.x * this.z;
76
- const cz = k.x * this.y - k.y * this.x;
77
- return new Point(
78
- this.x * cos + cx * sin + k.x * dot * (1 - cos),
79
- this.y * cos + cy * sin + k.y * dot * (1 - cos),
80
- this.z * cos + cz * sin + k.z * dot * (1 - cos),
81
- );
82
- }
83
-
84
- /**
85
- * Project this point onto a plane.
86
- * @param plane - Target plane
87
- * @param direction - Optional projection direction (default: perpendicular to plane)
88
- * @returns Projected point on plane
89
- */
90
- projectOntoPlane(plane: Plane, direction?: Vec3): Point {
91
- return direction ? plane.projectPointAlongDirection(this, direction) : plane.projectPoint(this);
92
- }
93
-
94
- /**
95
- * Get the vector from another point to this point.
96
- * @param other - Origin point
97
- * @returns Vector from other to this
98
- */
99
- sub(other: Point): Vec3 {
100
- return new Vec3(this.x - other.x, this.y - other.y, this.z - other.z);
101
- }
102
-
103
- /**
104
- * Create a copy of this point.
105
- * @returns New point with same coordinates
106
- */
107
- clone(): Point {
108
- return new Point(this.x, this.y, this.z);
109
- }
110
-
111
- /**
112
- * Check equality with another point within tolerance.
113
- * @param other - Point to compare
114
- * @param eps - Tolerance (default 1e-10)
115
- * @returns True if points are approximately equal
116
- */
117
- equals(other: Point, eps = 1e-10): boolean {
118
- return (
119
- Math.abs(this.x - other.x) < eps &&
120
- Math.abs(this.y - other.y) < eps &&
121
- Math.abs(this.z - other.z) < eps
122
- );
123
- }
124
-
125
- /**
126
- * Convert to array format.
127
- * @returns Tuple [x, y, z]
128
- */
129
- toArray(): [number, number, number] {
130
- return [this.x, this.y, this.z];
131
- }
132
-
133
- /**
134
- * Create a Point from a plain object or JSON.
135
- * @param json - Object with x, y, z properties
136
- * @returns New Point instance
137
- */
138
- static fromJSON(json: any): Point {
139
- if (json instanceof Point) return json;
140
- return new Point(json.x ?? 0, json.y ?? 0, json.z ?? 0);
141
- }
142
-
143
- /** Origin point (0, 0, 0) */
144
- static readonly ORIGIN = new Point(0, 0, 0);
145
- }
1
+ import { Vec3 } from "./Vec3.js";
2
+ import type { Plane } from "./Plane.js";
3
+ import type { RotationAxis } from "./types.js";
4
+
5
+ /**
6
+ * Immutable 3D point for representing positions in space.
7
+ * All operations return new Point instances.
8
+ */
9
+ export class Point {
10
+ constructor(
11
+ public readonly x: number,
12
+ public readonly y: number,
13
+ public readonly z: number
14
+ ) {}
15
+
16
+ /**
17
+ * Compute the distance to another point.
18
+ * @param other - Target point
19
+ * @returns Euclidean distance between points
20
+ */
21
+ distanceTo(other: Point): number {
22
+ const dx = this.x - other.x;
23
+ const dy = this.y - other.y;
24
+ const dz = this.z - other.z;
25
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
26
+ }
27
+
28
+ /**
29
+ * Convert this point to a vector (from origin).
30
+ * @returns Vec3 with same coordinates
31
+ */
32
+ toVec3(): Vec3 {
33
+ return new Vec3(this.x, this.y, this.z);
34
+ }
35
+
36
+ /**
37
+ * Add a vector to this point.
38
+ * @param v - Vector to add
39
+ * @returns New point offset by vector
40
+ */
41
+ add(v: Vec3): Point {
42
+ return new Point(this.x + v.x, this.y + v.y, this.z + v.z);
43
+ }
44
+
45
+ /**
46
+ * Translate this point by an offset vector.
47
+ * Alias for add().
48
+ * @param offset - Translation vector
49
+ * @returns New translated point
50
+ */
51
+ translate(offset: Vec3): Point {
52
+ return this.add(offset);
53
+ }
54
+
55
+ /**
56
+ * Apply a row-major 4x4 matrix to this point (same convention as
57
+ * Mesh.applyMatrix / Brep.applyMatrix).
58
+ * @param matrix - Row-major 4x4 matrix (16 entries)
59
+ * @returns New transformed point
60
+ */
61
+ applyMatrix(matrix: number[] | Float64Array): Point {
62
+ if (matrix.length < 16) throw new Error("Point.applyMatrix: need 16 entries");
63
+ const m = matrix;
64
+ return new Point(
65
+ m[0] * this.x + m[1] * this.y + m[2] * this.z + m[3],
66
+ m[4] * this.x + m[5] * this.y + m[6] * this.z + m[7],
67
+ m[8] * this.x + m[9] * this.y + m[10] * this.z + m[11]
68
+ );
69
+ }
70
+
71
+ /**
72
+ * Rotate around an axis by angle in radians using Rodrigues' formula.
73
+ * @param axis - Vec3 (rotation around origin) or Line (rotation around arbitrary axis)
74
+ * @param angle - Rotation angle in radians
75
+ * @returns Rotated point
76
+ */
77
+ rotate(axis: RotationAxis, angle: number): Point {
78
+ if ('start' in axis && 'end' in axis) {
79
+ const dir = new Vec3(axis.end.x - axis.start.x, axis.end.y - axis.start.y, axis.end.z - axis.start.z);
80
+ const shifted = new Point(this.x - axis.start.x, this.y - axis.start.y, this.z - axis.start.z);
81
+ const rotated = shifted.rotate(dir, angle);
82
+ return new Point(rotated.x + axis.start.x, rotated.y + axis.start.y, rotated.z + axis.start.z);
83
+ }
84
+ const len = Math.sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z);
85
+ if (len < 1e-15) return this.clone();
86
+ const k = { x: axis.x / len, y: axis.y / len, z: axis.z / len };
87
+ const cos = Math.cos(angle);
88
+ const sin = Math.sin(angle);
89
+ const dot = k.x * this.x + k.y * this.y + k.z * this.z;
90
+ const cx = k.y * this.z - k.z * this.y;
91
+ const cy = k.z * this.x - k.x * this.z;
92
+ const cz = k.x * this.y - k.y * this.x;
93
+ return new Point(
94
+ this.x * cos + cx * sin + k.x * dot * (1 - cos),
95
+ this.y * cos + cy * sin + k.y * dot * (1 - cos),
96
+ this.z * cos + cz * sin + k.z * dot * (1 - cos),
97
+ );
98
+ }
99
+
100
+ /**
101
+ * Project this point onto a plane.
102
+ * @param plane - Target plane
103
+ * @param direction - Optional projection direction (default: perpendicular to plane)
104
+ * @returns Projected point on plane
105
+ */
106
+ projectOntoPlane(plane: Plane, direction?: Vec3): Point {
107
+ return direction ? plane.projectPointAlongDirection(this, direction) : plane.projectPoint(this);
108
+ }
109
+
110
+ /**
111
+ * Get the vector from another point to this point.
112
+ * @param other - Origin point
113
+ * @returns Vector from other to this
114
+ */
115
+ sub(other: Point): Vec3 {
116
+ return new Vec3(this.x - other.x, this.y - other.y, this.z - other.z);
117
+ }
118
+
119
+ /**
120
+ * Create a copy of this point.
121
+ * @returns New point with same coordinates
122
+ */
123
+ clone(): Point {
124
+ return new Point(this.x, this.y, this.z);
125
+ }
126
+
127
+ /**
128
+ * Check equality with another point within tolerance.
129
+ * @param other - Point to compare
130
+ * @param eps - Tolerance (default 1e-10)
131
+ * @returns True if points are approximately equal
132
+ */
133
+ equals(other: Point, eps = 1e-10): boolean {
134
+ return (
135
+ Math.abs(this.x - other.x) < eps &&
136
+ Math.abs(this.y - other.y) < eps &&
137
+ Math.abs(this.z - other.z) < eps
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Convert to array format.
143
+ * @returns Tuple [x, y, z]
144
+ */
145
+ toArray(): [number, number, number] {
146
+ return [this.x, this.y, this.z];
147
+ }
148
+
149
+ /**
150
+ * Create a Point from a plain object or JSON.
151
+ * @param json - Object with x, y, z properties
152
+ * @returns New Point instance
153
+ */
154
+ static fromJSON(json: any): Point {
155
+ if (json instanceof Point) return json;
156
+ return new Point(json.x ?? 0, json.y ?? 0, json.z ?? 0);
157
+ }
158
+
159
+ /** Origin point (0, 0, 0) */
160
+ static readonly ORIGIN = new Point(0, 0, 0);
161
+ }
package/src/Polyline.ts CHANGED
@@ -135,6 +135,15 @@ export class Polyline {
135
135
  return new Polyline(this.points.map(p => p.add(offset)));
136
136
  }
137
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
+
138
147
  /**
139
148
  * Rotate this polyline around an axis.
140
149
  * @param axis - Rotation axis (Vec3 through origin, or Line for arbitrary axis)