molstar
Version:
A comprehensive macromolecular library.
97 lines (96 loc) • 3.43 kB
JavaScript
/**
* 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.js';
// 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, normalVectors, binormalVectors, n) {
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);
}
}