frenet-serret-frames 3.1.0 → 4.0.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/CHANGELOG.md +14 -0
- package/README.md +58 -48
- package/index.js +376 -112
- package/package.json +29 -15
- package/types/index.d.ts +4 -2
- package/types/types.d.ts +46 -18
- package/types.js +36 -21
- package/.nojekyll +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
# [4.0.0](https://github.com/dmnsgn/frenet-serret-frames/compare/v3.1.1...v4.0.0) (2026-09-29)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* compute rotation minimizing frames with the double reflection method ([8b0a12b](https://github.com/dmnsgn/frenet-serret-frames/commit/8b0a12b7b89370310724d0fa65c91d344dae0c4e))
|
|
10
|
+
|
|
11
|
+
### BREAKING CHANGES
|
|
12
|
+
|
|
13
|
+
* normals and binormals are computed with the double reflection method from chordal tangents, and closed paths spread the closing twist by arc length.
|
|
14
|
+
|
|
15
|
+
## [3.1.1](https://github.com/dmnsgn/frenet-serret-frames/compare/v3.1.0...v3.1.1) (2024-07-07)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
5
19
|
# [3.1.0](https://github.com/dmnsgn/frenet-serret-frames/compare/v3.0.3...v3.1.0) (2024-03-15)
|
|
6
20
|
|
|
7
21
|
|
package/README.md
CHANGED
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[](https://github.com/eslint/eslint)
|
|
11
11
|
[](https://github.com/dmnsgn/frenet-serret-frames/blob/main/LICENSE.md)
|
|
12
12
|
|
|
13
|
-
Compute Frenet-Serret frames for a
|
|
13
|
+
Compute rotation minimizing (Bishop) or Frenet-Serret frames for a path of 3D positions.
|
|
14
14
|
|
|
15
15
|
[](https://paypal.me/dmnsgn)
|
|
16
16
|
[](https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3)
|
|
17
17
|
[](https://twitter.com/dmnsgn)
|
|
18
18
|
|
|
19
|
-

|
|
19
|
+
[](https://dmnsgn.github.io/frenet-serret-frames/)
|
|
20
20
|
|
|
21
21
|
## Installation
|
|
22
22
|
|
|
@@ -24,23 +24,36 @@ Compute Frenet-Serret frames for a geometry of 3D positions and optionally provi
|
|
|
24
24
|
npm install frenet-serret-frames
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- **Bishop frames** (default): rotation minimizing frames, also known as parallel transport frames, computed with the [double reflection method](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/12/Computation-of-rotation-minimizing-frames.pdf). Minimal twist around the path, suited for sweeping tubes and ribbons.
|
|
30
|
+
- **Frenet frames**: normal pointing towards the centre of curvature. Flips at inflection points; straight parts take the nearest frame.
|
|
31
|
+
- **Seamless closed paths**: the mismatch at the seam is spread along the arc length.
|
|
32
|
+
- **Constraints**: `initialNormal`, `finalNormal` on open paths, and an additional `twist` spread along the arc length.
|
|
33
|
+
- **Any layout**: flat or nested positions, normals and binormals out in the same layout, written into provided arrays.
|
|
34
|
+
- **Robust**: duplicated points, straight lines and reversed tangents handled without flips.
|
|
35
|
+
|
|
27
36
|
## Usage
|
|
28
37
|
|
|
29
38
|
```js
|
|
30
39
|
import frenetSerretFrames from "frenet-serret-frames";
|
|
40
|
+
import { squirclePath } from "primitive-geometry";
|
|
31
41
|
|
|
32
|
-
const geometry =
|
|
42
|
+
const geometry = squirclePath();
|
|
33
43
|
frenetSerretFrames(geometry, {
|
|
34
|
-
closed:
|
|
35
|
-
|
|
44
|
+
closed: false,
|
|
45
|
+
mode: "bishop",
|
|
46
|
+
initialNormal: null,
|
|
47
|
+
finalNormal: null,
|
|
48
|
+
twist: 0,
|
|
36
49
|
});
|
|
37
50
|
console.log(geometry);
|
|
38
51
|
// {
|
|
39
|
-
// positions: Float32Array [x, y, z, x, y, z,
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
52
|
+
// positions: Float32Array [x, y, z, x, y, z, ...],
|
|
53
|
+
// cells: [[a, b, c, ...]],
|
|
54
|
+
// tangents: Float32Array [x, y, z, x, y, z, ...],
|
|
55
|
+
// normals: Float32Array [x, y, z, x, y, z, ...],
|
|
56
|
+
// binormals: Float32Array [x, y, z, x, y, z, ...],
|
|
44
57
|
// }
|
|
45
58
|
```
|
|
46
59
|
|
|
@@ -52,22 +65,20 @@ console.log(geometry);
|
|
|
52
65
|
|
|
53
66
|
<dl>
|
|
54
67
|
<dt><a href="#frenetSerretFrames">frenetSerretFrames(geometry, [options])</a> ⇒ <code><a href="#SimplicialComplexWithTNB">SimplicialComplexWithTNB</a></code></dt>
|
|
55
|
-
<dd><p>Compute Frenet-Serret frames for a
|
|
68
|
+
<dd><p>Compute rotation minimizing (Bishop) or Frenet-Serret frames for a path of 3D
|
|
69
|
+
positions.</p>
|
|
56
70
|
</dd>
|
|
57
71
|
</dl>
|
|
58
72
|
|
|
59
73
|
## Typedefs
|
|
60
74
|
|
|
61
75
|
<dl>
|
|
62
|
-
<dt><a href="#vec2">vec2</a> : <code>Array.<number></code></dt>
|
|
63
|
-
<dd></dd>
|
|
64
|
-
<dt><a href="#vec3">vec3</a> : <code>Array.<number></code></dt>
|
|
65
|
-
<dd></dd>
|
|
66
76
|
<dt><a href="#SimplicialComplex">SimplicialComplex</a> : <code>object</code></dt>
|
|
67
77
|
<dd><p>Geometry definition.</p>
|
|
68
78
|
</dd>
|
|
69
79
|
<dt><a href="#SimplicialComplexWithTNB">SimplicialComplexWithTNB</a> : <code>object</code></dt>
|
|
70
|
-
<dd><p>Geometry definition augmented with
|
|
80
|
+
<dd><p>Geometry definition augmented with
|
|
81
|
+
tangents, normals and binormals.</p>
|
|
71
82
|
</dd>
|
|
72
83
|
<dt><a href="#Options">Options</a> : <code>object</code></dt>
|
|
73
84
|
<dd><p>Options for frames computation. All optional.</p>
|
|
@@ -78,26 +89,20 @@ console.log(geometry);
|
|
|
78
89
|
|
|
79
90
|
## frenetSerretFrames(geometry, [options]) ⇒ [<code>SimplicialComplexWithTNB</code>](#SimplicialComplexWithTNB)
|
|
80
91
|
|
|
81
|
-
Compute Frenet-Serret frames for a
|
|
92
|
+
Compute rotation minimizing (Bishop) or Frenet-Serret frames for a path of 3D
|
|
93
|
+
positions.
|
|
82
94
|
|
|
83
95
|
**Kind**: global function
|
|
84
|
-
**See
|
|
96
|
+
**See**
|
|
97
|
+
|
|
98
|
+
- [Frenet–Serret formulas](https://en.wikipedia.org/wiki/Frenet%E2%80%93Serret_formulas)
|
|
99
|
+
- [Computation of Rotation Minimizing Frames (Wang et al. 2008)](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/12/Computation-of-rotation-minimizing-frames.pdf)
|
|
85
100
|
|
|
86
101
|
| Param | Type | Default |
|
|
87
102
|
| --------- | ---------------------------------------------------- | --------------- |
|
|
88
103
|
| geometry | [<code>SimplicialComplex</code>](#SimplicialComplex) | |
|
|
89
104
|
| [options] | [<code>Options</code>](#Options) | <code>{}</code> |
|
|
90
105
|
|
|
91
|
-
<a name="vec2"></a>
|
|
92
|
-
|
|
93
|
-
## vec2 : <code>Array.<number></code>
|
|
94
|
-
|
|
95
|
-
**Kind**: global typedef
|
|
96
|
-
<a name="vec3"></a>
|
|
97
|
-
|
|
98
|
-
## vec3 : <code>Array.<number></code>
|
|
99
|
-
|
|
100
|
-
**Kind**: global typedef
|
|
101
106
|
<a name="SimplicialComplex"></a>
|
|
102
107
|
|
|
103
108
|
## SimplicialComplex : <code>object</code>
|
|
@@ -107,31 +112,33 @@ Geometry definition.
|
|
|
107
112
|
**Kind**: global typedef
|
|
108
113
|
**Properties**
|
|
109
114
|
|
|
110
|
-
| Name
|
|
111
|
-
|
|
|
112
|
-
| positions
|
|
113
|
-
| [tangents]
|
|
114
|
-
| [normals]
|
|
115
|
-
| [
|
|
116
|
-
| [
|
|
115
|
+
| Name | Type |
|
|
116
|
+
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
117
|
+
| positions | <code>module:pex-math~TypedArray</code> \| <code>Array.<number></code> \| <code>Array.<module:pex-math~Vec3></code> |
|
|
118
|
+
| [tangents] | <code>module:pex-math~TypedArray</code> \| <code>Array.<number></code> \| <code>Array.<module:pex-math~Vec3></code> |
|
|
119
|
+
| [normals] | <code>module:pex-math~TypedArray</code> \| <code>Array.<number></code> \| <code>Array.<module:pex-math~Vec3></code> |
|
|
120
|
+
| [binormals] | <code>module:pex-math~TypedArray</code> \| <code>Array.<number></code> \| <code>Array.<module:pex-math~Vec3></code> |
|
|
121
|
+
| [uvs] | <code>module:pex-math~TypedArray</code> \| <code>Array.<number></code> \| <code>Array.<module:pex-math~Vec2></code> |
|
|
122
|
+
| [cells] | <code>Uint8Array</code> \| <code>Uint16Array</code> \| <code>Uint32Array</code> \| <code>Array</code> \| <code>Array.<module:pex-math~Vec3></code> |
|
|
117
123
|
|
|
118
124
|
<a name="SimplicialComplexWithTNB"></a>
|
|
119
125
|
|
|
120
126
|
## SimplicialComplexWithTNB : <code>object</code>
|
|
121
127
|
|
|
122
|
-
Geometry definition augmented with
|
|
128
|
+
Geometry definition augmented with
|
|
129
|
+
tangents, normals and binormals.
|
|
123
130
|
|
|
124
131
|
**Kind**: global typedef
|
|
125
132
|
**Properties**
|
|
126
133
|
|
|
127
|
-
| Name | Type
|
|
128
|
-
| --------- |
|
|
129
|
-
| positions | <code>
|
|
130
|
-
| tangents | <code>
|
|
131
|
-
| normals | <code>
|
|
132
|
-
| binormals | <code>
|
|
133
|
-
| [uvs] | <code>
|
|
134
|
-
| [cells] | <code>Uint8Array</code> \| <code>Uint16Array</code> \| <code>Uint32Array</code> \| <code>Array</code> \|
|
|
134
|
+
| Name | Type |
|
|
135
|
+
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
136
|
+
| positions | <code>module:pex-math~TypedArray</code> \| <code>Array.<number></code> \| <code>Array.<module:pex-math~Vec3></code> |
|
|
137
|
+
| tangents | <code>module:pex-math~TypedArray</code> \| <code>Array.<number></code> \| <code>Array.<module:pex-math~Vec3></code> |
|
|
138
|
+
| normals | <code>module:pex-math~TypedArray</code> \| <code>Array.<number></code> \| <code>Array.<module:pex-math~Vec3></code> |
|
|
139
|
+
| binormals | <code>module:pex-math~TypedArray</code> \| <code>Array.<number></code> \| <code>Array.<module:pex-math~Vec3></code> |
|
|
140
|
+
| [uvs] | <code>module:pex-math~TypedArray</code> \| <code>Array.<number></code> \| <code>Array.<module:pex-math~Vec2></code> |
|
|
141
|
+
| [cells] | <code>Uint8Array</code> \| <code>Uint16Array</code> \| <code>Uint32Array</code> \| <code>Array</code> \| <code>Array.<module:pex-math~Vec3></code> |
|
|
135
142
|
|
|
136
143
|
<a name="Options"></a>
|
|
137
144
|
|
|
@@ -142,10 +149,13 @@ Options for frames computation. All optional.
|
|
|
142
149
|
**Kind**: global typedef
|
|
143
150
|
**Properties**
|
|
144
151
|
|
|
145
|
-
| Name | Type
|
|
146
|
-
| --------------- |
|
|
147
|
-
| [closed] | <code>boolean</code>
|
|
148
|
-
| [
|
|
152
|
+
| Name | Type | Default | Description |
|
|
153
|
+
| --------------- | ------------------------------------------------------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
154
|
+
| [closed] | <code>boolean</code> | <code>false</code> | Specify if the path is closed. |
|
|
155
|
+
| [mode] | <code>"bishop"</code> \| <code>"frenet"</code> | <code>"bishop"</code> | Frame type: - "bishop": rotation minimizing (parallel transport) frames, computed with the double reflection method. Twist free, suited for sweeping. - "frenet": normal pointing towards the curvature centre. Flips at inflection points and is undefined on straight parts, where the nearest frame is transported instead. |
|
|
156
|
+
| [initialNormal] | <code>module:pex-math~Vec3</code> | <code></code> | Specify a starting normal for "bishop" frames, made orthogonal to the first tangent. Defaults to a coordinate axis not aligned with the first tangent, also used when the initial normal is parallel to it. |
|
|
157
|
+
| [finalNormal] | <code>module:pex-math~Vec3</code> | <code></code> | Specify an ending normal for "bishop" frames of open paths, made orthogonal to the last tangent. Reached with the smallest rotation, spread along the arc length. Ignored when parallel to the last tangent. |
|
|
158
|
+
| [twist] | <code>number</code> | <code>0</code> | Additional rotation of "bishop" frames around the tangent, in radians, spread along the arc length from the first frame to the last. Closed paths and paths with a final normal need a multiple of 2π to keep their end frame, or of π when the sign of the normal doesn't matter (eg. flat ribbons). |
|
|
149
159
|
|
|
150
160
|
<!-- api-end -->
|
|
151
161
|
|
package/index.js
CHANGED
|
@@ -1,147 +1,411 @@
|
|
|
1
|
-
import { avec3
|
|
2
|
-
import computePathTangents
|
|
1
|
+
import { avec3 } from "pex-math";
|
|
2
|
+
import computePathTangents, {
|
|
3
|
+
computeNeighbours,
|
|
4
|
+
setDelta,
|
|
5
|
+
} from "path-tangents";
|
|
3
6
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
+
const X_AXIS = [1, 0, 0];
|
|
8
|
+
const Y_AXIS = [0, 1, 0];
|
|
9
|
+
const Z_AXIS = [0, 0, 1];
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* @param {import("./types.js").Options} [options={}]
|
|
13
|
-
* @returns {import("./types.js").SimplicialComplexWithTNB}
|
|
14
|
-
*
|
|
15
|
-
* @see [Frenet–Serret formulas]{@link https://en.wikipedia.org/wiki/Frenet%E2%80%93Serret_formulas}
|
|
16
|
-
*/
|
|
17
|
-
function frenetSerretFrames(geometry, options) {
|
|
18
|
-
const isFlatArray = !geometry.positions[0]?.length;
|
|
11
|
+
// v₂ = tᵢ₊₁ − R₁tᵢ has |v₂|² ≈ 4 on sampled curves and vanishes where R₂ is
|
|
12
|
+
// undefined (Wang et al. 2008, §4.4): its direction, hence the frame, is then
|
|
13
|
+
// set by rounding errors. Accuracy is unchanged from 1e-12 to 1e-2.
|
|
14
|
+
const DEGENERATE_V2_SQ = 1e-6;
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
// Turns below this many rounding errors of the positions are noise.
|
|
17
|
+
const FRENET_NOISE = 4;
|
|
18
|
+
const FLOAT32_EPSILON = 2 ** -23;
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
// Temporary vectors reused to avoid allocations.
|
|
21
|
+
// v₁, v₂ and tᴸ follow Wang et al. 2008.
|
|
22
|
+
const TEMP_AVEC3 = new Float64Array(5 * 3);
|
|
23
|
+
const [V1, V2, TANGENT_L, END_NORMAL, CROSS] = [0, 1, 2, 3, 4];
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
function toFlatArray(array) {
|
|
26
|
+
if (!array[0]?.length) return array;
|
|
27
|
+
const flat = new Float64Array(array.length * 3);
|
|
28
|
+
array.forEach((v, i) => avec3.set(flat, i, v, 0));
|
|
29
|
+
return flat;
|
|
30
|
+
}
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
: geometry.tangents[0];
|
|
34
|
-
let normal;
|
|
32
|
+
function fromFlatArray(flat, size, ArrayConstructor, out) {
|
|
33
|
+
const isFlatArray = ArrayConstructor !== Array;
|
|
34
|
+
out ||= new ArrayConstructor(isFlatArray ? size * 3 : size);
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const atx = Math.abs(tangent[0]);
|
|
40
|
-
const aty = Math.abs(tangent[1]);
|
|
41
|
-
const atz = Math.abs(tangent[2]);
|
|
42
|
-
|
|
43
|
-
if (aty > atx && aty >= atz) {
|
|
44
|
-
v = vec3.cross([...tangent], X_UP);
|
|
45
|
-
} else if (atz > atx && atz >= aty) {
|
|
46
|
-
v = vec3.cross([...tangent], Y_UP);
|
|
36
|
+
for (let i = 0; i < size; i++) {
|
|
37
|
+
if (isFlatArray) {
|
|
38
|
+
avec3.set(out, i, flat, i);
|
|
47
39
|
} else {
|
|
48
|
-
|
|
40
|
+
avec3.set((out[i] ||= [0, 0, 0]), 0, flat, i);
|
|
49
41
|
}
|
|
50
|
-
|
|
51
|
-
normal = vec3.normalize(vec3.cross([...tangent], v));
|
|
52
42
|
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
53
45
|
|
|
54
|
-
|
|
46
|
+
// Remove the component of out[i] along the unit axes[j], leaving the part
|
|
47
|
+
// orthogonal to it.
|
|
48
|
+
function reject(out, i, axes, j) {
|
|
49
|
+
avec3.addScaled(out, i, axes, j, -avec3.dot(out, i, axes, j));
|
|
50
|
+
}
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
// Mirror a[i] across the plane orthogonal to v[j], which needs no normalizing.
|
|
53
|
+
function reflect(a, i, v, j) {
|
|
54
|
+
avec3.addScaled(
|
|
55
|
+
a,
|
|
56
|
+
i,
|
|
57
|
+
v,
|
|
58
|
+
j,
|
|
59
|
+
(-2 * avec3.dot(v, j, a, i)) / avec3.lengthSq(v, j),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Any axis but the one most aligned with the tangent: at least 45° away.
|
|
64
|
+
function getReferenceAxis(tangents) {
|
|
65
|
+
const x = Math.abs(tangents[0]);
|
|
66
|
+
const y = Math.abs(tangents[1]);
|
|
67
|
+
const z = Math.abs(tangents[2]);
|
|
68
|
+
|
|
69
|
+
if (y > x && y >= z) return X_AXIS;
|
|
70
|
+
return z > x && z >= y ? Y_AXIS : Z_AXIS;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function setInitialNormal(normals, tangents, initialNormal) {
|
|
74
|
+
if (initialNormal) {
|
|
75
|
+
avec3.set(normals, 0, initialNormal, 0);
|
|
76
|
+
avec3.normalize(normals, 0);
|
|
77
|
+
reject(normals, 0, tangents, 0);
|
|
59
78
|
} else {
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
// Clear leftovers of undefined Frenet normals
|
|
80
|
+
normals.fill(0, 0, 3);
|
|
62
81
|
}
|
|
63
82
|
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
83
|
+
// Missing, zero or parallel to the tangent: t × (t × axis) = (t·axis)t − axis
|
|
84
|
+
if (avec3.lengthSq(normals, 0) <= Number.EPSILON) {
|
|
85
|
+
const axis = getReferenceAxis(tangents);
|
|
86
|
+
avec3.set(normals, 0, tangents, 0);
|
|
87
|
+
avec3.scale(normals, 0, avec3.dot(tangents, 0, axis, 0));
|
|
88
|
+
avec3.sub(normals, 0, axis, 0);
|
|
89
|
+
}
|
|
67
90
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
avec3.set(previousTangent, 0, geometry.tangents, i - 1);
|
|
71
|
-
avec3.set(tangent, 0, geometry.tangents, i);
|
|
72
|
-
} else {
|
|
73
|
-
previousTangent = geometry.tangents[i - 1];
|
|
74
|
-
tangent = geometry.tangents[i];
|
|
75
|
-
}
|
|
91
|
+
avec3.normalize(normals, 0);
|
|
92
|
+
}
|
|
76
93
|
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
// Double reflection of the normal at `from` onto `to` across v₁ then v₂,
|
|
95
|
+
// written at out[i]. Returns false where either reflection is undefined or
|
|
96
|
+
// ill-conditioned.
|
|
97
|
+
function doubleReflect(out, i, normals, tangents, from, to) {
|
|
98
|
+
if (!avec3.lengthSq(TEMP_AVEC3, V1)) return false;
|
|
79
99
|
|
|
80
|
-
|
|
100
|
+
avec3.set(TEMP_AVEC3, TANGENT_L, tangents, from);
|
|
101
|
+
reflect(TEMP_AVEC3, TANGENT_L, TEMP_AVEC3, V1);
|
|
102
|
+
avec3.set(TEMP_AVEC3, V2, tangents, to);
|
|
103
|
+
avec3.sub(TEMP_AVEC3, V2, TEMP_AVEC3, TANGENT_L);
|
|
81
104
|
|
|
82
|
-
|
|
83
|
-
vec3.normalize(v);
|
|
105
|
+
if (avec3.lengthSq(TEMP_AVEC3, V2) <= DEGENERATE_V2_SQ) return false;
|
|
84
106
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
quat.fromAxisAngle(rotation, v, theta);
|
|
89
|
-
vec3.multQuat(normal, rotation);
|
|
90
|
-
}
|
|
91
|
-
binormal = vec3.cross([...tangent], normal);
|
|
107
|
+
avec3.set(out, i, normals, from);
|
|
108
|
+
reflect(out, i, TEMP_AVEC3, V1);
|
|
109
|
+
reflect(out, i, TEMP_AVEC3, V2);
|
|
92
110
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Transport the normal at `from` onto `to`, written at out[i], with the double
|
|
115
|
+
// reflection method. Where it degenerates, fall back to the rotation method
|
|
116
|
+
// (v₁ set to the tangent at `from`, Wang et al. 2008, §2.1), and keep the
|
|
117
|
+
// normal on reversed tangents where no rotation is minimal.
|
|
118
|
+
function transport(out, i, normals, tangents, positions, from, to) {
|
|
119
|
+
setDelta(TEMP_AVEC3, V1, positions, from, to);
|
|
120
|
+
|
|
121
|
+
if (!doubleReflect(out, i, normals, tangents, from, to)) {
|
|
122
|
+
avec3.set(TEMP_AVEC3, V1, tangents, from);
|
|
123
|
+
if (!doubleReflect(out, i, normals, tangents, from, to)) {
|
|
124
|
+
avec3.set(out, i, normals, from);
|
|
99
125
|
}
|
|
100
126
|
}
|
|
101
127
|
|
|
128
|
+
avec3.normalize(out, i);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function rotateAroundTangent(normals, tangents, i, angle) {
|
|
132
|
+
avec3.set(TEMP_AVEC3, CROSS, tangents, i);
|
|
133
|
+
avec3.cross(TEMP_AVEC3, CROSS, normals, i);
|
|
134
|
+
avec3.scale(normals, i, Math.cos(angle));
|
|
135
|
+
avec3.addScaled(normals, i, TEMP_AVEC3, CROSS, Math.sin(angle));
|
|
136
|
+
avec3.normalize(normals, i);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Angle from a[i] to b[j] around axes[k].
|
|
140
|
+
function signedAngle(a, i, b, j, axes, k) {
|
|
141
|
+
avec3.set(TEMP_AVEC3, CROSS, a, i);
|
|
142
|
+
avec3.cross(TEMP_AVEC3, CROSS, b, j);
|
|
143
|
+
|
|
144
|
+
return Math.atan2(
|
|
145
|
+
avec3.dot(TEMP_AVEC3, CROSS, axes, k),
|
|
146
|
+
avec3.dot(a, i, b, j),
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Minimal rotation from the last frame to the end condition (Wang et al. 2008,
|
|
151
|
+
// §6.3): the first frame for closed paths, transported back to the start, or
|
|
152
|
+
// the final normal for open ones.
|
|
153
|
+
function computeEndTwist(
|
|
154
|
+
normals,
|
|
155
|
+
tangents,
|
|
156
|
+
positions,
|
|
157
|
+
size,
|
|
158
|
+
closed,
|
|
159
|
+
finalNormal,
|
|
160
|
+
) {
|
|
161
|
+
const last = size - 1;
|
|
162
|
+
|
|
102
163
|
if (closed) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
164
|
+
transport(TEMP_AVEC3, END_NORMAL, normals, tangents, positions, last, 0);
|
|
165
|
+
return signedAngle(TEMP_AVEC3, END_NORMAL, normals, 0, tangents, 0);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!finalNormal) return 0;
|
|
169
|
+
|
|
170
|
+
avec3.set(TEMP_AVEC3, END_NORMAL, finalNormal, 0);
|
|
171
|
+
avec3.normalize(TEMP_AVEC3, END_NORMAL);
|
|
172
|
+
reject(TEMP_AVEC3, END_NORMAL, tangents, last);
|
|
173
|
+
|
|
174
|
+
// Zero or parallel to the tangent: no end condition.
|
|
175
|
+
return avec3.lengthSq(TEMP_AVEC3, END_NORMAL) <= Number.EPSILON
|
|
176
|
+
? 0
|
|
177
|
+
: signedAngle(normals, last, TEMP_AVEC3, END_NORMAL, tangents, last);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Additional rotation proportional to arc length: minimal total squared
|
|
181
|
+
// angular speed (Wang et al. 2008, §6.3), even on uneven sampling.
|
|
182
|
+
function spreadTwist(normals, tangents, positions, size, closed, twist) {
|
|
183
|
+
const lengths = new Float64Array(size);
|
|
184
|
+
|
|
185
|
+
for (let i = 1; i < size; i++) {
|
|
186
|
+
lengths[i] =
|
|
187
|
+
lengths[i - 1] + avec3.distance(positions, i - 1, positions, i);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let totalLength = lengths[size - 1];
|
|
191
|
+
|
|
192
|
+
if (closed) totalLength += avec3.distance(positions, size - 1, positions, 0);
|
|
193
|
+
if (totalLength === 0) return;
|
|
194
|
+
|
|
195
|
+
for (let i = 1; i < size; i++) {
|
|
196
|
+
rotateAroundTangent(
|
|
197
|
+
normals,
|
|
198
|
+
tangents,
|
|
199
|
+
i,
|
|
200
|
+
(twist * lengths[i]) / totalLength,
|
|
115
201
|
);
|
|
116
|
-
|
|
202
|
+
}
|
|
203
|
+
}
|
|
117
204
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
205
|
+
function computeBishopNormals(
|
|
206
|
+
normals,
|
|
207
|
+
tangents,
|
|
208
|
+
positions,
|
|
209
|
+
size,
|
|
210
|
+
closed,
|
|
211
|
+
initialNormal,
|
|
212
|
+
finalNormal,
|
|
213
|
+
twist,
|
|
214
|
+
) {
|
|
215
|
+
if (!size) return;
|
|
216
|
+
|
|
217
|
+
setInitialNormal(normals, tangents, initialNormal);
|
|
218
|
+
|
|
219
|
+
for (let i = 1; i < size; i++) {
|
|
220
|
+
transport(normals, i, normals, tangents, positions, i - 1, i);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (size < 2) return;
|
|
224
|
+
|
|
225
|
+
const totalTwist =
|
|
226
|
+
twist +
|
|
227
|
+
computeEndTwist(normals, tangents, positions, size, closed, finalNormal);
|
|
228
|
+
|
|
229
|
+
if (totalTwist) {
|
|
230
|
+
spreadTwist(normals, tangents, positions, size, closed, totalTwist);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const maxAbs = (a, i) =>
|
|
235
|
+
Math.max(Math.abs(a[i * 3]), Math.abs(a[i * 3 + 1]), Math.abs(a[i * 3 + 2]));
|
|
236
|
+
|
|
237
|
+
// Discrete curvature direction: how the unit segment directions turn at a
|
|
238
|
+
// point, between its distinct neighbours. Open path ends borrow the turn of
|
|
239
|
+
// their neighbour. Returns false where the turn is below the rounding noise of
|
|
240
|
+
// the positions, which grows with their magnitude over the segment lengths.
|
|
241
|
+
function computeFrenetNormal(
|
|
242
|
+
normals,
|
|
243
|
+
tangents,
|
|
244
|
+
positions,
|
|
245
|
+
{ prev, next },
|
|
246
|
+
precision,
|
|
247
|
+
i,
|
|
248
|
+
) {
|
|
249
|
+
let center = i;
|
|
250
|
+
if (prev[i] === -1) {
|
|
251
|
+
center = next[i];
|
|
252
|
+
} else if (next[i] === -1) {
|
|
253
|
+
center = prev[i];
|
|
254
|
+
}
|
|
255
|
+
if (center === -1 || prev[center] === -1 || next[center] === -1) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
setDelta(TEMP_AVEC3, V1, positions, prev[center], center);
|
|
260
|
+
setDelta(TEMP_AVEC3, V2, positions, center, next[center]);
|
|
261
|
+
|
|
262
|
+
const l1 = avec3.length(TEMP_AVEC3, V1);
|
|
263
|
+
const l2 = avec3.length(TEMP_AVEC3, V2);
|
|
264
|
+
|
|
265
|
+
avec3.scale(TEMP_AVEC3, V1, 1 / l1);
|
|
266
|
+
avec3.scale(TEMP_AVEC3, V2, 1 / l2);
|
|
267
|
+
|
|
268
|
+
avec3.set(normals, i, TEMP_AVEC3, V2);
|
|
269
|
+
avec3.sub(normals, i, TEMP_AVEC3, V1);
|
|
270
|
+
reject(normals, i, tangents, i);
|
|
271
|
+
|
|
272
|
+
const magnitude = Math.max(
|
|
273
|
+
maxAbs(positions, prev[center]),
|
|
274
|
+
maxAbs(positions, center),
|
|
275
|
+
maxAbs(positions, next[center]),
|
|
276
|
+
);
|
|
277
|
+
const noise = FRENET_NOISE * precision * magnitude * (1 / l1 + 1 / l2);
|
|
278
|
+
if (avec3.length(normals, i) <= noise) return false;
|
|
279
|
+
|
|
280
|
+
avec3.normalize(normals, i);
|
|
281
|
+
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Frenet frames are undefined where the curve is straight: fill those points
|
|
286
|
+
// by transporting the nearest defined frame.
|
|
287
|
+
function computeFrenetNormals(
|
|
288
|
+
normals,
|
|
289
|
+
tangents,
|
|
290
|
+
positions,
|
|
291
|
+
size,
|
|
292
|
+
closed,
|
|
293
|
+
precision,
|
|
294
|
+
) {
|
|
295
|
+
const neighbours = computeNeighbours(positions, size, closed);
|
|
296
|
+
const defined = Array.from({ length: size }, (_, i) =>
|
|
297
|
+
computeFrenetNormal(normals, tangents, positions, neighbours, precision, i),
|
|
298
|
+
);
|
|
299
|
+
const first = defined.indexOf(true);
|
|
300
|
+
if (first === -1) {
|
|
301
|
+
computeBishopNormals(
|
|
302
|
+
normals,
|
|
303
|
+
tangents,
|
|
304
|
+
positions,
|
|
305
|
+
size,
|
|
306
|
+
closed,
|
|
307
|
+
null,
|
|
308
|
+
null,
|
|
309
|
+
0,
|
|
310
|
+
);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
121
313
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
avec3.set(binormal, 0, geometry.binormals, i);
|
|
127
|
-
|
|
128
|
-
quat.fromAxisAngle(rotation, tangent, theta * i);
|
|
129
|
-
vec3.multQuat(normal, rotation);
|
|
130
|
-
binormal = vec3.cross([...tangent], normal);
|
|
131
|
-
|
|
132
|
-
avec3.set(geometry.normals, i, normal, 0);
|
|
133
|
-
avec3.set(geometry.binormals, i, binormal, 0);
|
|
134
|
-
} else {
|
|
135
|
-
const tangent = geometry.tangents[i];
|
|
136
|
-
const normal = geometry.normals[i];
|
|
137
|
-
quat.fromAxisAngle(rotation, tangent, theta * i);
|
|
138
|
-
vec3.multQuat(normal, rotation);
|
|
139
|
-
geometry.binormals[i] = vec3.cross([...tangent], normal);
|
|
140
|
-
}
|
|
314
|
+
// Closed paths wrap around so frames stay continuous across the seam.
|
|
315
|
+
if (!closed) {
|
|
316
|
+
for (let i = first - 1; i >= 0; i--) {
|
|
317
|
+
transport(normals, i, normals, tangents, positions, i + 1, i);
|
|
141
318
|
}
|
|
142
319
|
}
|
|
320
|
+
const end = closed ? first + size : size;
|
|
321
|
+
for (let k = first + 1; k < end; k++) {
|
|
322
|
+
const i = k % size;
|
|
323
|
+
if (!defined[i]) {
|
|
324
|
+
transport(normals, i, normals, tangents, positions, (k - 1) % size, i);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Compute rotation minimizing (Bishop) or Frenet-Serret frames for a path of 3D
|
|
331
|
+
* positions.
|
|
332
|
+
*
|
|
333
|
+
* @param {import("./types.js").SimplicialComplex} geometry
|
|
334
|
+
* @param {import("./types.js").Options} [options={}]
|
|
335
|
+
* @returns {import("./types.js").SimplicialComplexWithTNB}
|
|
336
|
+
* @see [Frenet–Serret formulas]{@link https://en.wikipedia.org/wiki/Frenet%E2%80%93Serret_formulas}
|
|
337
|
+
* @see [Computation of Rotation Minimizing Frames (Wang et al. 2008)]{@link https://www.microsoft.com/en-us/research/wp-content/uploads/2016/12/Computation-of-rotation-minimizing-frames.pdf}
|
|
338
|
+
*/
|
|
339
|
+
function frenetSerretFrames(geometry, options) {
|
|
340
|
+
const {
|
|
341
|
+
closed = false,
|
|
342
|
+
mode = "bishop",
|
|
343
|
+
initialNormal = null,
|
|
344
|
+
finalNormal = null,
|
|
345
|
+
twist = 0,
|
|
346
|
+
} = { ...options };
|
|
347
|
+
|
|
348
|
+
const isFlatArray = !geometry.positions[0]?.length;
|
|
349
|
+
const size = geometry.positions.length / (isFlatArray ? 3 : 1);
|
|
350
|
+
const positions = toFlatArray(geometry.positions);
|
|
351
|
+
|
|
352
|
+
// Same layout as path-tangents output
|
|
353
|
+
let ArrayConstructor = Array;
|
|
354
|
+
if (isFlatArray) {
|
|
355
|
+
ArrayConstructor =
|
|
356
|
+
geometry.positions instanceof Float64Array ? Float64Array : Float32Array;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
geometry.tangents ||= computePathTangents(geometry.positions, { closed });
|
|
360
|
+
const tangents = toFlatArray(geometry.tangents);
|
|
361
|
+
const normals = new Float64Array(size * 3);
|
|
362
|
+
const binormals = new Float64Array(size * 3);
|
|
363
|
+
|
|
364
|
+
if (mode === "frenet") {
|
|
365
|
+
computeFrenetNormals(
|
|
366
|
+
normals,
|
|
367
|
+
tangents,
|
|
368
|
+
positions,
|
|
369
|
+
size,
|
|
370
|
+
closed,
|
|
371
|
+
geometry.positions instanceof Float32Array
|
|
372
|
+
? FLOAT32_EPSILON
|
|
373
|
+
: Number.EPSILON,
|
|
374
|
+
);
|
|
375
|
+
} else {
|
|
376
|
+
computeBishopNormals(
|
|
377
|
+
normals,
|
|
378
|
+
tangents,
|
|
379
|
+
positions,
|
|
380
|
+
size,
|
|
381
|
+
closed,
|
|
382
|
+
initialNormal,
|
|
383
|
+
finalNormal,
|
|
384
|
+
twist,
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
for (let i = 0; i < size; i++) {
|
|
389
|
+
avec3.set(binormals, i, tangents, i);
|
|
390
|
+
avec3.cross(binormals, i, normals, i);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
geometry.normals = fromFlatArray(
|
|
394
|
+
normals,
|
|
395
|
+
size,
|
|
396
|
+
ArrayConstructor,
|
|
397
|
+
geometry.normals,
|
|
398
|
+
);
|
|
399
|
+
geometry.binormals = fromFlatArray(
|
|
400
|
+
binormals,
|
|
401
|
+
size,
|
|
402
|
+
ArrayConstructor,
|
|
403
|
+
geometry.binormals,
|
|
404
|
+
);
|
|
143
405
|
|
|
144
406
|
return geometry;
|
|
145
407
|
}
|
|
146
408
|
|
|
147
409
|
export default frenetSerretFrames;
|
|
410
|
+
|
|
411
|
+
export * from "./types.js";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frenet-serret-frames",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Compute Frenet-Serret frames for a
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "Compute rotation minimizing (Bishop) or Frenet-Serret frames for a path of 3D positions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"path",
|
|
7
7
|
"spline",
|
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
"frenet",
|
|
11
11
|
"serret",
|
|
12
12
|
"frames",
|
|
13
|
-
"parallel-transport"
|
|
13
|
+
"parallel-transport",
|
|
14
|
+
"rotation-minimizing",
|
|
15
|
+
"bishop",
|
|
16
|
+
"double-reflection"
|
|
14
17
|
],
|
|
15
18
|
"homepage": "https://github.com/dmnsgn/frenet-serret-frames",
|
|
16
19
|
"bugs": "https://github.com/dmnsgn/frenet-serret-frames/issues",
|
|
17
|
-
"repository":
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/dmnsgn/frenet-serret-frames.git"
|
|
23
|
+
},
|
|
18
24
|
"funding": [
|
|
19
25
|
{
|
|
20
26
|
"type": "individual",
|
|
@@ -29,21 +35,29 @@
|
|
|
29
35
|
"author": "Damien Seguin (https://github.com/dmnsgn)",
|
|
30
36
|
"sideEffects": false,
|
|
31
37
|
"type": "module",
|
|
32
|
-
"exports":
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./types/index.d.ts",
|
|
41
|
+
"default": "./index.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"main": "./index.js",
|
|
45
|
+
"types": "./types/index.d.ts",
|
|
35
46
|
"dependencies": {
|
|
36
|
-
"path-tangents": "^
|
|
37
|
-
"pex-math": "^4.
|
|
47
|
+
"path-tangents": "^4.2.0",
|
|
48
|
+
"pex-math": "^4.4.0"
|
|
38
49
|
},
|
|
39
50
|
"devDependencies": {
|
|
40
|
-
"es-module-shims": "^
|
|
41
|
-
"pex-cam": "^3.0.
|
|
42
|
-
"pex-context": "^
|
|
43
|
-
"
|
|
51
|
+
"es-module-shims": "^2.8.4",
|
|
52
|
+
"pex-cam": "^3.0.1",
|
|
53
|
+
"pex-context": "^4.1.0",
|
|
54
|
+
"pex-random": "^2.1.2",
|
|
55
|
+
"primitive-geometry": "^3.1.0",
|
|
56
|
+
"tweakpane": "^4.0.5"
|
|
44
57
|
},
|
|
58
|
+
"packageManager": "npm@10.5.1",
|
|
45
59
|
"engines": {
|
|
46
|
-
"node": ">=
|
|
47
|
-
"
|
|
60
|
+
"node": ">=22.0.0",
|
|
61
|
+
"snowdev": ">=3.0.0-alpha.3"
|
|
48
62
|
}
|
|
49
63
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export default frenetSerretFrames;
|
|
2
|
+
export * from "./types.js";
|
|
2
3
|
/**
|
|
3
|
-
* Compute Frenet-Serret frames for a
|
|
4
|
+
* Compute rotation minimizing (Bishop) or Frenet-Serret frames for a path of 3D
|
|
5
|
+
* positions.
|
|
4
6
|
*
|
|
5
7
|
* @param {import("./types.js").SimplicialComplex} geometry
|
|
6
8
|
* @param {import("./types.js").Options} [options={}]
|
|
7
9
|
* @returns {import("./types.js").SimplicialComplexWithTNB}
|
|
8
|
-
*
|
|
9
10
|
* @see [Frenet–Serret formulas]{@link https://en.wikipedia.org/wiki/Frenet%E2%80%93Serret_formulas}
|
|
11
|
+
* @see [Computation of Rotation Minimizing Frames (Wang et al. 2008)]{@link https://www.microsoft.com/en-us/research/wp-content/uploads/2016/12/Computation-of-rotation-minimizing-frames.pdf}
|
|
10
12
|
*/
|
|
11
13
|
declare function frenetSerretFrames(geometry: import("./types.js").SimplicialComplex, options?: import("./types.js").Options): import("./types.js").SimplicialComplexWithTNB;
|
package/types/types.d.ts
CHANGED
|
@@ -1,36 +1,64 @@
|
|
|
1
|
-
export type vec2 = number[];
|
|
2
|
-
export type vec3 = number[];
|
|
3
1
|
/**
|
|
4
2
|
* Geometry definition.
|
|
5
3
|
*/
|
|
6
4
|
export type SimplicialComplex = {
|
|
7
|
-
positions:
|
|
8
|
-
tangents?:
|
|
9
|
-
normals?:
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
positions: import("pex-math").TypedArray | number[] | import("pex-math").Vec3[];
|
|
6
|
+
tangents?: number[] | import("pex-math").TypedArray | import("pex-math").Vec3[] | undefined;
|
|
7
|
+
normals?: number[] | import("pex-math").TypedArray | import("pex-math").Vec3[] | undefined;
|
|
8
|
+
binormals?: number[] | import("pex-math").TypedArray | import("pex-math").Vec3[] | undefined;
|
|
9
|
+
uvs?: number[] | import("pex-math").TypedArray | import("pex-math").Vec2[] | undefined;
|
|
10
|
+
cells?: any[] | Uint8Array<ArrayBufferLike> | Uint16Array<ArrayBufferLike> | Uint32Array<ArrayBufferLike> | import("pex-math").Vec3[] | undefined;
|
|
12
11
|
};
|
|
13
12
|
/**
|
|
14
|
-
* Geometry definition augmented with
|
|
13
|
+
* Geometry definition augmented with
|
|
14
|
+
* tangents, normals and binormals.
|
|
15
15
|
*/
|
|
16
16
|
export type SimplicialComplexWithTNB = {
|
|
17
|
-
positions:
|
|
18
|
-
tangents:
|
|
19
|
-
normals:
|
|
20
|
-
binormals:
|
|
21
|
-
uvs?:
|
|
22
|
-
cells?:
|
|
17
|
+
positions: import("pex-math").TypedArray | number[] | import("pex-math").Vec3[];
|
|
18
|
+
tangents: import("pex-math").TypedArray | number[] | import("pex-math").Vec3[];
|
|
19
|
+
normals: import("pex-math").TypedArray | number[] | import("pex-math").Vec3[];
|
|
20
|
+
binormals: import("pex-math").TypedArray | number[] | import("pex-math").Vec3[];
|
|
21
|
+
uvs?: number[] | import("pex-math").TypedArray | import("pex-math").Vec2[] | undefined;
|
|
22
|
+
cells?: any[] | Uint8Array<ArrayBufferLike> | Uint16Array<ArrayBufferLike> | Uint32Array<ArrayBufferLike> | import("pex-math").Vec3[] | undefined;
|
|
23
23
|
};
|
|
24
24
|
/**
|
|
25
25
|
* Options for frames computation. All optional.
|
|
26
26
|
*/
|
|
27
27
|
export type Options = {
|
|
28
28
|
/**
|
|
29
|
-
* Specify
|
|
29
|
+
* Specify if the path is closed.
|
|
30
30
|
*/
|
|
31
|
-
closed?: boolean;
|
|
31
|
+
closed?: boolean | undefined;
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* Frame type:
|
|
34
|
+
*
|
|
35
|
+
* - "bishop": rotation minimizing (parallel transport) frames, computed with the
|
|
36
|
+
* double reflection method. Twist free, suited for sweeping.
|
|
37
|
+
* - "frenet": normal pointing towards the curvature centre. Flips at inflection
|
|
38
|
+
* points and is undefined on straight parts, where the nearest frame is
|
|
39
|
+
* transported instead.
|
|
34
40
|
*/
|
|
35
|
-
|
|
41
|
+
mode?: "bishop" | "frenet" | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Specify a starting
|
|
44
|
+
* normal for "bishop" frames, made orthogonal to the first tangent. Defaults
|
|
45
|
+
* to a coordinate axis not aligned with the first tangent, also used when the
|
|
46
|
+
* initial normal is parallel to it.
|
|
47
|
+
*/
|
|
48
|
+
initialNormal?: import("pex-math").Vec3 | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Specify an ending
|
|
51
|
+
* normal for "bishop" frames of open paths, made orthogonal to the last
|
|
52
|
+
* tangent. Reached with the smallest rotation, spread along the arc length.
|
|
53
|
+
* Ignored when parallel to the last tangent.
|
|
54
|
+
*/
|
|
55
|
+
finalNormal?: import("pex-math").Vec3 | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Additional rotation of "bishop" frames around
|
|
58
|
+
* the tangent, in radians, spread along the arc length from the first frame
|
|
59
|
+
* to the last. Closed paths and paths with a final normal need a multiple of
|
|
60
|
+
* 2π to keep their end frame, or of π when the sign of the normal doesn't
|
|
61
|
+
* matter (eg. flat ribbons).
|
|
62
|
+
*/
|
|
63
|
+
twist?: number | undefined;
|
|
36
64
|
};
|
package/types.js
CHANGED
|
@@ -1,33 +1,48 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {number[]} vec2
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* @typedef {number[]} vec3
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
/**
|
|
9
2
|
* @typedef {object} SimplicialComplex Geometry definition.
|
|
10
|
-
* @property {
|
|
11
|
-
* @property {
|
|
12
|
-
* @property {
|
|
13
|
-
* @property {
|
|
14
|
-
* @property {(
|
|
3
|
+
* @property {import("pex-math").TypedArray | number[] | import("pex-math").Vec3[]} positions
|
|
4
|
+
* @property {import("pex-math").TypedArray | number[] | import("pex-math").Vec3[]} [tangents]
|
|
5
|
+
* @property {import("pex-math").TypedArray | number[] | import("pex-math").Vec3[]} [normals]
|
|
6
|
+
* @property {import("pex-math").TypedArray | number[] | import("pex-math").Vec3[]} [binormals]
|
|
7
|
+
* @property {import("pex-math").TypedArray | number[] | import("pex-math").Vec2[]} [uvs]
|
|
8
|
+
* @property {Uint8Array | Uint16Array | Uint32Array | Array | import("pex-math").Vec3[]} [cells]
|
|
15
9
|
*/
|
|
16
10
|
|
|
17
11
|
/**
|
|
18
|
-
* @typedef {object} SimplicialComplexWithTNB Geometry definition augmented with
|
|
19
|
-
*
|
|
20
|
-
* @property {
|
|
21
|
-
* @property {
|
|
22
|
-
* @property {
|
|
23
|
-
* @property {
|
|
24
|
-
* @property {(
|
|
12
|
+
* @typedef {object} SimplicialComplexWithTNB Geometry definition augmented with
|
|
13
|
+
* tangents, normals and binormals.
|
|
14
|
+
* @property {import("pex-math").TypedArray | number[] | import("pex-math").Vec3[]} positions
|
|
15
|
+
* @property {import("pex-math").TypedArray | number[] | import("pex-math").Vec3[]} tangents
|
|
16
|
+
* @property {import("pex-math").TypedArray | number[] | import("pex-math").Vec3[]} normals
|
|
17
|
+
* @property {import("pex-math").TypedArray | number[] | import("pex-math").Vec3[]} binormals
|
|
18
|
+
* @property {import("pex-math").TypedArray | number[] | import("pex-math").Vec2[]} [uvs]
|
|
19
|
+
* @property {Uint8Array | Uint16Array | Uint32Array | Array | import("pex-math").Vec3[]} [cells]
|
|
25
20
|
*/
|
|
26
21
|
|
|
27
22
|
/**
|
|
28
23
|
* @typedef {object} Options Options for frames computation. All optional.
|
|
29
|
-
* @property {boolean} [closed=false] Specify
|
|
30
|
-
* @property {
|
|
24
|
+
* @property {boolean} [closed=false] Specify if the path is closed.
|
|
25
|
+
* @property {"bishop" | "frenet"} [mode="bishop"] Frame type:
|
|
26
|
+
*
|
|
27
|
+
* - "bishop": rotation minimizing (parallel transport) frames, computed with the
|
|
28
|
+
* double reflection method. Twist free, suited for sweeping.
|
|
29
|
+
* - "frenet": normal pointing towards the curvature centre. Flips at inflection
|
|
30
|
+
* points and is undefined on straight parts, where the nearest frame is
|
|
31
|
+
* transported instead.
|
|
32
|
+
*
|
|
33
|
+
* @property {import("pex-math").Vec3} [initialNormal=null] Specify a starting
|
|
34
|
+
* normal for "bishop" frames, made orthogonal to the first tangent. Defaults
|
|
35
|
+
* to a coordinate axis not aligned with the first tangent, also used when the
|
|
36
|
+
* initial normal is parallel to it.
|
|
37
|
+
* @property {import("pex-math").Vec3} [finalNormal=null] Specify an ending
|
|
38
|
+
* normal for "bishop" frames of open paths, made orthogonal to the last
|
|
39
|
+
* tangent. Reached with the smallest rotation, spread along the arc length.
|
|
40
|
+
* Ignored when parallel to the last tangent.
|
|
41
|
+
* @property {number} [twist=0] Additional rotation of "bishop" frames around
|
|
42
|
+
* the tangent, in radians, spread along the arc length from the first frame
|
|
43
|
+
* to the last. Closed paths and paths with a final normal need a multiple of
|
|
44
|
+
* 2π to keep their end frame, or of π when the sign of the normal doesn't
|
|
45
|
+
* matter (eg. flat ribbons).
|
|
31
46
|
*/
|
|
32
47
|
|
|
33
48
|
export {};
|
package/.nojekyll
DELETED
|
File without changes
|