UNPKG

@xeokit/xeokit-viewer

Version:
1,882 lines (1,720 loc) 2.17 MB
/** * @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