@threeify/math
Version:
Computer graphics oriented, High performance, tree-shakeable, TypeScript 3D vector math library.
280 lines (251 loc) • 7.41 kB
text/typescript
import {
delta,
EPSILON,
equalsTolerance,
parseSafeFloats,
toSafeString
} from './Functions.js';
import { Mat4 } from './Mat4.js';
import { Spherical } from './Spherical.js';
import { Vec3 } from './Vec3.js';
export function vec3Delta(a: Vec3, b: Vec3): number {
return delta(a.x, b.x) + delta(a.y, b.y) + delta(a.z, b.z);
}
export function vec3Equals(
a: Vec3,
b: Vec3,
tolerance: number = EPSILON
): boolean {
return (
equalsTolerance(a.x, b.x, tolerance) &&
equalsTolerance(a.y, b.y, tolerance) &&
equalsTolerance(a.z, b.z, tolerance)
);
}
export function vec3Add(a: Vec3, b: Vec3, result = new Vec3()): Vec3 {
return result.set(a.x + b.x, a.y + b.y, a.z + b.z);
}
export function vec3Subtract(a: Vec3, b: Vec3, result = new Vec3()): Vec3 {
return result.set(a.x - b.x, a.y - b.y, a.z - b.z);
}
export function vec3MultiplyByScalar(
a: Vec3,
b: number,
result = new Vec3()
): Vec3 {
return result.set(a.x * b, a.y * b, a.z * b);
}
export function vec3Negate(a: Vec3, result = new Vec3()): Vec3 {
return result.set(-a.x, -a.y, -a.z);
}
export function vec3Reciprocal(a: Vec3, result = new Vec3()): Vec3 {
return result.set(1 / a.x, 1 / a.y, 1 / a.z);
}
export function vec3LengthSq(a: Vec3): number {
return vec3Dot(a, a);
}
export function vec3Length(a: Vec3): number {
return Math.sqrt(vec3Dot(a, a));
}
export function vec3Distance(a: Vec3, b: Vec3): number {
// TODO: optimize this by breaking it apart
return vec3Length(vec3Subtract(a, b));
}
export function vec3DistanceSq(a: Vec3, b: Vec3): number {
// TODO: optimize this by breaking it apart
return vec3LengthSq(vec3Subtract(a, b));
}
export function vec3Min(a: Vec3, b: Vec3, result = new Vec3()): Vec3 {
return result.set(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.min(a.z, b.z));
}
export function vec3Max(a: Vec3, b: Vec3, result = new Vec3()): Vec3 {
return result.set(Math.max(a.x, b.x), Math.max(a.y, b.y), Math.max(a.z, b.z));
}
export function vec3MaxComponent(a: Vec3): number {
return Math.max(a.x, a.y, a.z);
}
export function vec3MinComponent(a: Vec3): number {
return Math.min(a.x, a.y, a.z);
}
export function vec3Ceil(a: Vec3, result = new Vec3()): Vec3 {
return result.set(Math.ceil(a.x), Math.ceil(a.y), Math.ceil(a.z));
}
export function vec3Floor(a: Vec3, result = new Vec3()): Vec3 {
return result.set(Math.floor(a.x), Math.floor(a.y), Math.floor(a.z));
}
export function vec3Clamp(
a: Vec3,
min: Vec3,
max: Vec3,
result = new Vec3()
): Vec3 {
return vec3Max(min, vec3Min(max, a, result), result);
}
export function vec3Normalize(a: Vec3, result = new Vec3()): Vec3 {
const length = vec3Length(a);
if (length === 0) {
return result.set(0, 0, 0);
}
return vec3MultiplyByScalar(a, 1 / length, result);
}
export function vec3Dot(a: Vec3, b: Vec3): number {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
export function vec3Cross(a: Vec3, b: Vec3, result = new Vec3()): Vec3 {
const ax = a.x;
const ay = a.y;
const az = a.z;
const bx = b.x;
const by = b.y;
const bz = b.z;
return result.set(ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx);
}
export function vec3Lerp(
a: Vec3,
b: Vec3,
t: number,
result = new Vec3()
): Vec3 {
const s = 1 - t;
return result.set(a.x * s + b.x * t, a.y * s + b.y * t, a.z * s + b.z * t);
}
export function arrayToVec3(
array: Float32Array | number[],
offset = 0,
result = new Vec3()
): Vec3 {
return result.set(array[offset + 0], array[offset + 1], array[offset + 2]);
}
export function vec3ToArray(
a: Vec3,
array: Float32Array | number[],
offset = 0
): void {
array[offset + 0] = a.x;
array[offset + 1] = a.y;
array[offset + 2] = a.z;
}
export function vec3ToString(a: Vec3): string {
return toSafeString([a.x, a.y, a.z]);
}
export function stringToVec3(text: string, result = new Vec3()): Vec3 {
return arrayToVec3(parseSafeFloats(text), 0, result);
}
export function crossFromCoplanarPoints(
a: Vec3,
b: Vec3,
c: Vec3,
result = new Vec3()
): Vec3 {
// TODO: replace with just number math, no classes? Or just use temporary Vec3 objects
vec3Subtract(c, b, result);
const v = vec3Subtract(a, b);
return vec3Cross(result, v, result);
}
export function sphericalToVec3(s: Spherical): Vec3 {
return sphericalCoordToVec3(s.radius, s.phi, s.theta);
}
export function sphericalCoordToVec3(
radius: number,
phi: number,
theta: number
): Vec3 {
const sinPhiRadius = Math.sin(phi) * radius;
return new Vec3(
sinPhiRadius * Math.sin(theta),
Math.cos(phi) * radius,
sinPhiRadius * Math.cos(theta)
);
}
// static/instance method to calculate barycentric coordinates
// based on: http://www.blackpawn.com/texts/pointinpoly/default.html
export function pointToBarycoords(
a: Vec3,
b: Vec3,
c: Vec3,
point: Vec3,
result = new Vec3()
): Vec3 {
const v0 = vec3Subtract(c, b);
const v1 = vec3Subtract(b, a);
const v2 = vec3Subtract(point, a);
const dot00 = vec3Dot(v0, v0);
const dot01 = vec3Dot(v0, v1);
const dot02 = vec3Dot(v0, v2);
const dot11 = vec3Dot(v1, v1);
const dot12 = vec3Dot(v1, v2);
const denom = dot00 * dot11 - dot01 * dot01;
// collinear or singular triangle
if (denom === 0) {
// arbitrary location outside of triangle?
// not sure if this is the best idea, maybe should be returning undefined
return result.set(-2, -1, -1);
}
const invDenom = 1 / denom;
const u = (dot11 * dot02 - dot01 * dot12) * invDenom;
const v = (dot00 * dot12 - dot01 * dot02) * invDenom;
// barycentric coordinates must always sum to 1
return result.set(1 - u - v, v, u);
}
// replace with just a linear weight function
export function barycoordWeightsToVec3(
baryCoord: Vec3,
a: Vec3,
b: Vec3,
c: Vec3,
result = new Vec3()
): Vec3 {
const v = baryCoord;
return result.set(
a.x * v.x + b.x * v.y + c.x * v.z,
a.y * v.x + b.y * v.y + c.y * v.z,
a.z * v.x + b.z * v.y + c.z * v.z
);
}
export function mat4TransformPosition3(
m: Mat4,
v: Vec3,
result = new Vec3()
): Vec3 {
const { x, y, z } = v;
const e = m.elements;
const w = 1 / (e[3] * x + e[7] * y + e[11] * z + e[15]);
result.x = (e[0] * x + e[4] * y + e[8] * z + e[12]) * w;
result.y = (e[1] * x + e[5] * y + e[9] * z + e[13]) * w;
result.z = (e[2] * x + e[6] * y + e[10] * z + e[14]) * w;
return result;
}
export function mat4TransformNormal3(
m: Mat4,
v: Vec3,
result = new Vec3()
): Vec3 {
const { x, y, z } = v;
const e = m.elements;
result.x = e[0] * x + e[4] * y + e[8] * z;
result.y = e[1] * x + e[5] * y + e[9] * z;
result.z = e[2] * x + e[6] * y + e[10] * z;
return vec3Normalize(result, result);
}
export function vec3Reflect(
incident: Vec3,
normal: Vec3,
result = new Vec3()
): Vec3 {
return vec3Subtract(
incident,
vec3MultiplyByScalar(vec3Project(incident, normal, result), 2, result),
result
);
}
// project vector onto another vector
export function vec3Project(a: Vec3, target: Vec3, result = new Vec3()): Vec3 {
const dot = vec3Dot(a, target);
return vec3MultiplyByScalar(target, dot / vec3Dot(target, target), result);
}
// determine angle between two vectors
export function vec3Angle(a: Vec3, b: Vec3): number {
const theta = vec3Dot(a, b) / (vec3Length(a) * vec3Length(b));
// clamp, to handle numerical problems
return Math.acos(Math.min(Math.max(theta, -1), 1));
}