@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
192 lines (130 loc) • 5.15 kB
JavaScript
import { Matrix4, Vector3 } from "three";
import { max2 } from "../../../../../../core/math/max2.js";
import { min2 } from "../../../../../../core/math/min2.js";
import { clamp } from "../../../../../../core/math/clamp.js";
import { vec3 } from "gl-matrix";
import { array_copy } from "../../../../../../core/collection/array/array_copy.js";
/**
* Compute a normal vector for Frenet frame based on a tangent (direction in which vertex points)
* @param {Vector3} result
* @param {Vector3} tangent
*/
function computeNormalHintFromTangent(result, tangent) {
let min = Number.MAX_VALUE;
const tx = Math.abs(tangent.x);
const ty = Math.abs(tangent.y);
const tz = Math.abs(tangent.z);
if (ty <= min) {
min = ty;
result.set(0, 1, 0);
}
if (tz < min) {
min = tz;
result.set(0, 0, 1);
}
if (tx < min) {
min = tx;
result.set(1, 0, 0);
}
}
/**
* @see https://github.com/mrdoob/three.js/blob/c12c9a166a1369cdd58622fff2aff7e3a84305d7/src/extras/core/Curve.js#L260
* @param {Float32Array|number[]} points
* @param {boolean} [closed]
* @param {Vector3} [normal_hint]
* @returns {{normals:Vector3[], binormals:Vector3[], tangents:Vector3[]}}
*/
export function computeFrenetFrames(points, closed = false, normal_hint) {
// see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
const normal = new Vector3();
const tangents = [];
const normals = [];
const binormals = [];
const vec = new Vector3();
const mat = new Matrix4();
const point_count = points.length / 3;
const segments = point_count - 1;
const v3_0 = [];
const v3_1 = [];
const p0_a = [];
const pR_a = [];
const p1_a = [];
// compute the tangent vectors for each segment on the curve
for (let i = 0; i <= segments; i++) {
// get points on either side
const i0 = max2(i - 1, 0);
const i1 = min2(i + 1, segments);
array_copy(points, i0 * 3, p0_a, 0, 3);
array_copy(points, i * 3, pR_a, 0, 3);
array_copy(points, i1 * 3, p1_a, 0, 3);
// get points along the lines close to the reference
vec3.sub(v3_0, p0_a, pR_a);
vec3.normalize(v3_0, v3_0);
vec3.sub(v3_1, p1_a, pR_a);
vec3.normalize(v3_1, v3_1);
vec3.sub(v3_1, v3_1, v3_0);
vec3.normalize(v3_1, v3_1);
// write out
const tangent_x = v3_1[0];
const tangent_y = v3_1[1];
const tangent_z = v3_1[2];
if (tangent_x === 0 && tangent_y === 0 && tangent_z === 0) {
// no tangent, copy previous one
if (i > 0) {
tangents[i] = tangents[i - 1];
} else {
// very first tangent is undefined, set something arbitrary
// TODO take normal_hint into account
tangents[i] = new Vector3(0, 0, 1);
}
continue;
}
tangents[i] = new Vector3(
tangent_x,
tangent_y,
tangent_z
);
}
// select an initial normal vector perpendicular to the first tangent vector,
// and in the direction of the minimum tangent xyz component
normals[0] = new Vector3();
binormals[0] = new Vector3();
if (normal_hint !== undefined) {
normal.copy(normal_hint);
} else {
computeNormalHintFromTangent(normal, tangents[0]);
}
vec.crossVectors(tangents[0], normal).normalize();
normals[0].crossVectors(tangents[0], vec);
binormals[0].crossVectors(tangents[0], normals[0]);
// compute the slowly-varying normal and binormal vectors for each segment on the curve
for (let i = 1; i <= segments; i++) {
normals[i] = normals[i - 1].clone();
binormals[i] = binormals[i - 1].clone();
vec.crossVectors(tangents[i - 1], tangents[i]);
if (vec.length() > Number.EPSILON) {
vec.normalize();
const theta = Math.acos(clamp(tangents[i - 1].dot(tangents[i]), -1, 1)); // clamp for floating pt errors
normals[i].applyMatrix4(mat.makeRotationAxis(vec, theta));
}
binormals[i].crossVectors(tangents[i], normals[i]);
}
// if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
if (closed === true) {
let theta = Math.acos(clamp(normals[0].dot(normals[segments]), -1, 1));
theta /= segments;
if (tangents[0].dot(vec.crossVectors(normals[0], normals[segments])) > 0) {
theta = -theta;
}
for (let i = 1; i <= segments; i++) {
// twist a little...
normals[i].applyMatrix4(mat.makeRotationAxis(tangents[i], theta * i));
binormals[i].crossVectors(tangents[i], normals[i]);
}
}
return {
tangents: tangents,
normals: normals,
binormals: binormals
};
}