mdx-m3-viewer
Version:
A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.
231 lines (194 loc) • 6.25 kB
text/typescript
import { vec3, vec4, quat, mat4 } from 'gl-matrix';
export const VEC3_UNIT_X = vec3.fromValues(1, 0, 0);
export const VEC3_UNIT_Y = vec3.fromValues(0, 1, 0);
export const VEC3_UNIT_Z = vec3.fromValues(0, 0, 1);
export const VEC3_ZERO = vec3.create();
export const VEC3_ONE = vec3.fromValues(1, 1, 1);
export const QUAT_ZERO = quat.fromValues(0, 0, 0, 0);
export const QUAT_DEFAULT = quat.create();
const vec4Heap = vec4.create();
export function unproject(out: vec3, v: vec3, inverseMatrix: mat4, viewport: vec4) {
let x = 2 * (v[0] - viewport[0]) / viewport[2] - 1;
let y = 1 - 2 * (v[1] - viewport[1]) / viewport[3];
let z = 2 * v[2] - 1;
vec4.set(vec4Heap, x, y, z, 1);
vec4.transformMat4(vec4Heap, vec4Heap, inverseMatrix);
vec3.set(out, vec4Heap[0] / vec4Heap[3], vec4Heap[1] / vec4Heap[3], vec4Heap[2] / vec4Heap[3]);
return out;
}
/**
* Get the distance of a point from a plane.
*
* dot(plane, vec4(point, 1))
*/
export function distanceToPlane(plane: vec4, point: vec3) {
return plane[0] * point[0] + plane[1] * point[1] + plane[2] * point[2] + plane[3];
}
/**
* Get the distance of a point from a plane.
*
* dot(plane, vec4(x, y, 0, 1))
*/
export function distanceToPlane2(plane: vec4, x: number, y: number) {
return plane[0] * x + plane[1] * y + plane[3];
}
/**
* Get the distance of a point from a plane.
*
* dot(plane, vec4(x, y, z, 1))
*/
export function distanceToPlane3(plane: vec4, x: number, y: number, z: number) {
return plane[0] * x + plane[1] * y + plane[2] * z + plane[3];
}
/**
* Test it a sphere with the given center and radius intersects the given planes.
* If it doesn't, the index of the first plane that proved this is returned.
* Otherwise returns -1.
*
* If first is given, the test will begin from the plane at that index.
*/
export function testSphere(planes: vec4[], x: number, y: number, z: number, r: number, first: number) {
if (first === -1) {
first = 0;
}
for (let i = 0; i < 6; i++) {
let index = (first + i) % 6;
if (distanceToPlane3(planes[index], x, y, z) <= -r) {
return index;
}
}
return -1;
}
/**
* Test if a cell with the given coordinates intersects the given planes.
* If it doesn't, the index of the first plane that proved this is returned.
* Otherwise returns -1.
*
* If first is given, the test will begin from the plane at that index.
*/
export function testCell(planes: vec4[], left: number, right: number, bottom: number, top: number, first: number) {
if (first === -1) {
first = 0;
}
for (let i = 0; i < 6; i++) {
let index = (first + i) % 6;
let plane = planes[index];
if (distanceToPlane2(plane, left, bottom) < 0 &&
distanceToPlane2(plane, left, top) < 0 &&
distanceToPlane2(plane, right, top) < 0 &&
distanceToPlane2(plane, right, bottom) < 0) {
return index;
}
}
return -1;
}
export function planeLength(plane: vec4) {
return Math.hypot(plane[0], plane[1], plane[2]);
}
/**
* Normalize a plane.
*
* Note that this is not the same as normalizing a vec4.
*/
export function normalizePlane(out: vec4, plane: vec4) {
let len = planeLength(plane);
out[0] = plane[0] / len;
out[1] = plane[1] / len;
out[2] = plane[2] / len;
out[3] = plane[3] / len;
}
/**
* Unpacks a matrix's planes.
*/
export function unpackPlanes(planes: vec4[], m: mat4) {
// eslint-disable-next-line one-var
let a00 = m[0], a01 = m[4], a02 = m[8], a03 = m[12],
a10 = m[1], a11 = m[5], a12 = m[9], a13 = m[13],
a20 = m[2], a21 = m[6], a22 = m[10], a23 = m[14],
a30 = m[3], a31 = m[7], a32 = m[11], a33 = m[15];
let plane;
// Left clipping plane
plane = planes[0];
plane[0] = a30 + a00;
plane[1] = a31 + a01;
plane[2] = a32 + a02;
plane[3] = a33 + a03;
// Right clipping plane
plane = planes[1];
plane[0] = a30 - a00;
plane[1] = a31 - a01;
plane[2] = a32 - a02;
plane[3] = a33 - a03;
// Top clipping plane
plane = planes[2];
plane[0] = a30 - a10;
plane[1] = a31 - a11;
plane[2] = a32 - a12;
plane[3] = a33 - a13;
// Bottom clipping plane
plane = planes[3];
plane[0] = a30 + a10;
plane[1] = a31 + a11;
plane[2] = a32 + a12;
plane[3] = a33 + a13;
// Near clipping plane
plane = planes[4];
plane[0] = a30 + a20;
plane[1] = a31 + a21;
plane[2] = a32 + a22;
plane[3] = a33 + a23;
// Far clipping plane
plane = planes[5];
plane[0] = a30 - a20;
plane[1] = a31 - a21;
plane[2] = a32 - a22;
plane[3] = a33 - a23;
normalizePlane(planes[0], planes[0]);
normalizePlane(planes[1], planes[1]);
normalizePlane(planes[2], planes[2]);
normalizePlane(planes[3], planes[3]);
normalizePlane(planes[4], planes[4]);
normalizePlane(planes[5], planes[5]);
}
const F = vec3.create();
const R = vec3.create();
const U = vec3.create();
/**
* A look-at matrix, but for quaternions.
*
* See https://stackoverflow.com/a/52551983/2503048
*/
export function quatLookAt(out: quat, from: vec3, to: vec3, worldUp: vec3) {
vec3.normalize(F, vec3.sub(F, to, from));
vec3.normalize(R, vec3.cross(R, worldUp, F));
vec3.cross(U, R, F);
let trace = R[0] + U[2] + F[1];
if (trace > 0.0) {
let s = 0.5 / Math.sqrt(trace + 1.0);
out[3] = 0.25 / s;
out[0] = (U[1] - F[2]) * s;
out[2] = (F[0] - R[1]) * s;
out[1] = (R[2] - U[0]) * s;
} else {
if (R[0] > U[2] && R[0] > F[1]) {
let s = 2.0 * Math.sqrt(1.0 + R[0] - U[2] - F[1]);
out[3] = (U[1] - F[2]) / s;
out[0] = 0.25 * s;
out[2] = (U[0] + R[2]) / s;
out[1] = (F[0] + R[1]) / s;
} else if (U[2] > F[1]) {
let s = 2.0 * Math.sqrt(1.0 + U[2] - R[0] - F[1]);
out[3] = (F[0] - R[1]) / s;
out[0] = (U[0] + R[2]) / s;
out[2] = 0.25 * s;
out[1] = (F[2] + U[1]) / s;
} else {
let s = 2.0 * Math.sqrt(1.0 + F[1] - R[0] - U[2]);
out[3] = (R[2] - U[0]) / s;
out[0] = (F[0] + R[1]) / s;
out[2] = (F[2] + U[1]) / s;
out[1] = 0.25 * s;
}
}
return out;
}