mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 13:30:24 +08:00
add frenet-frames helper
This commit is contained in:
106
src/mol-math/linear-algebra/3d/frenet-frames.ts
Normal file
106
src/mol-math/linear-algebra/3d/frenet-frames.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2026 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec3 } from './vec3';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3fromArray = Vec3.fromArray;
|
||||
const v3sub = Vec3.sub;
|
||||
const v3normalize = Vec3.normalize;
|
||||
const v3isZero = Vec3.isZero;
|
||||
const v3set = Vec3.set;
|
||||
const v3cross = Vec3.cross;
|
||||
const v3toArray = Vec3.toArray;
|
||||
const v3copy = Vec3.copy;
|
||||
const v3magnitude = Vec3.magnitude;
|
||||
const v3rotateAroundAxis = Vec3.rotateAroundAxis;
|
||||
const v3dot = Vec3.dot;
|
||||
|
||||
/**
|
||||
* Compute normal and binormal vectors along a curve using parallel transport
|
||||
*/
|
||||
export function computeFrenetFrames(curvePoints: Float32Array, normalVectors: Float32Array, binormalVectors: Float32Array, n: number) {
|
||||
const tangent = Vec3();
|
||||
const prevTangent = Vec3();
|
||||
const normal = Vec3();
|
||||
const binormal = Vec3();
|
||||
const p0 = Vec3();
|
||||
const p1 = Vec3();
|
||||
|
||||
// Compute initial tangent
|
||||
v3fromArray(p0, curvePoints, 0);
|
||||
v3fromArray(p1, curvePoints, 3);
|
||||
v3sub(tangent, p1, p0);
|
||||
v3normalize(tangent, tangent);
|
||||
if (v3isZero(tangent)) {
|
||||
v3set(tangent, 1, 0, 0);
|
||||
}
|
||||
|
||||
// Find initial normal (perpendicular to tangent)
|
||||
// Use the smallest component of tangent to find a perpendicular vector
|
||||
const absX = Math.abs(tangent[0]);
|
||||
const absY = Math.abs(tangent[1]);
|
||||
const absZ = Math.abs(tangent[2]);
|
||||
|
||||
if (absX <= absY && absX <= absZ) {
|
||||
v3set(normal, 1, 0, 0);
|
||||
} else if (absY <= absZ) {
|
||||
v3set(normal, 0, 1, 0);
|
||||
} else {
|
||||
v3set(normal, 0, 0, 1);
|
||||
}
|
||||
|
||||
// Orthogonalize normal against tangent
|
||||
v3cross(binormal, tangent, normal);
|
||||
v3normalize(binormal, binormal);
|
||||
v3cross(normal, binormal, tangent);
|
||||
v3normalize(normal, normal);
|
||||
|
||||
// Store first frame
|
||||
v3toArray(normal, normalVectors, 0);
|
||||
v3toArray(binormal, binormalVectors, 0);
|
||||
|
||||
// Propagate frames along the curve using parallel transport
|
||||
v3copy(prevTangent, tangent);
|
||||
for (let i = 1; i < n; ++i) {
|
||||
// Compute tangent at this point
|
||||
if (i < n - 1) {
|
||||
v3fromArray(p0, curvePoints, (i - 1) * 3);
|
||||
v3fromArray(p1, curvePoints, (i + 1) * 3);
|
||||
v3sub(tangent, p1, p0);
|
||||
} else {
|
||||
v3fromArray(p0, curvePoints, (i - 1) * 3);
|
||||
v3fromArray(p1, curvePoints, i * 3);
|
||||
v3sub(tangent, p1, p0);
|
||||
}
|
||||
v3normalize(tangent, tangent);
|
||||
|
||||
// Parallel transport: rotate the previous frame
|
||||
const dot = v3dot(prevTangent, tangent);
|
||||
if (dot < 0.9999) {
|
||||
const axis = Vec3();
|
||||
v3cross(axis, prevTangent, tangent);
|
||||
if (v3magnitude(axis) > 0.0001) {
|
||||
v3normalize(axis, axis);
|
||||
const angle = Math.acos(Math.min(1, Math.max(-1, dot)));
|
||||
|
||||
// Rotate normal and binormal around axis by angle
|
||||
v3rotateAroundAxis(normal, normal, axis, angle);
|
||||
v3rotateAroundAxis(binormal, binormal, axis, angle);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure orthogonality
|
||||
v3cross(binormal, tangent, normal);
|
||||
v3normalize(binormal, binormal);
|
||||
v3cross(normal, binormal, tangent);
|
||||
v3normalize(normal, normal);
|
||||
|
||||
v3toArray(normal, normalVectors, i * 3);
|
||||
v3toArray(binormal, binormalVectors, i * 3);
|
||||
v3copy(prevTangent, tangent);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2026 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -593,6 +593,18 @@ export namespace Vec3 {
|
||||
return Mat4.fromRotation(mat, by, axis);
|
||||
}
|
||||
|
||||
const tmpCrossAxis = Vec3();
|
||||
export function rotateAroundAxis(out: Vec3, v: Vec3, axis: Vec3, angle: number) {
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
const dot = Vec3.dot(v, axis);
|
||||
Vec3.cross(tmpCrossAxis, axis, v);
|
||||
out[0] = v[0] * cos + tmpCrossAxis[0] * sin + axis[0] * dot * (1 - cos);
|
||||
out[1] = v[1] * cos + tmpCrossAxis[1] * sin + axis[1] * dot * (1 - cos);
|
||||
out[2] = v[2] * cos + tmpCrossAxis[2] * sin + axis[2] * dot * (1 - cos);
|
||||
return out;
|
||||
}
|
||||
|
||||
export function isZero(v: Vec3) {
|
||||
return v[0] === 0 && v[1] === 0 && v[2] === 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user