@xeokit/xeokit-viewer
Version:
BIM viewer built on xeokit
1,882 lines (1,720 loc) • 2.17 MB
JavaScript
/**
* @private
*/
function xmlToJson(node, attributeRenamer) {
if (node.nodeType === node.TEXT_NODE) {
var v = node.nodeValue;
if (v.match(/^\s+$/) === null) {
return v;
}
} else if (node.nodeType === node.ELEMENT_NODE ||
node.nodeType === node.DOCUMENT_NODE) {
var json = {type: node.nodeName, children: []};
if (node.nodeType === node.ELEMENT_NODE) {
for (var j = 0; j < node.attributes.length; j++) {
var attribute = node.attributes[j];
var nm = attributeRenamer[attribute.nodeName] || attribute.nodeName;
json[nm] = attribute.nodeValue;
}
}
for (var i = 0; i < node.childNodes.length; i++) {
var item = node.childNodes[i];
var j = xmlToJson(item, attributeRenamer);
if (j) json.children.push(j);
}
return json;
}
}
/**
* @private
*/
function clone(ob) {
return JSON.parse(JSON.stringify(ob));
}
/**
* @private
*/
var guidChars = [["0", 10], ["A", 26], ["a", 26], ["_", 1], ["$", 1]].map(function (a) {
var li = [];
var st = a[0].charCodeAt(0);
var en = st + a[1];
for (var i = st; i < en; ++i) {
li.push(i);
}
return String.fromCharCode.apply(null, li);
}).join("");
/**
* @private
*/
function b64(v, len) {
var r = (!len || len === 4) ? [0, 6, 12, 18] : [0, 6];
return r.map(function (i) {
return guidChars.substr(parseInt(v / (1 << i)) % 64, 1)
}).reverse().join("");
}
/**
* @private
*/
function compressGuid(g) {
var bs = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30].map(function (i) {
return parseInt(g.substr(i, 2), 16);
});
return b64(bs[0], 2) + [1, 4, 7, 10, 13].map(function (i) {
return b64((bs[i] << 16) + (bs[i + 1] << 8) + bs[i + 2]);
}).join("");
}
/**
* @private
*/
function findNodeOfType(m, t) {
var li = [];
var _ = function (n) {
if (n.type === t) li.push(n);
(n.children || []).forEach(function (c) {
_(c);
});
};
_(m);
return li;
}
/**
* @private
*/
function timeout(dt) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, dt);
});
}
/**
* @private
*/
function httpRequest(args) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(args.method || "GET", args.url, true);
xhr.onload = function (e) {
console.log(args.url, xhr.readyState, xhr.status);
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseXML);
} else {
reject(xhr.statusText);
}
}
};
xhr.send(null);
});
}
/**
* @private
*/
const queryString = function () {
// This function is anonymous, is executed immediately and
// the return value is assigned to QueryString!
var query_string = {};
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
// If first entry with this name
if (typeof query_string[pair[0]] === "undefined") {
query_string[pair[0]] = decodeURIComponent(pair[1]);
// If second entry with this name
} else if (typeof query_string[pair[0]] === "string") {
var arr = [query_string[pair[0]], decodeURIComponent(pair[1])];
query_string[pair[0]] = arr;
// If third or later entry with this name
} else {
query_string[pair[0]].push(decodeURIComponent(pair[1]));
}
}
return query_string;
}();
/**
* @private
*/
function loadJSON(url, ok, err) {
// Avoid checking ok and err on each use.
var defaultCallback = (_value) => undefined;
ok = ok || defaultCallback;
err = err || defaultCallback;
var request = new XMLHttpRequest();
request.overrideMimeType("application/json");
request.open('GET', url, true);
request.addEventListener('load', function (event) {
var response = event.target.response;
if (this.status === 200) {
var json;
try {
json = JSON.parse(response);
} catch (e) {
err(`utils.loadJSON(): Failed to parse JSON response - ${e}`);
}
ok(json);
} else if (this.status === 0) {
// Some browsers return HTTP Status 0 when using non-http protocol
// e.g. 'file://' or 'data://'. Handle as success.
console.warn('loadFile: HTTP Status 0 received.');
try {
ok(JSON.parse(response));
} catch (e) {
err(`utils.loadJSON(): Failed to parse JSON response - ${e}`);
}
} else {
err(event);
}
}, false);
request.addEventListener('error', function (event) {
err(event);
}, false);
request.send(null);
}
/**
* @private
*/
function loadArraybuffer(url, ok, err) {
// Check for data: URI
var defaultCallback = (_value) => undefined;
ok = ok || defaultCallback;
err = err || defaultCallback;
const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
const dataUriRegexResult = url.match(dataUriRegex);
if (dataUriRegexResult) { // Safari can't handle data URIs through XMLHttpRequest
const isBase64 = !!dataUriRegexResult[2];
var data = dataUriRegexResult[3];
data = window.decodeURIComponent(data);
if (isBase64) {
data = window.atob(data);
}
try {
const buffer = new ArrayBuffer(data.length);
const view = new Uint8Array(buffer);
for (var i = 0; i < data.length; i++) {
view[i] = data.charCodeAt(i);
}
window.setTimeout(function () {
ok(buffer);
}, 0);
} catch (error) {
window.setTimeout(function () {
err(error);
}, 0);
}
} else {
const request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
ok(request.response);
} else {
err('loadArrayBuffer error : ' + request.response);
}
}
};
request.send(null);
}
}
/**
Tests if the given object is an array
@private
*/
function isArray(value) {
return value && !(value.propertyIsEnumerable('length')) && typeof value === 'object' && typeof value.length === 'number';
}
/**
Tests if the given value is a string
@param value
@returns {boolean}
@private
*/
function isString(value) {
return (typeof value === 'string' || value instanceof String);
}
/**
Tests if the given value is a number
@param value
@returns {boolean}
@private
*/
function isNumeric(value) {
return !isNaN(parseFloat(value)) && isFinite(value);
}
/**
Tests if the given value is an ID
@param value
@returns {boolean}
@private
*/
function isID(value) {
return utils.isString(value) || utils.isNumeric(value);
}
/**
Tests if the given components are the same, where the components can be either IDs or instances.
@param c1
@param c2
@returns {boolean}
@private
*/
function isSameComponent(c1, c2) {
if (!c1 || !c2) {
return false;
}
const id1 = (utils.isNumeric(c1) || utils.isString(c1)) ? `${c1}` : c1.id;
const id2 = (utils.isNumeric(c2) || utils.isString(c2)) ? `${c2}` : c2.id;
return id1 === id2;
}
/**
Tests if the given value is a function
@param value
@returns {boolean}
@private
*/
function isFunction(value) {
return (typeof value === "function");
}
/**
Tests if the given value is a JavaScript JSON object, eg, ````{ foo: "bar" }````.
@param value
@returns {boolean}
@private
*/
function isObject(value) {
const objectConstructor = {}.constructor;
return (!!value && value.constructor === objectConstructor);
}
/** Returns a shallow copy
*/
function copy(o) {
return utils.apply(o, {});
}
/** Add properties of o to o2, overwriting them on o2 if already there
*/
function apply(o, o2) {
for (const name in o) {
if (o.hasOwnProperty(name)) {
o2[name] = o[name];
}
}
return o2;
}
/**
Add non-null/defined properties of o to o2
@private
*/
function apply2(o, o2) {
for (const name in o) {
if (o.hasOwnProperty(name)) {
if (o[name] !== undefined && o[name] !== null) {
o2[name] = o[name];
}
}
}
return o2;
}
/**
Add properties of o to o2 where undefined or null on o2
@private
*/
function applyIf(o, o2) {
for (const name in o) {
if (o.hasOwnProperty(name)) {
if (o2[name] === undefined || o2[name] === null) {
o2[name] = o[name];
}
}
}
return o2;
}
/**
Returns true if the given map is empty.
@param obj
@returns {boolean}
@private
*/
function isEmptyObject(obj) {
for (const name in obj) {
if (obj.hasOwnProperty(name)) {
return false;
}
}
return true;
}
/**
Returns the given ID as a string, in quotes if the ID was a string to begin with.
This is useful for logging IDs.
@param {Number| String} id The ID
@returns {String}
@private
*/
function inQuotes(id) {
return utils.isNumeric(id) ? (`${id}`) : (`'${id}'`);
}
/**
Returns the concatenation of two typed arrays.
@param a
@param b
@returns {*|a}
@private
*/
function concat(a, b) {
const c = new a.constructor(a.length + b.length);
c.set(a);
c.set(b, a.length);
return c;
}
function flattenParentChildHierarchy(root) {
var list = [];
function visit(node) {
node.id = node.uuid;
delete node.oid;
list.push(node);
var children = node.children;
if (children) {
for (var i = 0, len = children.length; i < len; i++) {
const child = children[i];
child.parent = node.id;
visit(children[i]);
}
}
node.children = [];
}
visit(root);
return list;
}
/**
* @private
*/
const utils = {
xmlToJson: xmlToJson,
clone: clone,
compressGuid: compressGuid,
findNodeOfType: findNodeOfType,
timeout: timeout,
httpRequest: httpRequest,
loadJSON: loadJSON,
loadArraybuffer: loadArraybuffer,
queryString: queryString,
isArray: isArray,
isString: isString,
isNumeric: isNumeric,
isID: isID,
isSameComponent: isSameComponent,
isFunction: isFunction,
isObject: isObject,
copy: copy,
apply: apply,
apply2: apply2,
applyIf: applyIf,
isEmptyObject: isEmptyObject,
inQuotes: inQuotes,
concat: concat,
flattenParentChildHierarchy: flattenParentChildHierarchy
};
/**
* Default server client which loads content for a {@link BIMViewer} via HTTP from the file system.
*
* A BIMViewer is instantiated with an instance of this class.
*
* To load content from an alternative source, instantiate BIMViewer with your own custom implementation of this class.
*/
class Server {
/**
* Constructs a Server.
*
* @param {*} [cfg] Server configuration.
* @param {String} [cfg.dataDir] Base directory for content.
*/
constructor(cfg = {}) {
this._dataDir = cfg.dataDir || "";
}
/**
* Gets information on all available projects.
*
* @param {Function} done Callback through which the JSON result is returned.
* @param {Function} error Callback through which an error message is returned on error.
*/
getProjects(done, error) {
const url = this._dataDir + "/projects/index.json";
utils.loadJSON(url, done, error);
}
/**
* Gets information for a project.
*
* @param {String} projectId ID of the project.
* @param {Function} done Callback through which the JSON result is returned.
* @param {Function} error Callback through which an error message is returned on error.
*/
getProject(projectId, done, error) {
const url = this._dataDir + "/projects/" + projectId + "/index.json";
utils.loadJSON(url, done, error);
}
/**
* Gets metadata for a model within a project.
*
* @param {String} projectId ID of the project.
* @param {String} modelId ID of the model.
* @param {Function} done Callback through which the JSON result is returned.
* @param {Function} error Callback through which an error message is returned on error.
*/
getMetadata(projectId, modelId, done, error) {
const url = this._dataDir + "/projects/" + projectId + "/models/" + modelId + "/metadata.json";
utils.loadJSON(url, done, error);
}
/**
* Gets geometry for a model within a project.
*
* @param {String} projectId ID of the project.
* @param {String} modelId ID of the model.
* @param {Function} done Callback through which the JSON result is returned.
* @param {Function} error Callback through which an error message is returned on error.
*/
getGeometry(projectId, modelId, done, error) {
const url = this._dataDir + "/projects/" + projectId + "/models/" + modelId + "/geometry.xkt";
utils.loadArraybuffer(url, done, error);
}
/**
* Gets metadata for an object within a model within a project.
*
* @param {String} projectId ID of the project.
* @param {String} modelId ID of the model.
* @param {String} objectId ID of the object.
* @param {Function} done Callback through which the JSON result is returned.
* @param {Function} error Callback through which an error message is returned on error.
*/
getObjectInfo(projectId, modelId, objectId, done, error) {
const url = this._dataDir + "/projects/" + projectId + "/models/" + modelId + "/objects/" + objectId + "/properties.json";
utils.loadJSON(url, done, error);
}
/**
* Gets existing issues for a model within a project.
*
* @param {String} projectId ID of the project.
* @param {String} modelId ID of the model.
* @param {Function} done Callback through which the JSON result is returned.
* @param {Function} error Callback through which an error message is returned on error.
*/
getIssues(projectId, modelId, done, error) {
const url = this._dataDir + "/projects/" + projectId + "/models/" + modelId + "/issues.json";
utils.loadJSON(url, done, error);
}
}
/** @private */
class Map {
constructor(items, baseId) {
this.items = items || [];
this._lastUniqueId = (baseId || 0) + 1;
}
/**
* Usage:
*
* id = myMap.addItem("foo") // ID internally generated
* id = myMap.addItem("foo", "bar") // ID is "foo"
*/
addItem() {
let item;
if (arguments.length === 2) {
const id = arguments[0];
item = arguments[1];
if (this.items[id]) { // Won't happen if given ID is string
throw "ID clash: '" + id + "'";
}
this.items[id] = item;
return id;
} else {
item = arguments[0] || {};
while (true) {
const findId = this._lastUniqueId++;
if (!this.items[findId]) {
this.items[findId] = item;
return findId;
}
}
}
}
removeItem(id) {
const item = this.items[id];
delete this.items[id];
return item;
}
}
/** @private */
class Controller {
/**
* @protected
*/
constructor(parent, cfg, server, viewer) {
this.bimViewer = (parent ? (parent.bimViewer || parent) : this);
this.server = parent ? parent.server : server;
this.viewer = parent ? parent.viewer : viewer;
this._children = [];
if (parent) {
parent._children.push(this);
}
this._subIdMap = null; // Subscription subId pool
this._subIdEvents = null; // Subscription subIds mapped to event names
this._eventSubs = null; // Event names mapped to subscribers
this._events = null; // Maps names to events
this._eventCallDepth = 0; // Helps us catch stack overflows from recursive events
this._enabled = null; // Used by #setEnabled() and #getEnabled()
this._active = null; // Used by #setActive() and #getActive()
}
/**
* Fires an event on this Controller.
*
* @protected
*
* @param {String} event The event type name
* @param {Object} value The event parameters
* @param {Boolean} [forget=false] When true, does not retain for subsequent subscribers
*/
fire(event, value, forget) {
if (!this._events) {
this._events = {};
}
if (!this._eventSubs) {
this._eventSubs = {};
}
if (forget !== true) {
this._events[event] = value || true; // Save notification
}
const subs = this._eventSubs[event];
let sub;
if (subs) { // Notify subscriptions
for (const subId in subs) {
if (subs.hasOwnProperty(subId)) {
sub = subs[subId];
this._eventCallDepth++;
if (this._eventCallDepth < 300) {
sub.callback.call(sub.scope, value);
} else {
this.error("fire: potential stack overflow from recursive event '" + event + "' - dropping this event");
}
this._eventCallDepth--;
}
}
}
}
/**
* Subscribes to an event on this Controller.
*
* The callback is be called with this component as scope.
*
* @param {String} event The event
* @param {Function} callback Called fired on the event
* @param {Object} [scope=this] Scope for the callback
* @return {String} Handle to the subscription, which may be used to unsubscribe with {@link #off}.
*/
on(event, callback, scope) {
if (!this._events) {
this._events = {};
}
if (!this._subIdMap) {
this._subIdMap = new Map(); // Subscription subId pool
}
if (!this._subIdEvents) {
this._subIdEvents = {};
}
if (!this._eventSubs) {
this._eventSubs = {};
}
let subs = this._eventSubs[event];
if (!subs) {
subs = {};
this._eventSubs[event] = subs;
}
const subId = this._subIdMap.addItem(); // Create unique subId
subs[subId] = {
callback: callback,
scope: scope || this
};
this._subIdEvents[subId] = event;
const value = this._events[event];
if (value !== undefined) { // A publication exists, notify callback immediately
callback.call(scope || this, value);
}
return subId;
}
/**
* Cancels an event subscription that was previously made with {@link Controller#on} or {@link Controller#once}.
*
* @param {String} subId Subscription ID
*/
off(subId) {
if (subId === undefined || subId === null) {
return;
}
if (!this._subIdEvents) {
return;
}
const event = this._subIdEvents[subId];
if (event) {
delete this._subIdEvents[subId];
const subs = this._eventSubs[event];
if (subs) {
delete subs[subId];
}
this._subIdMap.removeItem(subId); // Release subId
}
}
/**
* Subscribes to the next occurrence of the given event, then un-subscribes as soon as the event is handled.
*
* This is equivalent to calling {@link Controller#on}, and then calling {@link Controller#off} inside the callback function.
*
* @param {String} event Data event to listen to
* @param {Function} callback Called when fresh data is available at the event
* @param {Object} [scope=this] Scope for the callback
*/
once(event, callback, scope) {
const self = this;
const subId = this.on(event,
function (value) {
self.off(subId);
callback.call(scope || this, value);
},
scope);
}
/**
* Logs a console debugging message for this Controller.
*
* The console message will have this format: *````[LOG] [<component type> <component id>: <message>````*
*
* @protected
*
* @param {String} message The message to log
*/
log(message) {
message = "[LOG] " + message;
window.console.log(message);
}
/**
* Logs a warning for this Controller to the JavaScript console.
*
* The console message will have this format: *````[WARN] [<component type> =<component id>: <message>````*
*
* @protected
*
* @param {String} message The message to log
*/
warn(message) {
message = "[WARN] " + message;
window.console.warn(message);
}
/**
* Logs an error for this Controller to the JavaScript console.
*
* The console message will have this format: *````[ERROR] [<component type> =<component id>: <message>````*
*
* @protected
*
* @param {String} message The message to log
*/
error(message) {
message = "[ERROR] " + message;
window.console.error(message);
}
_mutexActivation(controllers) {
const numControllers = controllers.length;
for (let i = 0; i < numControllers; i++) {
const controller = controllers[i];
controller.on("active", (function () {
const _i = i;
return function (active) {
if (!active) {
return;
}
for (let j = 0; j < numControllers; j++) {
if (j === _i) {
continue;
}
controllers[j].setActive(false);
}
};
})());
}
}
/**
* Enables or disables this Controller.
*
* Fires an "enabled" event on update.
*
* @protected
*
*
* @param {boolean} enabled Whether or not to enable.
*/
setEnabled(enabled) {
if (this._enabled === enabled) {
return;
}
this._enabled = enabled;
this.fire("enabled", this._enabled);
}
/**
* Gets whether or not this Controller is enabled.
*
* @protected
*
* @returns {boolean}
*/
getEnabled() {
return this._enabled;
}
/**
* Activates or deactivates this Controller.
*
* Fires an "active" event on update.
*
* @protected
*
* @param {boolean} active Whether or not to activate.
*/
setActive(active) {
if (this._active === active) {
return;
}
this._active = active;
this.fire("active", this._active);
}
/**
* Gets whether or not this Controller is active.
*
* @protected
*
* @returns {boolean}
*/
getActive() {
return this._active;
}
/**
* Destroys this Controller.
*
* @protected
*
*/
destroy() {
if (this.destroyed) {
return;
}
/**
* Fired when this Controller is destroyed.
* @event destroyed
*/
this.fire("destroyed", this.destroyed = true);
this._subIdMap = null;
this._subIdEvents = null;
this._eventSubs = null;
this._events = null;
this._eventCallDepth = 0;
for (let i = 0, len = this._children.length; i < len; i++) {
this._children.destroy();
}
this._children = [];
}
}
/** @private */
class BusyModal extends Controller {
constructor(parent, cfg = {}) {
super(parent, cfg);
const busyModalBackdropElement = cfg.busyModalBackdropElement || document.body;
if (!busyModalBackdropElement) {
throw "Missing config: busyModalBackdropElement";
}
this._modal = document.createElement("div");
this._modal.classList.add("xeokit-busy-modal");
this._modal.innerHTML = '<div class="xeokit-busy-modal-content"><div class="xeokit-busy-modal-body"><div class="xeokit-busy-modal-message">Default text</div></div></div>';
busyModalBackdropElement.appendChild(this._modal);
this._modalVisible = false;
this._modal.style.display = 'hidden';
}
show(message) {
this._modalVisible = true;
this._modal.querySelector('.xeokit-busy-modal-message').innerText = message;
this._modal.style.display = 'block';
}
hide() {
this._modalVisible = false;
this._modal.style.display = 'none';
}
destroy() {
super.destroy();
if (this._modal) {
this._modal.parentNode.removeChild(this._modal);
this._modal = null;
}
}
}
// Some temporary vars to help avoid garbage collection
const tempMat1 = new Float32Array(16);
const tempMat2 = new Float32Array(16);
const tempVec4 = new Float32Array(4);
/**
* @private
*/
const math = {
MAX_DOUBLE: Number.MAX_VALUE,
MIN_DOUBLE: Number.MIN_VALUE,
/**
* The number of radiians in a degree (0.0174532925).
* @property DEGTORAD
* @type {Number}
*/
DEGTORAD: 0.0174532925,
/**
* The number of degrees in a radian.
* @property RADTODEG
* @type {Number}
*/
RADTODEG: 57.295779513,
/**
* Returns a new, uninitialized two-element vector.
* @method vec2
* @param [values] Initial values.
* @static
* @returns {Number[]}
*/
vec2(values) {
return new Float32Array(values || 2);
},
/**
* Returns a new, uninitialized three-element vector.
* @method vec3
* @param [values] Initial values.
* @static
* @returns {Number[]}
*/
vec3(values) {
return new Float32Array(values || 3);
},
/**
* Returns a new, uninitialized four-element vector.
* @method vec4
* @param [values] Initial values.
* @static
* @returns {Number[]}
*/
vec4(values) {
return new Float32Array(values || 4);
},
/**
* Returns a new, uninitialized 3x3 matrix.
* @method mat3
* @param [values] Initial values.
* @static
* @returns {Number[]}
*/
mat3(values) {
return new Float32Array(values || 9);
},
/**
* Converts a 3x3 matrix to 4x4
* @method mat3ToMat4
* @param mat3 3x3 matrix.
* @param mat4 4x4 matrix
* @static
* @returns {Number[]}
*/
mat3ToMat4(mat3, mat4 = new Float32Array(16)) {
mat4[0] = mat3[0];
mat4[1] = mat3[1];
mat4[2] = mat3[2];
mat4[3] = 0;
mat4[4] = mat3[3];
mat4[5] = mat3[4];
mat4[6] = mat3[5];
mat4[7] = 0;
mat4[8] = mat3[6];
mat4[9] = mat3[7];
mat4[10] = mat3[8];
mat4[11] = 0;
mat4[12] = 0;
mat4[13] = 0;
mat4[14] = 0;
mat4[15] = 1;
return mat4;
},
/**
* Returns a new, uninitialized 4x4 matrix.
* @method mat4
* @param [values] Initial values.
* @static
* @returns {Number[]}
*/
mat4(values) {
return new Float32Array(values || 16);
},
/**
* Converts a 4x4 matrix to 3x3
* @method mat4ToMat3
* @param mat4 4x4 matrix.
* @param mat3 3x3 matrix
* @static
* @returns {Number[]}
*/
mat4ToMat3(mat4, mat3) { // TODO
//return new Float32Array(values || 9);
},
/**
* Returns a new UUID.
* @method createUUID
* @static
* @return string The new UUID
*/
//createUUID: function () {
// // http://www.broofa.com/Tools/Math.uuid.htm
// var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
// var uuid = new Array(36);
// var rnd = 0;
// var r;
// return function () {
// for (var i = 0; i < 36; i++) {
// if (i === 8 || i === 13 || i === 18 || i === 23) {
// uuid[i] = '-';
// } else if (i === 14) {
// uuid[i] = '4';
// } else {
// if (rnd <= 0x02) {
// rnd = 0x2000000 + ( Math.random() * 0x1000000 ) | 0;
// }
// r = rnd & 0xf;
// rnd = rnd >> 4;
// uuid[i] = chars[( i === 19 ) ? ( r & 0x3 ) | 0x8 : r];
// }
// }
// return uuid.join('');
// };
//}(),
//
createUUID: ((() => {
const lut = [];
for (let i = 0; i < 256; i++) {
lut[i] = (i < 16 ? '0' : '') + (i).toString(16);
}
return () => {
const d0 = Math.random() * 0xffffffff | 0;
const d1 = Math.random() * 0xffffffff | 0;
const d2 = Math.random() * 0xffffffff | 0;
const d3 = Math.random() * 0xffffffff | 0;
return `${lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff]}-${lut[d1 & 0xff]}${lut[d1 >> 8 & 0xff]}-${lut[d1 >> 16 & 0x0f | 0x40]}${lut[d1 >> 24 & 0xff]}-${lut[d2 & 0x3f | 0x80]}${lut[d2 >> 8 & 0xff]}-${lut[d2 >> 16 & 0xff]}${lut[d2 >> 24 & 0xff]}${lut[d3 & 0xff]}${lut[d3 >> 8 & 0xff]}${lut[d3 >> 16 & 0xff]}${lut[d3 >> 24 & 0xff]}`;
};
}))(),
/**
* Clamps a value to the given range.
* @param {Number} value Value to clamp.
* @param {Number} min Lower bound.
* @param {Number} max Upper bound.
* @returns {Number} Clamped result.
*/
clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
},
/**
* Floating-point modulus
* @method fmod
* @static
* @param {Number} a
* @param {Number} b
* @returns {*}
*/
fmod(a, b) {
if (a < b) {
console.error("math.fmod : Attempting to find modulus within negative range - would be infinite loop - ignoring");
return a;
}
while (b <= a) {
a -= b;
}
return a;
},
/**
* Negates a four-element vector.
* @method negateVec4
* @static
* @param {Array(Number)} v Vector to negate
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, v otherwise
*/
negateVec4(v, dest) {
if (!dest) {
dest = v;
}
dest[0] = -v[0];
dest[1] = -v[1];
dest[2] = -v[2];
dest[3] = -v[3];
return dest;
},
/**
* Adds one four-element vector to another.
* @method addVec4
* @static
* @param {Array(Number)} u First vector
* @param {Array(Number)} v Second vector
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, u otherwise
*/
addVec4(u, v, dest) {
if (!dest) {
dest = u;
}
dest[0] = u[0] + v[0];
dest[1] = u[1] + v[1];
dest[2] = u[2] + v[2];
dest[3] = u[3] + v[3];
return dest;
},
/**
* Adds a scalar value to each element of a four-element vector.
* @method addVec4Scalar
* @static
* @param {Array(Number)} v The vector
* @param {Number} s The scalar
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, v otherwise
*/
addVec4Scalar(v, s, dest) {
if (!dest) {
dest = v;
}
dest[0] = v[0] + s;
dest[1] = v[1] + s;
dest[2] = v[2] + s;
dest[3] = v[3] + s;
return dest;
},
/**
* Adds one three-element vector to another.
* @method addVec3
* @static
* @param {Array(Number)} u First vector
* @param {Array(Number)} v Second vector
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, u otherwise
*/
addVec3(u, v, dest) {
if (!dest) {
dest = u;
}
dest[0] = u[0] + v[0];
dest[1] = u[1] + v[1];
dest[2] = u[2] + v[2];
return dest;
},
/**
* Adds a scalar value to each element of a three-element vector.
* @method addVec4Scalar
* @static
* @param {Array(Number)} v The vector
* @param {Number} s The scalar
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, v otherwise
*/
addVec3Scalar(v, s, dest) {
if (!dest) {
dest = v;
}
dest[0] = v[0] + s;
dest[1] = v[1] + s;
dest[2] = v[2] + s;
return dest;
},
/**
* Subtracts one four-element vector from another.
* @method subVec4
* @static
* @param {Array(Number)} u First vector
* @param {Array(Number)} v Vector to subtract
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, u otherwise
*/
subVec4(u, v, dest) {
if (!dest) {
dest = u;
}
dest[0] = u[0] - v[0];
dest[1] = u[1] - v[1];
dest[2] = u[2] - v[2];
dest[3] = u[3] - v[3];
return dest;
},
/**
* Subtracts one three-element vector from another.
* @method subVec3
* @static
* @param {Array(Number)} u First vector
* @param {Array(Number)} v Vector to subtract
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, u otherwise
*/
subVec3(u, v, dest) {
if (!dest) {
dest = u;
}
dest[0] = u[0] - v[0];
dest[1] = u[1] - v[1];
dest[2] = u[2] - v[2];
return dest;
},
/**
* Subtracts one two-element vector from another.
* @method subVec2
* @static
* @param {Array(Number)} u First vector
* @param {Array(Number)} v Vector to subtract
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, u otherwise
*/
subVec2(u, v, dest) {
if (!dest) {
dest = u;
}
dest[0] = u[0] - v[0];
dest[1] = u[1] - v[1];
return dest;
},
/**
* Subtracts a scalar value from each element of a four-element vector.
* @method subVec4Scalar
* @static
* @param {Array(Number)} v The vector
* @param {Number} s The scalar
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, v otherwise
*/
subVec4Scalar(v, s, dest) {
if (!dest) {
dest = v;
}
dest[0] = v[0] - s;
dest[1] = v[1] - s;
dest[2] = v[2] - s;
dest[3] = v[3] - s;
return dest;
},
/**
* Sets each element of a 4-element vector to a scalar value minus the value of that element.
* @method subScalarVec4
* @static
* @param {Array(Number)} v The vector
* @param {Number} s The scalar
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, v otherwise
*/
subScalarVec4(v, s, dest) {
if (!dest) {
dest = v;
}
dest[0] = s - v[0];
dest[1] = s - v[1];
dest[2] = s - v[2];
dest[3] = s - v[3];
return dest;
},
/**
* Multiplies one three-element vector by another.
* @method mulVec3
* @static
* @param {Array(Number)} u First vector
* @param {Array(Number)} v Second vector
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, u otherwise
*/
mulVec4(u, v, dest) {
if (!dest) {
dest = u;
}
dest[0] = u[0] * v[0];
dest[1] = u[1] * v[1];
dest[2] = u[2] * v[2];
dest[3] = u[3] * v[3];
return dest;
},
/**
* Multiplies each element of a four-element vector by a scalar.
* @method mulVec34calar
* @static
* @param {Array(Number)} v The vector
* @param {Number} s The scalar
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, v otherwise
*/
mulVec4Scalar(v, s, dest) {
if (!dest) {
dest = v;
}
dest[0] = v[0] * s;
dest[1] = v[1] * s;
dest[2] = v[2] * s;
dest[3] = v[3] * s;
return dest;
},
/**
* Multiplies each element of a three-element vector by a scalar.
* @method mulVec3Scalar
* @static
* @param {Array(Number)} v The vector
* @param {Number} s The scalar
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, v otherwise
*/
mulVec3Scalar(v, s, dest) {
if (!dest) {
dest = v;
}
dest[0] = v[0] * s;
dest[1] = v[1] * s;
dest[2] = v[2] * s;
return dest;
},
/**
* Multiplies each element of a two-element vector by a scalar.
* @method mulVec2Scalar
* @static
* @param {Array(Number)} v The vector
* @param {Number} s The scalar
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, v otherwise
*/
mulVec2Scalar(v, s, dest) {
if (!dest) {
dest = v;
}
dest[0] = v[0] * s;
dest[1] = v[1] * s;
return dest;
},
/**
* Divides one three-element vector by another.
* @method divVec3
* @static
* @param {Array(Number)} u First vector
* @param {Array(Number)} v Second vector
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, u otherwise
*/
divVec3(u, v, dest) {
if (!dest) {
dest = u;
}
dest[0] = u[0] / v[0];
dest[1] = u[1] / v[1];
dest[2] = u[2] / v[2];
return dest;
},
/**
* Divides one four-element vector by another.
* @method divVec4
* @static
* @param {Array(Number)} u First vector
* @param {Array(Number)} v Second vector
* @param {Array(Number)} [dest] Destination vector
* @return {Array(Number)} dest if specified, u otherwise
*/
divVec4(u, v, dest) {
if (!dest) {
dest = u;
}
dest[0] = u[0] / v[0];
dest[1] = u[1] / v[1];
dest[2] = u[2] / v[2];
dest[3] = u[3] / v[3];
return dest;
},
/**
* Divides a scalar by a three-element vector, returning a new vector.
* @method divScalarVec3
* @static
* @param v vec3
* @param s scalar
* @param dest vec3 - optional destination
* @return [] dest if specified, v otherwise
*/
divScalarVec3(s, v, dest) {
if (!dest) {
dest = v;
}
dest[0] = s / v[0];
dest[1] = s / v[1];
dest[2] = s / v[2];
return dest;
},
/**
* Divides a three-element vector by a scalar.
* @method divVec3Scalar
* @static
* @param v vec3
* @param s scalar
* @param dest vec3 - optional destination
* @return [] dest if specified, v otherwise
*/
divVec3Scalar(v, s, dest) {
if (!dest) {
dest = v;
}
dest[0] = v[0] / s;
dest[1] = v[1] / s;
dest[2] = v[2] / s;
return dest;
},
/**
* Divides a four-element vector by a scalar.
* @method divVec4Scalar
* @static
* @param v vec4
* @param s scalar
* @param dest vec4 - optional destination
* @return [] dest if specified, v otherwise
*/
divVec4Scalar(v, s, dest) {
if (!dest) {
dest = v;
}
dest[0] = v[0] / s;
dest[1] = v[1] / s;
dest[2] = v[2] / s;
dest[3] = v[3] / s;
return dest;
},
/**
* Divides a scalar by a four-element vector, returning a new vector.
* @method divScalarVec4
* @static
* @param s scalar
* @param v vec4
* @param dest vec4 - optional destination
* @return [] dest if specified, v otherwise
*/
divScalarVec4(s, v, dest) {
if (!dest) {
dest = v;
}
dest[0] = s / v[0];
dest[1] = s / v[1];
dest[2] = s / v[2];
dest[3] = s / v[3];
return dest;
},
/**
* Returns the dot product of two four-element vectors.
* @method dotVec4
* @static
* @param {Array(Number)} u First vector
* @param {Array(Number)} v Second vector
* @return The dot product
*/
dotVec4(u, v) {
return (u[0] * v[0] + u[1] * v[1] + u[2] * v[2] + u[3] * v[3]);
},
/**
* Returns the cross product of two four-element vectors.
* @method cross3Vec4
* @static
* @param {Array(Number)} u First vector
* @param {Array(Number)} v Second vector
* @return The cross product
*/
cross3Vec4(u, v) {
const u0 = u[0];
const u1 = u[1];
const u2 = u[2];
const v0 = v[0];
const v1 = v[1];
const v2 = v[2];
return [
u1 * v2 - u2 * v1,
u2 * v0 - u0 * v2,
u0 * v1 - u1 * v0,
0.0];
},
/**
* Returns the cross product of two three-element vectors.
* @method cross3Vec3
* @static
* @param {Array(Number)} u First vector
* @param {Array(Number)} v Second vector
* @return The cross product
*/
cross3Vec3(u, v, dest) {
if (!dest) {
dest = u;
}
const x = u[0];
const y = u[1];
const z = u[2];
const x2 = v[0];
const y2 = v[1];
const z2 = v[2];
dest[0] = y * z2 - z * y2;
dest[1] = z * x2 - x * z2;
dest[2] = x * y2 - y * x2;
return dest;
},
sqLenVec4(v) { // TODO
return math.dotVec4(v, v);
},
/**
* Returns the length of a four-element vector.
* @method lenVec4
* @static
* @param {Array(Number)} v The vector
* @return The length
*/
lenVec4(v) {
return Math.sqrt(math.sqLenVec4(v));
},
/**
* Returns the dot product of two three-element vectors.
* @method dotVec3
* @static
* @param {Array(Number)} u First vector
* @param {Array(Number)} v Second vector
* @return The dot product
*/
dotVec3(u, v) {
return (u[0] * v[0] + u[1] * v[1] + u[2] * v[2]);
},
/**
* Returns the dot product of two two-element vectors.
* @method dotVec4
* @static
* @param {Array(Number)} u First vector
* @param {Array(Number)} v Second vector
* @return The dot product
*/
dotVec2(u, v) {
return (u[0] * v[0] + u[1] * v[1]);
},
sqLenVec3(v) {
return math.dotVec3(v, v);
},
sqLenVec2(v) {
return math.dotVec2(v, v);
},
/**
* Returns the length of a three-element vector.
* @method lenVec3
* @static
* @param {Array(Number)} v The vector
* @return The length
*/
lenVec3(v) {
return Math.sqrt(math.sqLenVec3(v));
},
distVec3: ((() => {
const vec = new Float32Array(3);
return (v, w) => math.lenVec3(math.subVec3(v, w, vec));
}))(),
/**
* Returns the length of a two-element vector.
* @method lenVec2
* @static
* @param {Array(Number)} v The vector
* @return The length
*/
lenVec2(v) {
return Math.sqrt(math.sqLenVec2(v));
},
distVec2: ((() => {
const vec = new Float32Array(2);
return (v, w) => math.lenVec2(math.subVec2(v, w, vec));
}))(),
/**
* @method rcpVec3
* @static
* @param v vec3
* @param dest vec3 - optional destination
* @return [] dest if specified, v otherwise
*
*/
rcpVec3(v, dest) {
return math.divScalarVec3(1.0, v, dest);
},
/**
* Normalizes a four-element vector
* @method normalizeVec4
* @static
* @param v vec4
* @param dest vec4 - optional destination
* @return [] dest if specified, v otherwise
*
*/
normalizeVec4(v, dest) {
const f = 1.0 / math.lenVec4(v);
return math.mulVec4Scalar(v, f, dest);
},
/**
* Normalizes a three-element vector
* @method normalizeVec4
* @static
*/
normalizeVec3(v, dest) {
const f = 1.0 / math.lenVec3(v);
return math.mulVec3Scalar(v, f, dest);
},
/**
* Normalizes a two-element vector
* @method normalizeVec2
* @static
*/
normalizeVec2(v, dest) {
const f = 1.0 / math.lenVec2(v);
return math.mulVec2Scalar(v, f, dest);
},
/**
* Gets the angle between two vectors
* @method angleVec3
* @param v
* @param w
* @returns {number}
*/
angleVec3(v, w) {
let theta = math.dotVec3(v, w) / (Math.sqrt(math.sqLenVec3(v) * math.sqLenVec3(w)));
theta = theta < -1 ? -1 : (theta > 1 ? 1 : theta); // Clamp to handle numerical problems
return Math.acos(theta);
},
/**
* Creates a three-element vector from the rotation part of a sixteen-element matrix.
* @param m
* @param dest
*/
vec3FromMat4Scale: ((() => {
const tempVec3 = new Float32Array(3);
return (m, dest) => {
tempVec3[0] = m[0];
tempVec3[1] = m[1];
tempVec3[2] = m[2];
dest[0] = math.lenVec3(tempVec3);
tempVec3[0] = m[4];
tempVec3[1] = m[5];
tempVec3[2] = m[6];
dest[1] = math.lenVec3(tempVec3);
tempVec3[0] = m[8];
tempVec3[1] = m[9];
tempVec3[2] = m[10];
dest[2] = math.lenVec3(tempVec3);
return dest;
};
}))(),
/**
* Converts an n-element vector to a JSON-serializable
* array with values rounded to two decimal places.
*/
vecToArray: ((() => {
function trunc(v) {
return Math.round(v * 100000) / 100000
}
return v => {
v = Array.prototype.slice.call(v);
for (let i = 0, len = v.length; i < len; i++) {
v[i] = trunc(v[i]);
}
return v;
};
}))(),
/**
* Converts a 3-element vector from an array to an object of the form ````{x:999, y:999, z:999}````.
* @param arr
* @returns {{x: *, y: *, z: *}}
*/
xyzArrayToObject(arr) {
return {"x": arr[0], "y": arr[1], "z": arr[2]};
},
/**
* Converts a 3-element vector object of the form ````{x:999, y:999, z:999}```` to an array.
* @param xyz
* @param [arry]
* @returns {*[]}
*/
xyzObjectToArray(xyz, arry) {
arry = arry || new Float32Array(3);
arry[0] = xyz.x;
arry[1] = xyz.y;
arry[2] = xyz.z;
return arry;
},
/**
* Duplicates a 4x4 identity matrix.
* @method dupMat4
* @static
*/
dupMat4(m) {
return m.slice(0, 16);
},
/**
* Extracts a 3x3 matrix from a 4x4 matrix.
* @method mat4To3
* @static
*/
mat4To3(m) {
return [
m[0], m[1], m[2],
m[4], m[5], m[6],
m[8], m[9], m[10]
];
},
/**
* Returns a 4x4 matrix with each element set to the given scalar value.
* @method m4s
* @static
*/
m4s(s) {
return [
s, s, s, s,
s, s, s, s,
s, s, s, s,
s, s, s, s
];
},
/**
* Returns a 4x4 matrix with each element set to zero.
* @method setMat4ToZeroes
* @static
*/
setMat4ToZeroes() {
return math.m4s(0.0);
},
/**
* Returns a 4x4 matrix with each element set to 1.0.
* @method setMat4ToOnes
* @static
*/
setMat4ToOnes() {
return math.m4s(1.0);
},
/**
* Returns a 4x4 matrix with each element set to 1.0.
* @method setMat4ToOnes
* @static
*/
diagonalMat4v(v) {
return new Float32Array([
v[0], 0.0, 0.0, 0.0,
0.0, v[1], 0.0, 0.0,
0.0, 0.0, v[2], 0.0,
0.0, 0.0, 0.0, v[3]
]);
},
/**
* Returns a 4x4 matrix with diagonal elements set to the given vector.
* @method diagonalMat4c
* @static
*/
diagonalMat4c(x, y, z, w) {
return math.diagonalMat4v([x, y, z, w]);
},
/**
* Returns a 4x4 matrix with diagonal elements set to the given scalar.
* @method diagonalMat4s
* @static
*/
diagonalMat4s(s) {
return math.diagonalMat4c(s, s, s, s);
},
/**
* Returns a 4x4 identity matrix.
* @method identityMat4
* @static
*/
id