-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathindex.js
executable file
·149 lines (122 loc) · 4.28 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { avec3, vec3, quat, utils } from "pex-math";
import computePathTangents from "path-tangents";
const X_UP = [1, 0, 0];
const Y_UP = [0, 1, 0];
const Z_UP = [0, 0, 1];
/**
* Compute Frenet-Serret frames for a geometry of 3D positions and optionally provided tangents.
*
* @param {import("./types.js").SimplicialComplex} geometry
* @param {import("./types.js").Options} [options={}]
* @returns {import("./types.js").SimplicialComplexWithTNB}
*
* @see [Frenet–Serret formulas]{@link https://en.wikipedia.org/wiki/Frenet%E2%80%93Serret_formulas}
*/
function frenetSerretFrames(geometry, options) {
const isFlatArray = !geometry.positions[0]?.length;
// Extends options
const { closed = false, initialNormal = null } = { ...options };
const size = geometry.positions.length / (isFlatArray ? 3 : 1);
geometry.tangents ||= computePathTangents(geometry.positions, closed);
geometry.normals ||= isFlatArray ? new Float32Array(size * 3) : [];
geometry.binormals ||= isFlatArray ? new Float32Array(size * 3) : [];
let v = vec3.create();
// Compute initial frame
let tangent = isFlatArray
? geometry.tangents.slice(0, 3)
: geometry.tangents[0];
let normal;
if (initialNormal) {
normal = [...initialNormal];
} else {
const atx = Math.abs(tangent[0]);
const aty = Math.abs(tangent[1]);
const atz = Math.abs(tangent[2]);
if (aty > atx && aty >= atz) {
v = vec3.cross([...tangent], X_UP);
} else if (atz > atx && atz >= aty) {
v = vec3.cross([...tangent], Y_UP);
} else {
v = vec3.cross([...tangent], Z_UP);
}
normal = vec3.normalize(vec3.cross([...tangent], v));
}
let binormal = vec3.normalize(vec3.cross([...tangent], normal));
if (isFlatArray) {
geometry.normals.set(normal);
geometry.binormals.set(binormal);
} else {
geometry.normals.push(normal);
geometry.binormals.push(binormal);
}
// Compute the rest of the frames
const rotation = quat.create();
let previousTangent = vec3.create();
for (let i = 1; i < size; i++) {
if (isFlatArray) {
avec3.set(previousTangent, 0, geometry.tangents, i - 1);
avec3.set(tangent, 0, geometry.tangents, i);
} else {
previousTangent = geometry.tangents[i - 1];
tangent = geometry.tangents[i];
}
normal = vec3.copy(normal);
binormal = vec3.copy(binormal);
v = vec3.cross([...previousTangent], tangent);
if (vec3.length(v) > Number.EPSILON) {
vec3.normalize(v);
const theta = Math.acos(
utils.clamp(vec3.dot(previousTangent, tangent), -1, 1),
);
quat.fromAxisAngle(rotation, v, theta);
vec3.multQuat(normal, rotation);
}
binormal = vec3.cross([...tangent], normal);
if (isFlatArray) {
avec3.set(geometry.normals, i, normal, 0);
avec3.set(geometry.binormals, i, binormal, 0);
} else {
geometry.normals.push(normal);
geometry.binormals.push(binormal);
}
}
if (closed) {
const firstNormal = isFlatArray
? geometry.normals.slice(0, 3)
: geometry.normals[0];
const lastNormal = isFlatArray
? geometry.normals.slice(-3)
: geometry.normals.at(-1);
const firstTangent = isFlatArray
? geometry.tangents.slice(0, 3)
: geometry.tangents[0];
let theta = Math.acos(
utils.clamp(vec3.dot(firstNormal, lastNormal), -1, 1),
);
theta /= size - 1;
if (vec3.dot(firstTangent, vec3.cross([...firstNormal], lastNormal)) > 0) {
theta = -theta;
}
for (let i = 0; i < size; i++) {
if (isFlatArray) {
avec3.set(tangent, 0, geometry.tangents, i);
avec3.set(normal, 0, geometry.normals, i);
avec3.set(binormal, 0, geometry.binormals, i);
quat.fromAxisAngle(rotation, tangent, theta * i);
vec3.multQuat(normal, rotation);
binormal = vec3.cross([...tangent], normal);
avec3.set(geometry.normals, i, normal, 0);
avec3.set(geometry.binormals, i, binormal, 0);
} else {
const tangent = geometry.tangents[i];
const normal = geometry.normals[i];
quat.fromAxisAngle(rotation, tangent, theta * i);
vec3.multQuat(normal, rotation);
geometry.binormals[i] = vec3.cross([...tangent], normal);
}
}
}
return geometry;
}
export default frenetSerretFrames;
export * from "./types.js";