mdx-m3-viewer
Version:
A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.
268 lines (233 loc) • 5.93 kB
JavaScript
import {vec3, vec4, quat} from 'gl-matrix';
let VEC3_UNIT_X = vec3.fromValues(1, 0, 0);
let VEC3_UNIT_Y = vec3.fromValues(0, 1, 0);
let VEC3_UNIT_Z = vec3.fromValues(0, 0, 1);
let VEC3_ZERO = vec3.create();
let VEC3_ONE = vec3.fromValues(1, 1, 1);
let QUAT_ZERO = quat.fromValues(0, 0, 0, 0);
let QUAT_DEFAULT = quat.create();
let heap = vec4.create();
/**
* @param {vec3} out
* @param {vec3} v
* @param {mat4} inverseMatrix
* @param {vec4} viewport
* @return {vec3}
*/
function unproject(out, v, inverseMatrix, viewport) {
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(heap, x, y, z, 1);
vec4.transformMat4(heap, heap, inverseMatrix);
vec3.set(out, heap[0] / heap[3], heap[1] / heap[3], heap[2] / heap[3]);
return out;
}
/**
* Get the distance of a point from a plane.
* dot(plane, vec4(point, 1))
*
* @param {vec4} plane
* @param {vec3} point
* @return {number}
*/
function distanceToPlane(plane, point) {
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(point, 0, 1))
*
* @param {vec4} plane
* @param {number} px
* @param {number} py
* @return {number}
*/
function distanceToPlane2(plane, px, py) {
return plane[0] * px + plane[1] * py + plane[3];
}
/**
* Get the distance of a point from a plane.
* dot(plane, vec4(point, 1))
*
* @param {vec4} plane
* @param {number} px
* @param {number} py
* @param {number} pz
* @return {number}
*/
function distanceToPlane3(plane, px, py, pz) {
return plane[0] * px + plane[1] * py + plane[2] * pz + 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.
*
* @param {Array<vec4>} planes
* @param {number} x
* @param {number} y
* @param {number} z
* @param {number} r
* @param {number} first
* @return {boolean}
*/
function testSphere(planes, x, y, z, r, first) {
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.
*
* @param {Array<vec4>} planes
* @param {number} left
* @param {number} right
* @param {number} bottom
* @param {number} top
* @param {number} first
* @return {boolean}
*/
function testCell(planes, left, right, bottom, top, first) {
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;
}
/**
* Normalize a plane. Note that this is not the same as normalizing a vec4.
*
* @param {vec4} out
* @param {vec4} plane
*/
function normalizePlane(out, plane) {
let len = vec3.len(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.
*
* @param {Array<vec4>} planes
* @param {mat4} m
*/
function unpackPlanes(planes, m) {
// 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]);
}
let vec3Heap = vec3.create();
/**
* @param {quat} q
* @return {number}
*/
function getRotationX(q) {
vec3.transformQuat(vec3Heap, VEC3_UNIT_Y, q);
return Math.atan2(vec3Heap[2], vec3Heap[1]);
}
/**
* @param {quat} q
* @return {number}
*/
function getRotationY(q) {
vec3.transformQuat(vec3Heap, VEC3_UNIT_Z, q);
return Math.atan2(vec3Heap[0], vec3Heap[2]);
}
/**
* @param {quat} q
* @return {number}
*/
function getRotationZ(q) {
vec3.transformQuat(vec3Heap, VEC3_UNIT_X, q);
return Math.atan2(vec3Heap[1], vec3Heap[0]);
}
export {
VEC3_UNIT_X,
VEC3_UNIT_Y,
VEC3_UNIT_Z,
VEC3_ZERO,
VEC3_ONE,
QUAT_ZERO,
QUAT_DEFAULT,
unproject,
distanceToPlane,
distanceToPlane2,
distanceToPlane3,
testSphere,
testCell,
normalizePlane,
unpackPlanes,
getRotationX,
getRotationY,
getRotationZ,
};